cleanup global-setup
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pull_request_closed/ci Pipeline was successful

This commit is contained in:
Madison Grubb
2026-02-12 14:05:17 -05:00
parent 06f9a5b96e
commit 14c70e3a4b
2 changed files with 13 additions and 38 deletions

View File

@@ -22,7 +22,7 @@ Open http://localhost:3000. The app requires login by default; you will see the
Camera and geolocation in the browser require a **secure context** (HTTPS) when you open the app from your phone. To test Share live from a device on your LAN without buying a domain or cert: Camera and geolocation in the browser require a **secure context** (HTTPS) when you open the app from your phone. To test Share live from a device on your LAN without buying a domain or cert:
1. Generate a self-signed cert (once). Use your machines LAN IP so the phone can use it: 1. Generate a self-signed cert (once). Use your machine's LAN IP so the phone can use it:
```bash ```bash
chmod +x scripts/gen-dev-cert.sh chmod +x scripts/gen-dev-cert.sh
./scripts/gen-dev-cert.sh 192.168.1.123 ./scripts/gen-dev-cert.sh 192.168.1.123
@@ -34,7 +34,7 @@ Camera and geolocation in the browser require a **secure context** (HTTPS) when
npm run dev npm run dev
``` ```
3. On your phone, open **https://192.168.1.123:3000** (same IP you passed above). Accept the browsers “untrusted certificate” warning once (e.g. Advanced → Proceed). Then log in and use Share live; camera and location will work. 3. On your phone, open **https://192.168.1.123:3000** (same IP you passed above). Accept the browser's “untrusted certificate” warning once (e.g. Advanced → Proceed). Then log in and use Share live; camera and location will work.
Without the certs, `npm run dev` still runs over HTTP as before. Without the certs, `npm run dev` still runs over HTTP as before.
@@ -48,7 +48,7 @@ The **Share live** feature uses WebRTC for real-time video streaming from mobile
- **Mediasoup** server (runs automatically in the Nuxt process) - **Mediasoup** server (runs automatically in the Nuxt process)
- **mediasoup-client** (browser library, included automatically) - **mediasoup-client** (browser library, included automatically)
**Streaming from a phone on your LAN:** The server auto-detects your machines LAN IP (from network interfaces) and uses it for WebRTC. Open **https://<your-LAN-IP>:3000** on both phone and laptop (same IP as for your dev cert). To override (e.g. Docker or multiple NICs), set `MEDIASOUP_ANNOUNCED_IP`. Ensure firewall allows UDP/TCP ports 4000049999 on the server. **Streaming from a phone on your LAN:** The server auto-detects your machine's LAN IP (from network interfaces) and uses it for WebRTC. Open **https://<your-LAN-IP>:3000** on both phone and laptop (same IP as for your dev cert). To override (e.g. Docker or multiple NICs), set `MEDIASOUP_ANNOUNCED_IP`. Ensure firewall allows UDP/TCP ports 4000049999 on the server.
See [docs/live-streaming.md](docs/live-streaming.md) for architecture details. See [docs/live-streaming.md](docs/live-streaming.md) for architecture details.
@@ -65,7 +65,7 @@ See [docs/live-streaming.md](docs/live-streaming.md) for architecture details.
- **Devices**: Manage cameras/devices via the API (`/api/devices`) or the Members/Cameras UI. Each device needs `name`, `device_type`, `lat`, `lng`, `stream_url`, and `source_type` (`mjpeg` or `hls`). - **Devices**: Manage cameras/devices via the API (`/api/devices`) or the Members/Cameras UI. Each device needs `name`, `device_type`, `lat`, `lng`, `stream_url`, and `source_type` (`mjpeg` or `hls`).
- **Environment**: No required env vars for basic run. For production, set `HOST=0.0.0.0` and `PORT` as needed (e.g. in Docker/Helm). - **Environment**: No required env vars for basic run. For production, set `HOST=0.0.0.0` and `PORT` as needed (e.g. in Docker/Helm).
- **Authentication**: The login page always offers password sign-in (local). Optionally set `BOOTSTRAP_EMAIL` and `BOOTSTRAP_PASSWORD` before the first run to create the first admin; otherwise a default admin is created and its credentials are printed in the terminal. To also show an OIDC sign-in button, configure `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, and optionally `OIDC_LABEL`, `OIDC_REDIRECT_URI`. See [docs/auth.md](docs/auth.md) for provider-specific examples. - **Authentication**: The login page always offers password sign-in (local). Optionally set `BOOTSTRAP_EMAIL` and `BOOTSTRAP_PASSWORD` before the first run to create the first admin; otherwise a default admin is created and its credentials are printed in the terminal. To also show an OIDC sign-in button, configure `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, and optionally `OIDC_LABEL`, `OIDC_REDIRECT_URI`. See [docs/auth.md](docs/auth.md) for provider-specific examples.
- **Bootstrap admin** (when using local auth): The server initializes the database and runs bootstrap at startup. On first run (no users in the database), it creates the first admin. If you set `BOOTSTRAP_EMAIL` and `BOOTSTRAP_PASSWORD` before starting, that account is created. If you dont set them, a default admin is created (identifier: `admin`) with a random password and the credentials are printed in the terminal—copy them and sign in at `/login`, then change the password or add users via Members. Use **Members** to change roles (admin, leader, member). Only admins can change roles; admins and leaders can edit POIs. - **Bootstrap admin** (when using local auth): The server initializes the database and runs bootstrap at startup. On first run (no users in the database), it creates the first admin. If you set `BOOTSTRAP_EMAIL` and `BOOTSTRAP_PASSWORD` before starting, that account is created. If you don't set them, a default admin is created (identifier: `admin`) with a random password and the credentials are printed in the terminal—copy them and sign in at `/login`, then change the password or add users via Members. Use **Members** to change roles (admin, leader, member). Only admins can change roles; admins and leaders can edit POIs.
- **Database**: SQLite file at `data/kestrelos.db` (created automatically). Contains users, sessions, and POIs. - **Database**: SQLite file at `data/kestrelos.db` (created automatically). Contains users, sessions, and POIs.
## Docker ## Docker

View File

@@ -1,72 +1,50 @@
/**
* Global setup for E2E tests.
* Runs once before all tests.
*/
import { existsSync, mkdirSync } from 'node:fs' import { existsSync, mkdirSync } from 'node:fs'
import { join, dirname } from 'node:path' import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { execSync } from 'node:child_process' import { execSync } from 'node:child_process'
const _dirname = dirname(fileURLToPath(import.meta.url)) const projectRoot = join(dirname(fileURLToPath(import.meta.url)), '../../..')
const projectRoot = join(_dirname, '../../..')
const devCertsDir = join(projectRoot, '.dev-certs') const devCertsDir = join(projectRoot, '.dev-certs')
const devKey = join(devCertsDir, 'key.pem') const devKey = join(devCertsDir, 'key.pem')
const devCert = join(devCertsDir, 'cert.pem') const devCert = join(devCertsDir, 'cert.pem')
// Import server modules (ES modules)
const { getDb } = await import('../../server/utils/db.js') const { getDb } = await import('../../server/utils/db.js')
const { hashPassword } = await import('../../server/utils/password.js') const { hashPassword } = await import('../../server/utils/password.js')
const { TEST_ADMIN } = await import('./fixtures/users.js') const { TEST_ADMIN } = await import('./fixtures/users.js')
function ensureDevCerts() { const ensureDevCerts = () => {
if (existsSync(devKey) && existsSync(devCert)) { if (existsSync(devKey) && existsSync(devCert)) return
return // Certs already exist
}
// Create .dev-certs directory
mkdirSync(devCertsDir, { recursive: true }) mkdirSync(devCertsDir, { recursive: true })
// Generate self-signed cert for localhost/127.0.0.1
const SAN = 'subjectAltName=IP:127.0.0.1,DNS:localhost'
try { try {
execSync( execSync(
`openssl req -x509 -newkey rsa:2048 -keyout "${devKey}" -out "${devCert}" -days 365 -nodes -subj "/CN=localhost" -addext "${SAN}"`, `openssl req -x509 -newkey rsa:2048 -keyout "${devKey}" -out "${devCert}" -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName=IP:127.0.0.1,DNS:localhost"`,
{ cwd: projectRoot, stdio: 'inherit' }, { cwd: projectRoot, stdio: process.env.CI ? 'pipe' : 'inherit' },
) )
console.log('[test] Generated .dev-certs/key.pem and .dev-certs/cert.pem')
} }
catch (error) { catch (error) {
throw new Error(`Failed to generate dev certificates: ${error.message}`) throw new Error(`Failed to generate dev certificates: ${error.message}`)
} }
} }
async function globalSetup() { export default async function globalSetup() {
ensureDevCerts() ensureDevCerts()
let retries = 3 let retries = 3
let lastError = null
while (retries > 0) { while (retries > 0) {
try { try {
const { get, run } = await getDb() const { get, run } = await getDb()
const existingUser = await get('SELECT id FROM users WHERE identifier = ?', [TEST_ADMIN.identifier]) const existing = await get('SELECT id FROM users WHERE identifier = ?', [TEST_ADMIN.identifier])
if (!existingUser) { if (!existing) {
const id = crypto.randomUUID()
const now = new Date().toISOString()
await run( await run(
'INSERT INTO users (id, identifier, password_hash, role, created_at, auth_provider, oidc_issuer, oidc_sub) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', 'INSERT INTO users (id, identifier, password_hash, role, created_at, auth_provider, oidc_issuer, oidc_sub) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[id, TEST_ADMIN.identifier, hashPassword(TEST_ADMIN.password), TEST_ADMIN.role, now, 'local', null, null], [crypto.randomUUID(), TEST_ADMIN.identifier, hashPassword(TEST_ADMIN.password), TEST_ADMIN.role, new Date().toISOString(), 'local', null, null],
) )
console.log(`[test] Created test admin user: ${TEST_ADMIN.identifier}`)
}
else {
console.log(`[test] Test admin user already exists: ${TEST_ADMIN.identifier}`)
} }
return return
} }
catch (error) { catch (error) {
lastError = error
if (error.message?.includes('SQLITE_BUSY') || error.message?.includes('database is locked')) { if (error.message?.includes('SQLITE_BUSY') || error.message?.includes('database is locked')) {
retries-- retries--
if (retries > 0) { if (retries > 0) {
@@ -77,7 +55,4 @@ async function globalSetup() {
throw error throw error
} }
} }
throw lastError
} }
export default globalSetup