@brewnet/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +184 -0
  2. package/dist/admin-server-DQVIEHV3.js +14 -0
  3. package/dist/admin-server-DQVIEHV3.js.map +1 -0
  4. package/dist/boilerplate-manager-P6QYUU7Q.js +29 -0
  5. package/dist/boilerplate-manager-P6QYUU7Q.js.map +1 -0
  6. package/dist/chunk-2VWMDHGI.js +1393 -0
  7. package/dist/chunk-2VWMDHGI.js.map +1 -0
  8. package/dist/chunk-4TJMJZMO.js +1173 -0
  9. package/dist/chunk-4TJMJZMO.js.map +1 -0
  10. package/dist/chunk-BAVGYMGA.js +114 -0
  11. package/dist/chunk-BAVGYMGA.js.map +1 -0
  12. package/dist/chunk-DH2VK3YI.js +293 -0
  13. package/dist/chunk-DH2VK3YI.js.map +1 -0
  14. package/dist/chunk-HCHY5UIQ.js +301 -0
  15. package/dist/chunk-HCHY5UIQ.js.map +1 -0
  16. package/dist/chunk-JFPHGZ6Z.js +254 -0
  17. package/dist/chunk-JFPHGZ6Z.js.map +1 -0
  18. package/dist/chunk-SIXBB6JU.js +2973 -0
  19. package/dist/chunk-SIXBB6JU.js.map +1 -0
  20. package/dist/chunk-SYV6PK3R.js +181 -0
  21. package/dist/chunk-SYV6PK3R.js.map +1 -0
  22. package/dist/chunk-ZKMWE5AH.js +444 -0
  23. package/dist/chunk-ZKMWE5AH.js.map +1 -0
  24. package/dist/cloudflare-client-TFT6VCXF.js +32 -0
  25. package/dist/cloudflare-client-TFT6VCXF.js.map +1 -0
  26. package/dist/compose-generator-O7GSIJ2S.js +19 -0
  27. package/dist/compose-generator-O7GSIJ2S.js.map +1 -0
  28. package/dist/frameworks-Z7VXDGP4.js +18 -0
  29. package/dist/frameworks-Z7VXDGP4.js.map +1 -0
  30. package/dist/index.d.ts +22 -0
  31. package/dist/index.js +7897 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/services/admin-daemon.d.ts +2 -0
  34. package/dist/services/admin-daemon.js +33 -0
  35. package/dist/services/admin-daemon.js.map +1 -0
  36. package/dist/stacks-M4FBTVO5.js +16 -0
  37. package/dist/stacks-M4FBTVO5.js.map +1 -0
  38. package/dist/state-2SI3P4JG.js +27 -0
  39. package/dist/state-2SI3P4JG.js.map +1 -0
  40. package/package.json +44 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/admin-server.ts","../src/services/app-manager.ts","../src/services/gitea-client.ts"],"sourcesContent":["/**\n * Brewnet CLI — Local Admin Panel Server (T101a)\n *\n * Node.js built-in HTTP server serving:\n * - Static HTML dashboard at GET /\n * - REST API per contracts/admin-api.md\n *\n * Port default: 8088 (localhost-only, no auth required)\n *\n * @module services/admin-server\n */\n\nimport { createServer, IncomingMessage, ServerResponse, Server } from 'node:http';\nimport { createConnection } from 'node:net';\nimport { join, resolve, extname } from 'node:path';\nimport { existsSync, readFileSync, writeFileSync, statSync, createReadStream } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { homedir } from 'node:os';\nimport Dockerode from 'dockerode';\nimport { addService, removeService } from './service-manager.js';\nimport { createBackup, listBackups } from './backup-manager.js';\nimport { getServiceDefinition, SERVICE_REGISTRY } from '../config/services.js';\n// SERVICE_DETAIL_MAP inlined from deleted status-page.ts (T044/T045)\nimport { getLastProject, loadState } from '../wizard/state.js';\nimport { logger } from '../utils/logger.js';\nimport { DomainManager } from './domain-manager.js';\nimport { verifyToken } from './cloudflare-client.js';\nimport type { WizardState, LogSource, UnifiedLogLevel } from '@brewnet/shared';\nimport { queryLogs, getLogStats } from '../utils/log-aggregator.js';\n// apps-page.ts import removed — HTML generation replaced by React SPA (T044)\nimport { createApp, getJobStatus, listApps, startApp, stopApp, removeApp as appRemove, getDeployHistory, listGiteaRepos, deployApp, rollbackApp, getAppGitInfo, getAppBranches, updateDeploySettings, getDeploySettings, getAppDir, detectBasePath } from './app-manager.js';\nimport type { DeploySettings } from '../types/app-entry.js';\nimport type { CreateAppOptions } from '../types/app-entry.js';\nimport { getStackById } from '../config/stacks.js';\n\n// ---------------------------------------------------------------------------\n// Types (per admin-api.md)\n// ---------------------------------------------------------------------------\n\nexport interface ServiceStatus {\n id: string;\n name: string;\n type: string;\n status: 'running' | 'stopped' | 'error' | 'not_installed';\n cpu: string;\n memory: string;\n uptime: string;\n port: number | null;\n url: string | null;\n externalUrl: string | null;\n removable: boolean;\n stackId?: string;\n}\n\nexport interface AdminServerOptions {\n port?: number;\n projectPath?: string;\n}\n\n// ---------------------------------------------------------------------------\n// HTML Dashboard (inline, dynamically generated with embedded config)\n// ---------------------------------------------------------------------------\n\n/** Shape of a single stack entry in .brewnet-boilerplate.json */\ninterface BoilerplateMeta {\n stackId: string;\n appDir?: string;\n backendUrl?: string;\n frontendUrl?: string;\n isUnified?: boolean;\n lang?: string;\n frameworkId?: string;\n dbDriver?: string;\n dbUser?: string;\n dbName?: string;\n gitBranch?: string;\n status?: string;\n}\n\ninterface DashboardConfig {\n adminUsername: string;\n passwordHint: string;\n domainProvider: string;\n quickTunnelUrl: string;\n zoneName: string;\n tunnelId: string;\n}\n\n// ---------------------------------------------------------------------------\n// Static icon assets — resolved once at module load from public/images/\n// ---------------------------------------------------------------------------\n\nconst PKG_ROOT = join(fileURLToPath(import.meta.url), '../../../..');\n\n// ---------------------------------------------------------------------------\n// Static file serving for React SPA (packages/admin-ui/dist)\n// ---------------------------------------------------------------------------\n\nconst ADMIN_UI_DIST = join(PKG_ROOT, 'packages/admin-ui/dist');\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.mjs': 'application/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.webmanifest': 'application/manifest+json',\n};\n\nfunction serveStaticFile(filePath: string, res: ServerResponse, statusCode = 200): void {\n const stat = statSync(filePath);\n const mime = MIME_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream';\n const isHashed = new RegExp('assets/[^/]+-[A-Za-z0-9]{8,}\\\\.[^.]+$').test(filePath);\n const cacheControl = isHashed ? 'public, max-age=31536000, immutable' : 'no-cache, no-store, must-revalidate';\n res.writeHead(statusCode, {\n 'Content-Type': mime,\n 'Content-Length': stat.size,\n 'Cache-Control': cacheControl,\n });\n createReadStream(filePath).pipe(res);\n}\n\n/** Brewnet SVG icon (inline string, served at /icon.svg) */\nconst ICON_SVG = (() => {\n const candidates = [\n join(PKG_ROOT, 'public/images/icon.svg'),\n join(PKG_ROOT, '../public/images/icon.svg'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return readFileSync(p, 'utf-8');\n }\n // Fallback: inline SVG (amber mug-wifi icon)\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"4 6 38 38\" fill=\"none\" stroke=\"#f5a623\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 26H32V34C32 36.8 29.8 39 27 39H13C10.2 39 8 36.8 8 34V26Z\" stroke-width=\"3.5\" fill=\"none\"/><path d=\"M32 28.5C35.5 28.5 37 30.5 37 32.5C37 34.5 35.5 36.5 32 36.5\" stroke-width=\"3.5\" fill=\"none\"/><circle cx=\"20\" cy=\"30\" r=\"2.2\" fill=\"#f5a623\" stroke=\"none\"/><path d=\"M16.5 20a5 5 0 0 1 7 0\" stroke-width=\"3.5\" fill=\"none\"/><path d=\"M13.5 15.5a10 10 0 0 1 13 0\" stroke-width=\"3.5\" fill=\"none\"/><path d=\"M10.5 11a15 15 0 0 1 19 0\" stroke-width=\"3.5\" fill=\"none\"/></svg>`;\n})();\n\n/** Brewnet favicon.ico (binary Buffer, served at /favicon.ico) */\nconst FAVICON_ICO = (() => {\n const candidates = [\n join(PKG_ROOT, 'public/images/favicon.ico'),\n join(PKG_ROOT, '../public/images/favicon.ico'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return readFileSync(p);\n }\n return null;\n})();\n\n// ---------------------------------------------------------------------------\n// Service catalog data — moved from deleted status-page.ts (T044/T045)\n// ---------------------------------------------------------------------------\n\ninterface ServiceDetailInfo {\n description: string;\n license: string;\n docs: string;\n features: string[];\n credentials: {\n method: 'env' | 'wizard' | 'cli' | 'basicauth' | 'none';\n summary: string;\n command?: string;\n };\n tips: string[];\n}\n\nconst SERVICE_DETAIL_MAP: Record<string, ServiceDetailInfo> = {\n Traefik: {\n description: 'Go-based open-source reverse proxy and load balancer',\n license: 'MIT',\n docs: 'https://traefik.io/traefik/',\n features: [\n 'Docker label-based automatic service discovery',\n 'Let\\'s Encrypt certificate auto-renewal',\n 'Built-in web dashboard for route monitoring',\n 'Middleware chain: BasicAuth, Rate Limit, IP Whitelist',\n 'HTTP to HTTPS automatic redirect',\n ],\n credentials: {\n method: 'basicauth',\n summary: 'No login in dev mode (--api.insecure=true). Use BasicAuth middleware for production.',\n command: 'htpasswd -nb admin YOUR_PASSWORD',\n },\n tips: [\n 'Remove --api.insecure=true in production and add BasicAuth or Authelia',\n 'Set exposedbydefault=false and explicitly enable each service with traefik.enable=true',\n 'Add --certificatesresolvers.le.acme.email=YOUR_EMAIL for Let\\'s Encrypt',\n ],\n },\n 'Traefik Dashboard': {\n description: 'Built-in Traefik web UI for monitoring routes, services, and middleware',\n license: 'MIT',\n docs: 'https://doc.traefik.io/traefik/operations/dashboard/',\n features: [\n 'Real-time view of HTTP/TCP routers',\n 'Service health and load balancer status',\n 'Middleware chain visualization',\n ],\n credentials: {\n method: 'none',\n summary: 'No authentication in dev mode (--api.insecure=true). Protected by BasicAuth in production.',\n },\n tips: [\n 'Dashboard URL requires trailing slash: /dashboard/',\n 'Secure with BasicAuth middleware before exposing externally',\n ],\n },\n Gitea: {\n description: 'Lightweight self-hosted Git service written in Go',\n license: 'MIT',\n docs: 'https://about.gitea.com/',\n features: [\n 'GitHub-like web UI with issues, PRs, wiki, project boards',\n 'Gitea Actions — GitHub Actions compatible CI/CD',\n 'Low memory footprint (~200 MB)',\n 'LDAP, OAuth2, SMTP authentication support',\n 'PostgreSQL, MySQL, SQLite backend support',\n ],\n credentials: {\n method: 'wizard',\n summary: 'First visit shows Installation Wizard. Create admin account in \"Administrator Account Settings\" section.',\n command: 'docker exec -it brewnet-gitea gitea admin user create --username admin --password PASSWORD --email admin@brewnet.dev --admin',\n },\n tips: [\n 'Set DISABLE_REGISTRATION=true to allow only admin-created accounts',\n 'Set REQUIRE_SIGNIN_VIEW=true to prevent anonymous repo browsing',\n 'SSH port mapped to 3022 to avoid conflict with host SSH (22)',\n ],\n },\n Nextcloud: {\n description: 'Self-hosted cloud storage platform (Google Drive/Dropbox alternative)',\n license: 'AGPL-3.0',\n docs: 'https://nextcloud.com/',\n features: [\n 'File sync, sharing, and collaboration',\n '200+ app extensions: calendar, contacts, notes, office docs',\n 'WebDAV protocol support',\n 'Desktop and mobile clients available',\n ],\n credentials: {\n method: 'env',\n summary: 'Uses admin credentials set in Pre-Step of brewnet init wizard.',\n command: 'docker exec -u www-data brewnet-nextcloud php occ user:add USERNAME --display-name=\"Display Name\"',\n },\n tips: [\n 'Redis connection recommended for file locking and cache performance',\n 'Add all access domains/IPs to NEXTCLOUD_TRUSTED_DOMAINS',\n 'Switch background jobs to cron: docker exec -u www-data brewnet-nextcloud php occ background:cron',\n ],\n },\n PostgreSQL: {\n description: 'Advanced open-source relational database',\n license: 'PostgreSQL (BSD-like)',\n docs: 'https://www.postgresql.org/',\n features: [\n 'Full ACID compliance with MVCC',\n 'Native JSON/JSONB support',\n 'Full-text search, PostGIS, time-series extensions',\n 'Logical and physical replication',\n ],\n credentials: {\n method: 'env',\n summary: 'Configured via POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB environment variables.',\n command: 'docker exec -it brewnet-postgresql psql -U brewnet -d brewnet_db',\n },\n tips: [\n 'Internal network only (brewnet-internal) — no host port exposed',\n 'Data persisted in named volume — safe across container restarts',\n 'Use init SQL scripts in docker-entrypoint-initdb.d/ for multi-DB setup',\n ],\n },\n MySQL: {\n description: 'Popular open-source relational database',\n license: 'GPL-2.0',\n docs: 'https://www.mysql.com/',\n features: [\n 'InnoDB storage engine with ACID transactions',\n 'JSON support and document store',\n 'Replication and clustering',\n 'Widely supported by web applications',\n ],\n credentials: {\n method: 'env',\n summary: 'Configured via MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD environment variables.',\n command: 'docker exec -it brewnet-mysql mysql -u brewnet -p brewnet_db',\n },\n tips: [\n 'Internal network only (brewnet-internal) — no host port exposed',\n 'Root password required at first startup',\n 'Init SQL scripts run once from docker-entrypoint-initdb.d/',\n ],\n },\n Redis: {\n description: 'In-memory key-value store for caching and message brokering',\n license: 'BSD-3',\n docs: 'https://redis.io/',\n features: [\n 'Session storage, cache, message queue, Pub/Sub',\n 'Single-threaded event loop — 100K+ ops/sec',\n 'RDB + AOF persistence support',\n 'Used by Nextcloud file locking and Gitea caching',\n ],\n credentials: {\n method: 'env',\n summary: 'No traditional user accounts. Optionally secured with --requirepass flag.',\n command: 'docker exec -it brewnet-redis redis-cli ping',\n },\n tips: [\n 'Set --maxmemory and --maxmemory-policy to prevent unbounded memory growth',\n 'Internal network only — no host port exposed',\n 'Redis 6+ supports ACL for multi-user access control',\n ],\n },\n pgAdmin: {\n description: 'Web-based administration tool for PostgreSQL',\n license: 'PostgreSQL (BSD-like)',\n docs: 'https://www.pgadmin.org/',\n features: [\n 'SQL editor with query execution and plan visualization',\n 'Table, index, view, and function GUI management',\n 'Backup and restore (pg_dump, pg_restore)',\n 'Multi-server management via server groups',\n ],\n credentials: {\n method: 'env',\n summary: 'Uses admin credentials set in Pre-Step. Email format: {username}@brewnet.dev. Register the DB server after first login.',\n },\n tips: [\n 'Connect to PostgreSQL using hostname \"postgresql\" (Docker container name), port 5432',\n 'Set PGADMIN_CONFIG_SERVER_MODE=False to skip login in dev mode',\n 'Mount servers.json to auto-register DB servers on startup',\n ],\n },\n Jellyfin: {\n description: 'Open-source media server (Plex/Emby free alternative)',\n license: 'GPL-2.0',\n docs: 'https://jellyfin.org/',\n features: [\n 'Movies, TV, music, photos, and live TV/DVR',\n 'Hardware transcoding (Intel QSV, NVIDIA NVENC, VAAPI)',\n 'Clients for web, Android, iOS, Roku, Fire TV, Kodi',\n 'DLNA support',\n ],\n credentials: {\n method: 'wizard',\n summary: 'First visit shows Setup Wizard. Create admin account in step 2 (User).',\n },\n tips: [\n 'Mount media folders as read-only (:ro) for safety',\n 'Add --device=/dev/dri:/dev/dri for Intel GPU hardware transcoding',\n 'DLNA requires --net=host (does not work in Docker bridge mode)',\n ],\n },\n 'SSH Server': {\n description: 'Industry-standard remote access via OpenSSH in Docker',\n license: 'BSD',\n docs: 'https://www.openssh.com/',\n features: [\n 'Key-based authentication (more secure than passwords)',\n 'Built-in SFTP — no separate FTP server needed',\n 'Port forwarding and tunneling support',\n 'Remote management entry point for Brewnet containers',\n ],\n credentials: {\n method: 'env',\n summary: 'Uses admin username set in Pre-Step. Password auth enabled (PASSWORD_ACCESS=true); switch to key-only after setup.',\n command: 'ssh -p 2222 USER@localhost',\n },\n tips: [\n 'Switch to key-only auth after initial setup: set PASSWORD_ACCESS=false',\n 'Port 2222 avoids conflict with host SSH (port 22)',\n 'SFTP runs as SSH subsystem — no separate container needed',\n ],\n },\n FileBrowser: {\n description: 'Lightweight web-based file manager written in Go',\n license: 'Apache-2.0',\n docs: 'https://filebrowser.org/',\n features: [\n 'Upload, download, edit, and delete files via browser',\n 'Multi-user support with per-user directory scoping',\n 'Built-in code editor for text files',\n 'Share link generation and shell command execution',\n ],\n credentials: {\n method: 'none',\n summary: 'Default user: admin. Random password printed to container logs on first start.',\n command: 'docker logs brewnet-filebrowser | grep \"password\"',\n },\n tips: [\n 'Initial password is shown only once in logs — change it immediately',\n 'Set per-user Scope to restrict directory access',\n 'All settings and user data stored in filebrowser.db file',\n ],\n },\n 'MinIO Console': {\n description: 'S3-compatible object storage with a web console',\n license: 'AGPL-3.0',\n docs: 'https://min.io/',\n features: [\n 'Amazon S3-compatible API',\n 'Web console for bucket and object management',\n 'Erasure coding and bitrot protection',\n 'Multi-user IAM with policies',\n ],\n credentials: {\n method: 'env',\n summary: 'Uses admin credentials set in Pre-Step of brewnet init wizard.',\n },\n tips: [\n 'Console on port 9001, API on port 9000',\n 'Create IAM users with limited policies for application access',\n 'Use mc (MinIO Client) CLI for scripted bucket management',\n ],\n },\n Cloudflared: {\n description: 'Cloudflare Tunnel daemon — exposes local services to the internet securely',\n license: 'Apache-2.0',\n docs: 'https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/',\n features: [\n 'No port forwarding or public IP required',\n 'Automatic SSL/TLS via Cloudflare',\n 'Quick Tunnel (*.trycloudflare.com) or Named Tunnel with custom domain',\n 'DDoS protection included',\n ],\n credentials: {\n method: 'none',\n summary: 'No login. Quick Tunnel needs no account. Named Tunnel uses TUNNEL_TOKEN from Cloudflare API.',\n },\n tips: [\n 'Quick Tunnel URL changes on every restart — use Named Tunnel for permanent access',\n 'Check tunnel status: brewnet domain tunnel status',\n 'Audit logs saved to ~/.brewnet/logs/tunnel.log',\n ],\n },\n Nginx: {\n description: 'High-performance HTTP and reverse proxy server',\n license: 'BSD-2',\n docs: 'https://nginx.org/',\n features: [\n 'Event-driven architecture — handles 10K+ concurrent connections',\n 'Static file serving and reverse proxy',\n 'Load balancing with multiple algorithms',\n 'SSL/TLS termination',\n ],\n credentials: {\n method: 'none',\n summary: 'No built-in authentication. Use auth_basic module or upstream auth for protection.',\n },\n tips: [\n 'Default config serves welcome page on port 80',\n 'Use location blocks for path-based routing to upstream services',\n 'Reload config without downtime: nginx -s reload',\n ],\n },\n Caddy: {\n description: 'Modern web server with automatic HTTPS',\n license: 'Apache-2.0',\n docs: 'https://caddyserver.com/',\n features: [\n 'Automatic HTTPS with Let\\'s Encrypt (zero config)',\n 'HTTP/2 and HTTP/3 support out of the box',\n 'Simple Caddyfile configuration',\n 'Reverse proxy with health checks',\n ],\n credentials: {\n method: 'none',\n summary: 'No built-in authentication. Use basicauth directive in Caddyfile for protection.',\n },\n tips: [\n 'Caddyfile syntax is simpler than Nginx — great for small setups',\n 'Automatic certificate management requires ports 80 and 443',\n 'Use caddy reload for config changes without downtime',\n ],\n },\n Valkey: {\n description: 'Open-source, high-performance Redis-compatible in-memory data store (Linux Foundation fork)',\n license: 'BSD-3',\n docs: 'https://valkey.io/',\n features: [\n 'Drop-in Redis replacement — fully API compatible',\n 'Session storage, cache, message queue, Pub/Sub',\n 'RDB + AOF persistence support',\n 'Active community-driven development post Redis license change',\n ],\n credentials: {\n method: 'env',\n summary: 'No traditional user accounts. Optionally secured with --requirepass flag.',\n command: 'docker exec -it brewnet-valkey valkey-cli ping',\n },\n tips: [\n 'Set --maxmemory and --maxmemory-policy to prevent unbounded memory growth',\n 'Internal network only — no host port exposed',\n 'Use OBJECT ENCODING to inspect memory layout of individual keys',\n ],\n },\n KeyDB: {\n description: 'Multithreaded Redis-compatible in-memory database with higher throughput',\n license: 'BSD-3',\n docs: 'https://docs.keydb.dev/',\n features: [\n 'Multi-threaded architecture — higher throughput than Redis on multi-core CPUs',\n 'Active-Active replication for multi-master setups',\n 'FLASH storage support for large datasets exceeding RAM',\n 'Drop-in Redis replacement — fully API compatible',\n ],\n credentials: {\n method: 'env',\n summary: 'No traditional user accounts. Optionally secured with requirepass config.',\n command: 'docker exec -it brewnet-keydb keydb-cli ping',\n },\n tips: [\n 'Set server-threads to number of CPU cores for best performance',\n 'Internal network only — no host port exposed',\n 'Use keydb-cli --stat to monitor live throughput',\n ],\n },\n};\n\n/**\n * Name alias map: SERVICE_REGISTRY display names → SERVICE_DETAIL_MAP keys.\n * Only entries that differ need to be listed here.\n */\nconst NAME_ALIASES: Record<string, string> = {\n 'OpenSSH Server': 'SSH Server',\n 'Cloudflare Tunnel': 'Cloudflared',\n 'MinIO': 'MinIO Console',\n};\n\n// ---------------------------------------------------------------------------\n// // Docker helpers\n// ---------------------------------------------------------------------------\n\nconst docker = new Dockerode();\n\nconst REQUIRED_SERVICES = new Set(['traefik', 'nginx', 'caddy', 'gitea']);\n\nconst INTERNAL_SERVICES = new Set(['brewnet-welcome', 'brewnet-landing', 'cloudflared']);\n\n// Non-HTTP services that should never show a clickable local URL.\n// All other services with a public TCP port get http://localhost:<port>.\nconst NO_HTTP_SERVICES = new Set([\n 'postgresql', 'mysql', 'mariadb',\n 'openssh-server',\n]);\n\n// Services that must be accessed through Traefik path-prefix routing.\n// Their OVERWRITEWEBROOT / SCRIPT_NAME settings make direct-port access broken.\nconst TRAEFIK_PATH_SERVICES: Record<string, string> = {\n traefik: 'http://localhost/dashboard/',\n gitea: 'http://localhost/git',\n nextcloud: 'http://localhost/cloud',\n pgadmin: 'http://localhost:5050/pgadmin',\n};\n\n// Known SSH ports that should never be used as the primary HTTP port.\nconst KNOWN_SSH_PORTS = new Set([22, 2222, 3022]);\n\nfunction getPrimaryPort(container: Dockerode.ContainerInfo): number | null {\n const tcp = (container.Ports ?? [])\n .filter((p) => p.Type === 'tcp' && p.PublicPort && !KNOWN_SSH_PORTS.has(p.PublicPort))\n .sort((a, b) => a.PublicPort! - b.PublicPort!);\n return tcp[0]?.PublicPort ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Request router\n// ---------------------------------------------------------------------------\n\nfunction json(res: ServerResponse, status: number, data: unknown): void {\n const payload = JSON.stringify(data);\n res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) });\n res.end(payload);\n}\n\nasync function readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve) => {\n let body = '';\n req.on('data', (c) => (body += c));\n req.on('end', () => resolve(body));\n });\n}\n\nasync function handleGetServices(\n _req: IncomingMessage,\n res: ServerResponse,\n _parts: string[],\n _body: string,\n _projectPath: string,\n urlMap: Record<string, string> = TRAEFIK_PATH_SERVICES,\n quickTunnelUrl = '',\n allowedDirs?: Set<string>,\n): Promise<void> {\n try {\n const allContainers = await docker.listContainers({ all: true });\n const services: ServiceStatus[] = [];\n\n for (const c of allContainers) {\n const composeService = c.Labels?.['com.docker.compose.service'];\n if (!composeService) continue;\n if (INTERNAL_SERVICES.has(composeService)) continue;\n\n const workingDir = c.Labels?.['com.docker.compose.project.working_dir'] ?? '';\n\n // Skip containers from unselected boilerplate stacks.\n // A container whose working_dir is under projectPath but NOT in allowedDirs\n // is an unselected boilerplate stack that shouldn't appear in the dashboard.\n if (allowedDirs && allowedDirs.size > 0) {\n if (workingDir && workingDir.startsWith(_projectPath) && !allowedDirs.has(workingDir)) {\n continue;\n }\n }\n\n const def = getServiceDefinition(composeService);\n const s = c.State as string;\n const status = s === 'running' ? 'running' : s === 'exited' ? 'stopped' : ('error' as const);\n const port = getPrimaryPort(c) ?? def?.ports?.[0] ?? null;\n\n // Detect Traefik PathPrefix and strip-prefix from container labels.\n // A container may have multiple PathPrefix routers (e.g. FileBrowser has\n // /files as main route and /static as an extra asset route). We need the\n // primary router whose name matches `quicktunnel-{serviceId}` exactly,\n // not auxiliary routers like `quicktunnel-{serviceId}-static`.\n const labels = c.Labels ?? {};\n const primaryRouterKey = `traefik.http.routers.quicktunnel-${composeService}.rule`;\n const routerRule: [string, string] | undefined =\n labels[primaryRouterKey] && String(labels[primaryRouterKey]).includes('PathPrefix')\n ? [primaryRouterKey, labels[primaryRouterKey]]\n : Object.entries(labels).find(\n ([k, v]) => k.includes('traefik.http.routers.') && k.endsWith('.rule') && String(v).includes('PathPrefix'),\n );\n let traefikPath = '';\n if (routerRule) {\n const pathMatch = String(routerRule[1]).match(/PathPrefix\\(`([^`]+)`\\)/);\n if (pathMatch) traefikPath = pathMatch[1]!;\n }\n // basePath stacks (e.g. Next.js): PathPrefix exists but no strip-prefix middleware.\n // The app serves content at the sub-path even on direct port access.\n const hasStripPrefix = Object.keys(labels).some(\n (k) => k.includes('.stripprefix.'),\n );\n const localBasePath = (traefikPath && !hasStripPrefix) ? traefikPath : '';\n\n // Compute external URL from Traefik PathPrefix labels on the container\n let externalUrl: string | null = null;\n const qtUrl = quickTunnelUrl;\n if (qtUrl && traefikPath) {\n let extPath = traefikPath;\n // Unified API-only stacks (e.g. nextjs-app): append /api/hello\n // so the external URL points to the API endpoint, not the empty root\n const stackLabel = labels['com.brewnet.stack'] ?? '';\n if (stackLabel === 'nodejs-nextjs' || (composeService === 'backend' && extPath.includes('nextjs-app'))) {\n extPath += '/api/hello';\n }\n externalUrl = qtUrl.replace(/\\/$/, '') + extPath;\n }\n if (!externalUrl && qtUrl) {\n // Fallback for known homeserver services (EXT_PATHS)\n const EXT_PATH_MAP: Record<string, string> = {\n traefik: '', gitea: '/git', nextcloud: '/cloud', pgadmin: '/pgadmin',\n jellyfin: '/jellyfin', filebrowser: '/files', minio: '/minio',\n };\n if (EXT_PATH_MAP[composeService] !== undefined) {\n externalUrl = qtUrl.replace(/\\/$/, '') + EXT_PATH_MAP[composeService];\n }\n }\n\n // Extract stackId from working_dir relative to projectPath (e.g. \".../go-gin\" → \"go-gin\")\n const stackId = (workingDir && workingDir.startsWith(_projectPath))\n ? workingDir.slice(_projectPath.length).replace(/^[/\\\\]/, '') || undefined\n : undefined;\n\n // For generic boilerplate service names (frontend/backend), prefix with the\n // compose project name so cards read \"nodejs-express-front\" instead of just \"frontend\".\n const GENERIC_BOILERPLATE_SERVICES = new Set(['frontend', 'backend']);\n const composeProject = c.Labels?.['com.docker.compose.project'] ?? '';\n const isGeneric = GENERIC_BOILERPLATE_SERVICES.has(composeService) && !!composeProject;\n const serviceId = isGeneric ? `${composeProject}-${composeService}` : composeService;\n const serviceName = isGeneric\n ? `${composeProject}-${composeService === 'frontend' ? 'front' : 'back'}`\n : (def?.name ?? composeService);\n\n services.push({\n id: serviceId,\n name: serviceName,\n type: def ? inferType(composeService) : 'unknown',\n status,\n cpu: '—',\n memory: '—',\n uptime: c.Status?.startsWith('Up') ? c.Status.replace(/^Up /, '') : '—',\n port: port ?? null,\n // Show a local URL for any service with a public HTTP port.\n // Database/queue services (non-HTTP) are excluded via NO_HTTP_SERVICES.\n // urlMap overrides apply first (e.g. Traefik-path services like gitea → /git).\n // localBasePath: Next.js basePath stacks serve at /apps/{name} even locally.\n url: port && !NO_HTTP_SERVICES.has(composeService)\n ? urlMap[composeService] ?? `http://localhost:${port}${localBasePath}`\n : null,\n externalUrl,\n removable: !REQUIRED_SERVICES.has(composeService),\n stackId,\n });\n }\n\n const running = services.filter((s) => s.status === 'running').length;\n json(res, 200, {\n services,\n summary: { total: services.length, running, stopped: services.length - running },\n });\n } catch (err) {\n json(res, 500, { success: false, error: String(err), code: 'BN001' });\n }\n}\n\nfunction inferType(id: string): string {\n if (['traefik', 'nginx', 'caddy'].includes(id)) return 'web';\n if (['postgresql', 'mysql'].includes(id)) return 'db';\n if (['nextcloud', 'minio', 'filebrowser'].includes(id)) return 'file';\n if (['jellyfin'].includes(id)) return 'media';\n if (['gitea'].includes(id)) return 'git';\n if (['openssh-server'].includes(id)) return 'ssh';\n return 'app';\n}\n\nasync function handleServiceAction(\n _req: IncomingMessage,\n res: ServerResponse,\n parts: string[],\n _body: string,\n _projectPath: string,\n): Promise<void> {\n const serviceId = parts[3]; // /api/services/containers/:id/start|stop → parts[3]=id\n const action = parts[4] as 'start' | 'stop';\n\n if (!serviceId || !['start', 'stop'].includes(action)) {\n json(res, 400, { success: false, error: 'Invalid request' });\n return;\n }\n\n try {\n const containers = await docker.listContainers({ all: true });\n const match = containers.find(\n (c) => c.Labels?.['com.docker.compose.service'] === serviceId,\n );\n\n if (!match) {\n json(res, 404, { success: false, error: 'Service not found', code: 'BN008' });\n return;\n }\n\n if (action === 'start' && match.State === 'running') {\n json(res, 400, { success: false, error: 'Service is already running', code: 'ALREADY_RUNNING' });\n return;\n }\n if (action === 'stop' && match.State !== 'running') {\n json(res, 400, { success: false, error: 'Service is not running', code: 'NOT_RUNNING' });\n return;\n }\n\n const container = docker.getContainer(match.Id);\n if (action === 'start') {\n await container.start();\n } else {\n await container.stop();\n }\n\n const newStatus = action === 'start' ? 'running' : 'stopped';\n json(res, 200, { success: true, id: serviceId, status: newStatus });\n } catch (err) {\n json(res, 500, { success: false, error: String(err), code: 'BN001' });\n }\n}\n\nasync function handleInstallService(\n _req: IncomingMessage,\n res: ServerResponse,\n _parts: string[],\n body: string,\n projectPath: string,\n): Promise<void> {\n try {\n const { id } = JSON.parse(body) as { id: string };\n if (!id) { json(res, 400, { success: false, error: 'Missing service id' }); return; }\n\n const result = await addService(id, projectPath);\n if (result.success) {\n json(res, 202, { success: true, id, status: 'installed', message: `Service ${id} added` });\n } else {\n const code = result.error?.includes('already') ? 'ALREADY_EXISTS' : 'BN006';\n json(res, result.error?.includes('already') ? 409 : 500, { success: false, error: result.error, code });\n }\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleRemoveService(\n req: IncomingMessage,\n res: ServerResponse,\n parts: string[],\n _body: string,\n projectPath: string,\n): Promise<void> {\n const serviceId = parts[3]; // DELETE /api/services/containers/:id → parts[3]=id\n if (!serviceId) { json(res, 400, { success: false, error: 'Missing service id' }); return; }\n\n if (REQUIRED_SERVICES.has(serviceId)) {\n json(res, 400, { success: false, error: `Cannot remove required service: ${serviceId}`, code: 'REQUIRED_SERVICE' });\n return;\n }\n\n const url = new URL(req.url ?? '/', `http://localhost`);\n const purge = url.searchParams.get('purge') === 'true';\n\n try {\n const result = await removeService(serviceId, projectPath, { purge });\n if (result.success) {\n json(res, 200, { success: true, id: serviceId, dataPreserved: !purge });\n } else {\n json(res, result.error?.includes('not found') ? 404 : 500, { success: false, error: result.error, code: 'BN008' });\n }\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleGetCatalog(\n _req: IncomingMessage,\n res: ServerResponse,\n _parts: string[],\n _body: string,\n _projectPath: string,\n): Promise<void> {\n try {\n const installed = new Set<string>();\n const containers = await docker.listContainers({ all: true });\n for (const c of containers) {\n const id = c.Labels?.['com.docker.compose.service'];\n if (id) installed.add(id);\n }\n\n const catalog = [...SERVICE_REGISTRY.values()]\n .filter((def) => !REQUIRED_SERVICES.has(def.id))\n .map((def) => ({\n id: def.id,\n name: def.name,\n description: '',\n category: inferType(def.id),\n image: def.image,\n ramEstimateMB: def.ramMB,\n installed: installed.has(def.id),\n }));\n\n json(res, 200, { catalog });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleBackup(\n req: IncomingMessage,\n res: ServerResponse,\n _parts: string[],\n _body: string,\n projectPath: string,\n): Promise<void> {\n const backupsDir = join(homedir(), '.brewnet', 'backups');\n\n if (req.method === 'GET') {\n try {\n const backups = listBackups(backupsDir);\n json(res, 200, { backups });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n return;\n }\n\n // POST - create backup\n try {\n const record = createBackup(projectPath, backupsDir);\n json(res, 202, { success: true, backupId: record.id, status: 'completed' });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Per-app domain operation log buffer (feeds into SSE /logs stream)\n// ---------------------------------------------------------------------------\n\n/** In-memory ring buffer: appName → last 200 domain op log lines */\nconst domainOpLogs = new Map<string, Array<{ line: string; ts: number }>>();\n\n/** Live listeners: SSE clients currently tailing logs for an app */\nconst domainOpListeners = new Map<string, Set<(line: string) => void>>();\n\nfunction writeDomainLog(appName: string, line: string): void {\n const tagged = `[domain-connect] ${line}`;\n logger.info('domain', `[${appName}] ${line}`);\n if (!domainOpLogs.has(appName)) domainOpLogs.set(appName, []);\n const buf = domainOpLogs.get(appName)!;\n buf.push({ line: tagged, ts: Date.now() });\n if (buf.length > 200) buf.shift();\n domainOpListeners.get(appName)?.forEach((fn) => fn(tagged));\n}\n\n// ---------------------------------------------------------------------------\n// Server factory\n// ---------------------------------------------------------------------------\n\nexport function createAdminServer(options: AdminServerOptions = {}): {\n server: Server;\n start: () => Promise<number>;\n stop: () => Promise<void>;\n} {\n const port = options.port ?? 8088;\n\n // Resolve project path and wizard state.\n // Always load wizard state from the last project — options.projectPath only\n // overrides the filesystem path, not whether state is loaded.\n let projectPath = options.projectPath ?? process.cwd();\n let wizardState: WizardState | null = null;\n const last = getLastProject();\n if (last) {\n const state = loadState(last);\n if (state) {\n wizardState = state;\n // Only fall back to state.projectPath when caller didn't supply one\n if (!options.projectPath && state.projectPath) projectPath = state.projectPath;\n }\n }\n // Expand leading ~ — Node.js fs APIs do not interpret tilde as home directory\n if (projectPath.startsWith('~/') || projectPath === '~') {\n projectPath = join(homedir(), projectPath.slice(1));\n }\n\n // Build dashboard config from wizard state (credentials resolved lazily if needed)\n const username = wizardState?.admin?.username ?? '';\n const password = wizardState?.admin?.password ?? '';\n\n // Mask helpers\n const maskUser = (u: string) => (u.length > 2 ? u.slice(0, -2) + '**' : '**');\n const maskPass = (p: string) => (p.length > 1 ? p[0] + '*'.repeat(p.length - 1) : '********');\n\n // Build set of allowed compose working dirs for service filtering.\n // Only containers from these directories are shown in the Services table.\n // This excludes unselected boilerplate stacks (e.g. test deployments of all 16 stacks).\n const allowedWorkingDirs = new Set<string>();\n allowedWorkingDirs.add(projectPath); // homeserver services (traefik, gitea, etc.)\n try {\n // Selected boilerplate stacks from wizard\n const bpMetaPath2 = join(projectPath, '.brewnet-boilerplate.json');\n if (existsSync(bpMetaPath2)) {\n const raw2 = JSON.parse(readFileSync(bpMetaPath2, 'utf-8')) as BoilerplateMeta | BoilerplateMeta[];\n const stacks2: BoilerplateMeta[] = Array.isArray(raw2) ? raw2 : (raw2.stackId ? [raw2] : []);\n for (const s of stacks2) {\n if (s.appDir) allowedWorkingDirs.add(s.appDir);\n }\n }\n // Apps registered via `brewnet deploy` (app-manager) — ~/.brewnet/apps.json\n const appsJsonPath = join(homedir(), '.brewnet', 'apps.json');\n if (existsSync(appsJsonPath)) {\n const apps = JSON.parse(readFileSync(appsJsonPath, 'utf-8')) as Array<{ appDir?: string }>;\n for (const app of apps) {\n if (app.appDir) allowedWorkingDirs.add(app.appDir);\n }\n }\n } catch { /* non-fatal */ }\n\n const dashConfig: DashboardConfig = {\n adminUsername: username ? maskUser(username) : '**',\n passwordHint: password ? maskPass(password) : '********',\n domainProvider: wizardState?.domain?.provider ?? 'local',\n quickTunnelUrl: wizardState?.domain?.cloudflare?.quickTunnelUrl ?? '',\n zoneName: wizardState?.domain?.cloudflare?.zoneName ?? '',\n tunnelId: wizardState?.domain?.cloudflare?.tunnelId ?? '',\n };\n\n // Compute runtime URL map — extends static TRAEFIK_PATH_SERVICES.\n // Jellyfin local URL always uses direct port 8096 (bypasses Traefik).\n // Reason: Traefik's catch-all landing page router returns HTTP 200 for any\n // unmapped path (including /System/Info/Public), which confuses Jellyfin SPA's\n // server auto-detection. Direct port access lets Jellyfin redirect unmapped\n // paths to ../../jellyfin/web/, giving the SPA a correct base URL hint.\n const runtimeUrlMap: Record<string, string> = {\n ...TRAEFIK_PATH_SERVICES,\n jellyfin: 'http://localhost:8096/jellyfin/web/',\n };\n\n let quickTunnelDetected = !!dashConfig.quickTunnelUrl;\n\n /**\n * Detect Quick Tunnel URL from running cloudflared container logs.\n * Called once on first request if no tunnel URL is in the config.\n */\n async function detectQuickTunnelUrl(): Promise<void> {\n if (quickTunnelDetected) return;\n quickTunnelDetected = true; // prevent repeated attempts\n try {\n const containers = await docker.listContainers({ all: true });\n const cf = containers.find(\n (c) => c.Labels?.['com.docker.compose.service'] === 'cloudflared',\n );\n if (!cf || cf.State !== 'running') return;\n const container = docker.getContainer(cf.Id);\n const logBuf = await container.logs({ stdout: true, stderr: true, tail: 50 });\n const logStr = logBuf.toString('utf-8');\n const match = logStr.match(/https?:\\/\\/([\\w]+-[\\w][\\w-]*\\.trycloudflare\\.com)/i);\n if (match) {\n dashConfig.quickTunnelUrl = `https://${match[1]}`;\n dashConfig.domainProvider = 'quick-tunnel';\n }\n } catch {\n // Non-critical — just serve without external URLs\n }\n }\n\n /**\n * Lazy credential detection from running Nextcloud container env vars.\n * Called once on first dashboard request when wizard state is unavailable.\n */\n let credentialsDetected = !!(username && password);\n async function detectCredentials(): Promise<void> {\n if (credentialsDetected) return;\n credentialsDetected = true; // prevent repeated attempts\n try {\n const containers = await docker.listContainers({ all: true });\n const nc = containers.find(\n (c) => c.Labels?.['com.docker.compose.service'] === 'nextcloud',\n );\n if (!nc) return;\n const info = await docker.getContainer(nc.Id).inspect();\n const envArr: string[] = info.Config?.Env ?? [];\n let u = '';\n let p = '';\n for (const entry of envArr) {\n if (!u && entry.startsWith('NEXTCLOUD_ADMIN_USER=')) {\n u = entry.split('=').slice(1).join('=');\n }\n if (!p && entry.startsWith('NEXTCLOUD_ADMIN_PASSWORD=')) {\n p = entry.split('=').slice(1).join('=');\n }\n }\n if (u || p) {\n dashConfig.adminUsername = maskUser(u || 'admin');\n dashConfig.passwordHint = maskPass(p);\n }\n } catch {\n // Non-critical — fall through to defaults\n }\n }\n\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = req.url ?? '/';\n const parts = url.split('?')[0].split('/').filter(Boolean);\n const body = await readBody(req);\n\n // CORS for dev convenience\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Admin-Password');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Serve Brewnet SVG icon\n if (req.method === 'GET' && url === '/icon.svg') {\n res.writeHead(200, { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=86400' });\n res.end(ICON_SVG);\n return;\n }\n\n // Serve favicon.ico (binary from disk; fallback: SVG with image/x-icon)\n if (req.method === 'GET' && url === '/favicon.ico') {\n if (FAVICON_ICO) {\n res.writeHead(200, { 'Content-Type': 'image/x-icon', 'Cache-Control': 'public, max-age=86400' });\n res.end(FAVICON_ICO);\n } else {\n res.writeHead(200, { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=86400' });\n res.end(ICON_SVG);\n }\n return;\n }\n\n // Serve React SPA static assets (/assets/*)\n if (req.method === 'GET' && url.startsWith('/assets/')) {\n const pathname = url.split('?')[0];\n const safePath = resolve(ADMIN_UI_DIST, '.' + pathname);\n if (!safePath.startsWith(ADMIN_UI_DIST)) {\n res.writeHead(403); res.end('Forbidden'); return;\n }\n if (existsSync(safePath) && statSync(safePath).isFile()) {\n serveStaticFile(safePath, res);\n return;\n }\n res.writeHead(404); res.end('Not Found');\n return;\n }\n\n // SPA fallback — serve index.html for all non-API GET requests\n if (req.method === 'GET' && !url.startsWith('/api/')) {\n const pathname = url.split('?')[0];\n // Exact static file (e.g. /vite.svg, /favicon.ico from dist/)\n const exactPath = resolve(ADMIN_UI_DIST, '.' + (pathname === '/' ? '/index.html' : pathname));\n if (exactPath.startsWith(ADMIN_UI_DIST) && existsSync(exactPath) && statSync(exactPath).isFile()) {\n serveStaticFile(exactPath, res);\n return;\n }\n // SPA fallback: serve index.html\n const indexPath = join(ADMIN_UI_DIST, 'index.html');\n if (existsSync(indexPath)) {\n serveStaticFile(indexPath, res);\n return;\n }\n // admin-ui not built yet\n res.writeHead(503, { 'Content-Type': 'text/plain' });\n res.end('Admin UI not built. Run: pnpm --filter @brewnet/admin-ui build');\n return;\n }\n\n\n // --- API routing ---\n if (parts[0] === 'api') {\n try {\n if (parts[1] === 'health' && req.method === 'GET') {\n // When a password is configured, validate it so PasswordGate can\n // use this endpoint as the real auth check.\n if (wizardState?.admin?.password) {\n const provided = req.headers['x-admin-password'] as string | undefined;\n if (!provided || provided !== wizardState.admin.password) {\n json(res, 401, { error: 'Unauthorized', message: 'Admin password required' });\n return;\n }\n }\n json(res, 200, { status: 'ok', version: '1.0.1' });\n return;\n }\n\n // GET /api/services/catalog — SERVICE_DETAIL_MAP + NAME_ALIASES for React SPA\n if (parts[1] === 'services' && parts[2] === 'catalog' && req.method === 'GET') {\n json(res, 200, { catalog: SERVICE_DETAIL_MAP, aliases: NAME_ALIASES });\n return;\n }\n\n // GET /api/config — dashboard bootstrap data for React SPA\n if (parts[1] === 'config' && req.method === 'GET') {\n await detectQuickTunnelUrl();\n await detectCredentials();\n json(res, 200, {\n adminUsername: dashConfig.adminUsername,\n passwordHint: dashConfig.passwordHint,\n domainProvider: dashConfig.domainProvider,\n quickTunnelUrl: dashConfig.quickTunnelUrl,\n zoneName: dashConfig.zoneName,\n tunnelId: dashConfig.tunnelId,\n });\n return;\n }\n\n if (parts[1] === 'services') {\n if (req.method === 'GET' && parts.length === 2) {\n await handleGetServices(req, res, parts, body, projectPath, runtimeUrlMap, dashConfig.quickTunnelUrl, allowedWorkingDirs);\n return;\n }\n if (req.method === 'POST' && parts[2] === 'install') {\n await handleInstallService(req, res, parts, body, projectPath);\n return;\n }\n // POST /api/services/containers/:id/start|stop → parts[3]=id, parts[4]=action\n if (req.method === 'POST' && parts[3] && ['start', 'stop'].includes(parts[4] ?? '')) {\n await handleServiceAction(req, res, parts, body, projectPath);\n return;\n }\n // DELETE /api/services/containers/:id → parts[3]=id\n if (req.method === 'DELETE' && parts[3]) {\n await handleRemoveService(req, res, parts, body, projectPath);\n return;\n }\n }\n\n if (parts[1] === 'catalog' && req.method === 'GET') {\n await handleGetCatalog(req, res, parts, body, projectPath);\n return;\n }\n\n if (parts[1] === 'backup') {\n await handleBackup(req, res, parts, body, projectPath);\n return;\n }\n\n // ── Logs API (T021-T022) ────────────────────────────────────\n if (parts[1] === 'logs') {\n if (req.method === 'GET' && parts[2] === 'stats') {\n const stats = await getLogStats(projectPath);\n json(res, 200, stats);\n return;\n }\n if (req.method === 'GET') {\n const qUrl = new URL(url, 'http://localhost');\n const sources = qUrl.searchParams.get('source');\n const levels = qUrl.searchParams.get('level');\n const services = qUrl.searchParams.get('service');\n const since = qUrl.searchParams.get('since') ?? undefined;\n const until = qUrl.searchParams.get('until') ?? undefined;\n const search = qUrl.searchParams.get('search') ?? undefined;\n const limit = parseInt(qUrl.searchParams.get('limit') ?? '100', 10);\n const offset = parseInt(qUrl.searchParams.get('offset') ?? '0', 10);\n\n const result = await queryLogs(\n {\n sources: sources ? [sources as LogSource] : undefined,\n levels: levels ? [levels as UnifiedLogLevel] : undefined,\n services: services ? [services] : undefined,\n since,\n until,\n search,\n limit: isNaN(limit) ? 100 : limit,\n offset: isNaN(offset) ? 0 : offset,\n },\n projectPath,\n );\n json(res, 200, result);\n return;\n }\n }\n\n if (parts[1] === 'apps') {\n if (req.method === 'GET' && parts.length === 2) {\n const apps = await listApps();\n // Enrich with lastDeployedAt + localUrl (with basePath for Next.js)\n const history = getDeployHistory();\n const historyByApp = new Map<string, typeof history[0]>();\n for (const h of history) { if (h.status === 'success') historyByApp.set(h.appName, h); }\n // Load boilerplate meta once for frontend URL lookup\n const bpMetaPath = join(projectPath, '.brewnet-boilerplate.json');\n let bpMetaMap = new Map<string, BoilerplateMeta>();\n if (existsSync(bpMetaPath)) {\n try {\n const raw = JSON.parse(readFileSync(bpMetaPath, 'utf-8')) as BoilerplateMeta | BoilerplateMeta[];\n const metas: BoilerplateMeta[] = Array.isArray(raw) ? raw : [raw];\n for (const m of metas) bpMetaMap.set(m.stackId, m);\n } catch { /* ignore parse errors */ }\n }\n const enrichedApps = apps.map((a) => {\n const lastDeploy = historyByApp.get(a.name) ?? null;\n const qt = dashConfig.quickTunnelUrl;\n // Non-unified: bpMeta says so, or fall back to stack catalog (for create-app apps)\n const bpMeta = a.mode === 'boilerplate' && a.stackId ? bpMetaMap.get(a.stackId) : undefined;\n const isNonUnified = bpMeta\n ? bpMeta.isUnified === false\n : !!(a.stackId && getStackById(a.stackId)?.isUnified === false);\n let localUrl: string | null;\n let externalUrl: string | null;\n let backendLocalUrl: string | null = null;\n let backendExternalUrl: string | null = null;\n if (isNonUnified) {\n // Read actual FRONTEND_PORT from .env — meta may have stale default (3000)\n let frontendPort = 3000;\n const feEnvPath = join(a.appDir, '.env');\n if (existsSync(feEnvPath)) {\n const feEnvContent = readFileSync(feEnvPath, 'utf-8');\n const fePortMatch = feEnvContent.match(/^FRONTEND_PORT=(\\d+)/m);\n if (fePortMatch) frontendPort = parseInt(fePortMatch[1], 10);\n }\n localUrl = `http://127.0.0.1:${frontendPort}`;\n externalUrl = qt ? `${qt.replace(/\\/$/, '')}/apps/${a.name}-ui` : null;\n backendLocalUrl = a.port ? `http://127.0.0.1:${a.port}` : null;\n backendExternalUrl = qt ? `${qt.replace(/\\/$/, '')}/apps/${a.name}` : null;\n } else {\n // Compute localUrl with basePath (same logic as Dashboard services)\n localUrl = a.port ? `http://localhost:${a.port}` : null;\n if (a.appDir) {\n const bp = detectBasePath(a.appDir);\n if (bp && localUrl) localUrl += bp;\n }\n externalUrl = qt ? `${qt.replace(/\\/$/, '')}/apps/${a.name}` : null;\n }\n return { ...a, lastDeployedAt: lastDeploy?.deployedAt ?? null, localUrl, externalUrl, backendLocalUrl, backendExternalUrl };\n });\n logger.info('admin-server', `[GET /api/apps] returning ${apps.length} app(s): ${JSON.stringify(apps.map((a) => a.name))}`);\n json(res, 200, { apps: enrichedApps });\n return;\n }\n if (req.method === 'GET' && parts[2] === 'boilerplates') {\n const bpPath = join(projectPath, '.brewnet-boilerplate.json');\n const metas: BoilerplateMeta[] = [];\n // Wizard-era boilerplates (from .brewnet-boilerplate.json)\n if (existsSync(bpPath)) {\n const raw = JSON.parse(readFileSync(bpPath, 'utf-8')) as BoilerplateMeta | BoilerplateMeta[];\n const wizardMetas = Array.isArray(raw) ? raw : [raw];\n metas.push(...wizardMetas);\n }\n // create-app boilerplate apps (from apps.json)\n const allApps = await listApps();\n for (const app of allApps) {\n if (app.mode !== 'boilerplate' || !app.stackId) continue;\n // Skip if already covered by wizard .brewnet-boilerplate.json (same appDir)\n if (metas.some((m) => m.appDir && app.appDir && m.appDir === app.appDir)) continue;\n const envPath = join(app.appDir, '.env');\n let frontendPort: number | undefined;\n let dbDriver: string | undefined;\n let dbUser: string | undefined;\n let dbName: string | undefined;\n if (existsSync(envPath)) {\n const envContent = readFileSync(envPath, 'utf-8');\n const fpMatch = envContent.match(/^FRONTEND_PORT=(\\d+)/m);\n if (fpMatch) frontendPort = parseInt(fpMatch[1]!, 10);\n const ddMatch = envContent.match(/^DB_DRIVER=(.+)/m);\n if (ddMatch) dbDriver = ddMatch[1]!.trim();\n const duMatch = envContent.match(/^DB_USER=(.+)/m);\n if (duMatch) dbUser = duMatch[1]!.trim();\n const dnMatch = envContent.match(/^DB_NAME=(.+)/m);\n if (dnMatch) dbName = dnMatch[1]!.trim();\n }\n const stackEntry = getStackById(app.stackId);\n metas.push({\n stackId: app.stackId,\n appDir: app.appDir,\n backendUrl: `http://127.0.0.1:${app.port}`,\n frontendUrl: frontendPort ? `http://127.0.0.1:${frontendPort}` : undefined,\n isUnified: stackEntry?.isUnified ?? false,\n lang: app.lang,\n dbDriver,\n dbUser,\n dbName,\n status: app.status,\n });\n }\n logger.info('admin-server', `[GET /api/apps/boilerplates] returning ${metas.length} boilerplate(s) (wizard=${metas.length - allApps.filter((a) => a.mode === 'boilerplate' && a.stackId).length}, create-app=${allApps.filter((a) => a.mode === 'boilerplate' && a.stackId).length})`);\n json(res, 200, { boilerplates: metas });\n return;\n }\n // POST /api/apps/boilerplates/:stackId/stop — docker compose down\n // POST /api/apps/boilerplates/:stackId/start — docker compose up -d\n if (req.method === 'POST' && parts[2] === 'boilerplates' && parts[3] && (parts[4] === 'stop' || parts[4] === 'start')) {\n const stackId = decodeURIComponent(parts[3]);\n const action = parts[4] as 'stop' | 'start';\n const bpPath = join(projectPath, '.brewnet-boilerplate.json');\n if (!existsSync(bpPath)) { json(res, 404, { error: 'No boilerplates found' }); return; }\n const bpRaw = JSON.parse(readFileSync(bpPath, 'utf-8'));\n const bpMetas: BoilerplateMeta[] = Array.isArray(bpRaw) ? bpRaw : [bpRaw];\n const meta = bpMetas.find((m) => m.stackId === stackId);\n if (!meta) { json(res, 404, { error: `Boilerplate \"${stackId}\" not found` }); return; }\n const { execa: execaBp } = await import('execa');\n if (action === 'stop') {\n await execaBp('docker', ['compose', 'down'], { cwd: meta.appDir });\n meta.status = 'stopped';\n } else {\n await execaBp('docker', ['compose', 'up', '-d'], { cwd: meta.appDir });\n meta.status = 'running';\n }\n // Persist status update back to .brewnet-boilerplate.json\n const { writeFileSync } = await import('node:fs');\n writeFileSync(bpPath, JSON.stringify(bpMetas, null, 2), 'utf-8');\n json(res, 200, { success: true });\n return;\n }\n if (req.method === 'POST' && parts[2] === 'create') {\n const opts = JSON.parse(body) as CreateAppOptions;\n const jobId = await createApp(opts);\n json(res, 202, { jobId });\n return;\n }\n if (req.method === 'GET' && parts[2] === 'jobs' && parts[3]) {\n const job = getJobStatus(parts[3]);\n if (!job) { json(res, 404, { error: 'Job not found' }); return; }\n json(res, 200, job as unknown as Record<string, unknown>);\n return;\n }\n if (req.method === 'POST' && parts[3] === 'start') {\n await startApp(decodeURIComponent(parts[2] ?? ''));\n json(res, 200, { success: true });\n return;\n }\n if (req.method === 'POST' && parts[3] === 'stop') {\n await stopApp(decodeURIComponent(parts[2] ?? ''));\n json(res, 200, { success: true });\n return;\n }\n if (req.method === 'DELETE' && parts[2]) {\n await appRemove(parts[2]);\n json(res, 200, { success: true });\n return;\n }\n\n // GET /api/apps/:name — single app detail (enriched with lastDeployedAt + URLs)\n if (req.method === 'GET' && parts[2] && !['boilerplates', 'jobs', 'check-port'].includes(parts[2]) && parts.length === 3) {\n const apps = await listApps();\n const found = apps.find((a) => a.name === decodeURIComponent(parts[2]!));\n if (!found) { json(res, 404, { error: 'App not found' }); return; }\n const history = getDeployHistory();\n const lastDeploy = history.filter((h) => h.appName === found.name && h.status === 'success').pop() ?? null;\n const qt = dashConfig.quickTunnelUrl;\n const bpMetaSingle = found.mode === 'boilerplate' && found.stackId\n ? (() => {\n const p = join(projectPath, '.brewnet-boilerplate.json');\n if (!existsSync(p)) return undefined;\n try {\n const raw = JSON.parse(readFileSync(p, 'utf-8')) as BoilerplateMeta | BoilerplateMeta[];\n const list = Array.isArray(raw) ? raw : [raw];\n return list.find((m) => m.stackId === found.stackId);\n } catch { return undefined; }\n })()\n : undefined;\n // Non-unified: bpMeta says so, or fall back to stack catalog (for create-app apps)\n const isNonUnified = bpMetaSingle\n ? bpMetaSingle.isUnified === false\n : !!(found.stackId && getStackById(found.stackId)?.isUnified === false);\n let localUrlSingle: string | null;\n let externalUrlSingle: string | null;\n let backendLocalUrlSingle: string | null = null;\n let backendExternalUrlSingle: string | null = null;\n if (isNonUnified) {\n let frontendPort = 3000;\n const feEnvPath = join(found.appDir, '.env');\n if (existsSync(feEnvPath)) {\n const m = readFileSync(feEnvPath, 'utf-8').match(/^FRONTEND_PORT=(\\d+)/m);\n if (m) frontendPort = parseInt(m[1], 10);\n }\n localUrlSingle = `http://127.0.0.1:${frontendPort}`;\n externalUrlSingle = qt ? `${qt.replace(/\\/$/, '')}/apps/${found.name}-ui` : null;\n backendLocalUrlSingle = found.port ? `http://127.0.0.1:${found.port}` : null;\n backendExternalUrlSingle = qt ? `${qt.replace(/\\/$/, '')}/apps/${found.name}` : null;\n } else {\n localUrlSingle = found.port ? `http://localhost:${found.port}` : null;\n if (found.appDir) {\n const bp = detectBasePath(found.appDir);\n if (bp && localUrlSingle) localUrlSingle += bp;\n }\n externalUrlSingle = qt ? `${qt.replace(/\\/$/, '')}/apps/${found.name}` : null;\n }\n const app = { ...found, lastDeployedAt: lastDeploy?.deployedAt ?? null, localUrl: localUrlSingle, externalUrl: externalUrlSingle, backendLocalUrl: backendLocalUrlSingle, backendExternalUrl: backendExternalUrlSingle };\n json(res, 200, { app });\n return;\n }\n\n // GET /api/apps/:name/git\n if (req.method === 'GET' && parts[3] === 'git' && parts.length === 4) {\n try {\n const git = await getAppGitInfo(decodeURIComponent(parts[2] ?? ''));\n json(res, 200, { git });\n } catch (err) {\n json(res, 502, { error: String(err) });\n }\n return;\n }\n\n // GET /api/apps/:name/branches\n if (req.method === 'GET' && parts[3] === 'branches' && parts.length === 4) {\n try {\n const branches = await getAppBranches(decodeURIComponent(parts[2] ?? ''));\n json(res, 200, { branches });\n } catch (err) {\n json(res, 200, { branches: [] });\n }\n return;\n }\n\n // GET /api/apps/:name/deploy/settings\n if (req.method === 'GET' && parts[3] === 'deploy' && parts[4] === 'settings') {\n const settings = getDeploySettings(decodeURIComponent(parts[2] ?? ''));\n json(res, 200, settings);\n return;\n }\n\n // PUT /api/apps/:name/deploy/settings\n if (req.method === 'PUT' && parts[3] === 'deploy' && parts[4] === 'settings') {\n const opts = JSON.parse(body) as Partial<DeploySettings>;\n updateDeploySettings(decodeURIComponent(parts[2] ?? ''), opts);\n json(res, 200, { success: true });\n return;\n }\n\n // POST /api/apps/:name/deploy — manual deploy trigger\n if (req.method === 'POST' && parts[3] === 'deploy' && !parts[4]) {\n const jobId = await deployApp(decodeURIComponent(parts[2] ?? ''));\n json(res, 202, { jobId });\n return;\n }\n\n // POST /api/apps/:name/rollback — rollback to a specific commit\n if (req.method === 'POST' && parts[3] === 'rollback' && !parts[4]) {\n let parsed: { commitHash?: string } = {};\n try { parsed = JSON.parse(body); } catch { json(res, 400, { error: 'Invalid JSON' }); return; }\n if (!parsed.commitHash) { json(res, 400, { error: 'commitHash is required' }); return; }\n const jobId = await rollbackApp(decodeURIComponent(parts[2] ?? ''), parsed.commitHash);\n json(res, 202, { jobId });\n return;\n }\n\n // GET /api/apps/:name/logs — SSE stream (auth: X-Admin-Password header or ?token query)\n if (req.method === 'GET' && parts[3] === 'logs') {\n const appDir = getAppDir(decodeURIComponent(parts[2] ?? ''));\n if (!appDir) { json(res, 404, { error: 'App not found' }); return; }\n // SSE: EventSource cannot send custom headers so this endpoint is\n // intentionally unauthenticated (log data is read-only, non-sensitive).\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n });\n // Flush headers immediately so EventSource fires 'open' even before\n // docker compose logs produces its first output line.\n res.flushHeaders();\n const { execa: execaLocal } = await import('execa');\n const proc = execaLocal('docker', ['compose', 'logs', '--follow', '--tail', '50'], {\n cwd: appDir,\n reject: false,\n stdout: 'pipe',\n stderr: 'pipe',\n });\n const sendLine = (line: string) => {\n if (line.trim()) res.write(`data: ${line.replace(/\\r?\\n/g, ' ')}\\n\\n`);\n };\n proc.stdout?.on('data', (chunk: Buffer) => {\n for (const line of chunk.toString().split('\\n')) sendLine(line);\n });\n proc.stderr?.on('data', (chunk: Buffer) => {\n for (const line of chunk.toString().split('\\n')) sendLine(line);\n });\n\n // Also stream domain operation logs (domain connect/disconnect events)\n const sseAppName = decodeURIComponent(parts[2] ?? '');\n const domainListener = (line: string) => sendLine(line);\n if (!domainOpListeners.has(sseAppName)) domainOpListeners.set(sseAppName, new Set());\n domainOpListeners.get(sseAppName)!.add(domainListener);\n // Flush recent domain op logs on connect\n for (const entry of domainOpLogs.get(sseAppName) ?? []) sendLine(entry.line);\n\n req.on('close', () => {\n try { proc.kill(); } catch { /* ignore */ }\n domainOpListeners.get(sseAppName)?.delete(domainListener);\n });\n return;\n }\n }\n\n // ── Gitea auto-login proxy ──────────────────────────────────\n // GET /api/gitea/autologin?redirect=<gitea-internal-path>\n // Server-side: log into Gitea with admin creds, forward session cookie,\n // redirect browser to the target Gitea page — no plaintext creds in client.\n if (parts[1] === 'gitea' && parts[2] === 'autologin' && req.method === 'GET') {\n const reqUrl = new URL(req.url ?? '/', 'http://localhost');\n const redirectPath = reqUrl.searchParams.get('redirect') ?? '/git';\n const giteaBase = 'http://localhost/git';\n const targetUrl = `http://localhost${redirectPath.startsWith('/') ? redirectPath : '/' + redirectPath}`;\n try {\n // Step 1: GET login page to obtain CSRF cookie + form token\n const loginPageRes = await fetch(`${giteaBase}/user/login`, {\n redirect: 'manual',\n headers: { 'User-Agent': 'brewnet-admin' },\n signal: AbortSignal.timeout(5000),\n });\n const rawSetCookies: string[] = [];\n loginPageRes.headers.forEach((val, key) => {\n if (key.toLowerCase() === 'set-cookie') rawSetCookies.push(val);\n });\n const csrfCookieFull = rawSetCookies.find((c) => c.startsWith('_csrf=')) ?? '';\n const csrfCookieVal = csrfCookieFull.split(';')[0] ?? '';\n const pageHtml = await loginPageRes.text();\n const csrfField = pageHtml.match(/name=\"_csrf\"\\s+value=\"([^\"]+)\"/)?.[1]\n ?? pageHtml.match(/value=\"([^\"]+)\"\\s+name=\"_csrf\"/)?.[1]\n ?? csrfCookieVal.replace('_csrf=', '');\n if (!csrfField) {\n // CSRF unavailable — redirect without login (Gitea will show login page)\n res.writeHead(302, { Location: targetUrl });\n res.end();\n return;\n }\n // Step 2: POST login form with admin credentials\n const formBody = new URLSearchParams({\n _csrf: csrfField,\n user_name: username,\n password: password,\n remember: 'on',\n });\n const loginRes = await fetch(`${giteaBase}/user/login`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'Cookie': csrfCookieVal,\n 'User-Agent': 'brewnet-admin',\n },\n body: formBody.toString(),\n redirect: 'manual',\n signal: AbortSignal.timeout(5000),\n });\n // Step 3: Extract ALL Gitea cookies from login response and forward them.\n // Gitea sets cookies with Path=/git (matching its ROOT_URL sub-path).\n // We must preserve the original Path so the browser sends them to Gitea.\n const respCookies: string[] = [];\n loginRes.headers.forEach((val, key) => {\n if (key.toLowerCase() === 'set-cookie') respCookies.push(val);\n });\n // Forward all non-empty, non-delete cookies (skip Max-Age=0 deletion entries)\n const forwardCookies = respCookies.filter((c) => !c.includes('Max-Age=0') && !c.match(/=;\\s/));\n const responseHeaders: Record<string, string | string[]> = { Location: targetUrl };\n if (forwardCookies.length > 0) {\n responseHeaders['Set-Cookie'] = forwardCookies;\n } else {\n logger.warn('admin-server', '[gitea/autologin] login POST did not return session cookies');\n }\n res.writeHead(302, responseHeaders);\n res.end();\n } catch (err) {\n logger.warn('admin-server', `[gitea/autologin] failed: ${String(err)}`);\n res.writeHead(302, { Location: targetUrl });\n res.end();\n }\n return;\n }\n\n // ── Deploy history, Git repos & Webhook ────────────────────\n if (parts[1] === 'deploy' && parts[2] === 'history' && req.method === 'GET') {\n const reqUrl = new URL(req.url ?? '/', 'http://localhost');\n const appFilter = reqUrl.searchParams.get('app') ?? undefined;\n const entries = getDeployHistory(appFilter);\n json(res, 200, { history: entries });\n return;\n }\n\n if (parts[1] === 'git' && parts[2] === 'repos' && req.method === 'GET') {\n try {\n const repos = await listGiteaRepos();\n const appsForEnrich = await listApps();\n // Enrich repos: map Gitea field names + attach connected appName\n const enriched = repos.map((repo) => {\n const r = repo as unknown as Record<string, unknown>;\n const connectedApp = appsForEnrich.find((app) =>\n app.giteaRepoUrl && (\n app.giteaRepoUrl.endsWith('/' + repo.name) ||\n app.giteaRepoUrl.includes('/' + repo.name + '.')\n ),\n );\n return {\n ...repo,\n language: (r['language'] as string | undefined) ?? '',\n stars: (r['stars_count'] as number | undefined) ?? 0,\n updatedAt: (r['updated'] as string | undefined) ?? '',\n appName: connectedApp?.name,\n };\n });\n json(res, 200, { repos: enriched });\n } catch (err) {\n json(res, 502, { success: false, error: String(err) });\n }\n return;\n }\n\n // POST /api/git/repos/:name/connect — Associate repo with an app\n if (parts[1] === 'git' && parts[2] === 'repos' && parts[3] && parts[4] === 'connect' && req.method === 'POST') {\n const repoName = decodeURIComponent(parts[3]);\n let parsed: { appName?: string } = {};\n try { parsed = JSON.parse(body); } catch { json(res, 400, { error: 'Invalid JSON' }); return; }\n const appName = parsed.appName?.trim();\n if (!appName) { json(res, 400, { error: 'appName required' }); return; }\n try {\n const appsPath = join(homedir(), '.brewnet', 'apps.json');\n let existing = existsSync(appsPath) ? JSON.parse(readFileSync(appsPath, 'utf-8')) : [];\n const repos = await listGiteaRepos();\n const repo = repos.find(r => r.name === repoName);\n if (!repo) { json(res, 404, { error: `Repo '${repoName}' not found in Gitea` }); return; }\n const repoUrl = repo.clone_url.replace(/\\.git$/, '');\n // Check not already connected to a different app\n const conflict = Array.isArray(existing) ? existing.find((a: { name: string; giteaRepoUrl?: string }) =>\n a.name !== appName && a.giteaRepoUrl && (a.giteaRepoUrl.endsWith('/' + repoName) || a.giteaRepoUrl.includes('/' + repoName + '.'))) : null;\n if (conflict) { json(res, 409, { error: `Repo already connected to app '${conflict.name}'` }); return; }\n let app = Array.isArray(existing) ? existing.find((a: { name: string }) => a.name === appName) : null;\n if (!app) {\n // Auto-create app entry for orphaned containers (e.g. wizard boilerplate\n // where health check failed but Gitea repo + Docker containers exist)\n const docker = new (await import('dockerode')).default();\n const containers = await docker.listContainers({ all: true });\n const matchedContainer = containers.find((c) => {\n const svc = c.Labels?.['com.docker.compose.service'] ?? '';\n const proj = c.Labels?.['com.docker.compose.project'] ?? '';\n return proj.includes(repoName) || proj.includes(appName) || svc === appName;\n });\n const port = matchedContainer\n ? parseInt(String(matchedContainer.Ports?.[0]?.PublicPort ?? 0), 10) || 8080\n : 8080;\n const lang = (repo as unknown as Record<string, unknown>)['language'] as string || '';\n app = {\n name: appName,\n mode: 'boilerplate' as const,\n appDir: join(projectPath, repoName),\n lang,\n port,\n giteaRepoUrl: repoUrl,\n status: matchedContainer?.State === 'running' ? 'running' : 'stopped',\n createdAt: new Date().toISOString(),\n };\n if (Array.isArray(existing)) { existing.push(app); } else { existing = [app]; }\n } else {\n app.giteaRepoUrl = repoUrl;\n }\n writeFileSync(appsPath, JSON.stringify(existing, null, 2));\n json(res, 200, { ok: true });\n } catch (err) {\n json(res, 500, { error: String(err) });\n }\n return;\n }\n\n // GET /api/apps/check-port?port=N — Check if a local port is available\n if (parts[1] === 'apps' && parts[2] === 'check-port' && req.method === 'GET') {\n const reqUrl = new URL(req.url ?? '/', 'http://localhost');\n const portStr = reqUrl.searchParams.get('port') ?? '';\n const port = parseInt(portStr, 10);\n if (!port || port < 1 || port > 65535) {\n json(res, 400, { error: 'Invalid port' });\n return;\n }\n const available = await new Promise<boolean>(resolve => {\n const sock = createConnection({ port, host: '127.0.0.1' });\n sock.once('connect', () => { sock.destroy(); resolve(false); });\n sock.once('error', () => resolve(true));\n sock.setTimeout(400, () => { sock.destroy(); resolve(true); });\n });\n json(res, 200, { port, available });\n return;\n }\n\n // POST /api/deploy/hook — Gitea push webhook for auto-deploy\n if (parts[1] === 'deploy' && parts[2] === 'hook' && req.method === 'POST') {\n try {\n const payload = JSON.parse(body) as {\n repository?: { name?: string };\n ref?: string;\n };\n const appName = payload.repository?.name;\n const branch = (payload.ref ?? '').replace('refs/heads/', '');\n if (appName) {\n const settings = getDeploySettings(appName);\n if (settings.autoDeploy && branch === settings.deployBranch) {\n void deployApp(appName);\n }\n }\n } catch { /* ignore parse errors */ }\n json(res, 200, { status: 'accepted' }); // always 200 to Gitea\n return;\n }\n\n // ── Domain API (T031-T036) ──────────────────────────────────\n if (parts[1] === 'domain') {\n // GET /api/domain/list — read-only, no auth needed (local admin server)\n if (req.method === 'GET' && parts[2] === 'list') {\n await handleDomainList(res, wizardState);\n return;\n }\n if (req.method === 'GET' && parts[2] === 'apps') {\n handleDomainApps(res, wizardState);\n return;\n }\n // POST /api/domain/connect — no auth needed (apps-page uses this; server is localhost-only)\n if (req.method === 'POST' && parts[2] === 'connect') {\n await handleDomainConnect(res, body, wizardState);\n return;\n }\n // Mutating operations that remain auth-gated\n if (!checkAdminAuth(req, res, wizardState)) return;\n if (req.method === 'DELETE' && parts[2] === 'disconnect' && parts[3]) {\n await handleDomainDisconnect(res, parts[3], wizardState);\n return;\n }\n if (req.method === 'GET' && parts[2] === 'status' && parts[3]) {\n await handleDomainStatus(res, parts[3], wizardState);\n return;\n }\n }\n\n // ── Cloudflare API (006-domain-settings) ───────────────────\n if (parts[1] === 'cloudflare') {\n if (!checkAdminAuth(req, res, wizardState)) return;\n if (req.method === 'GET' && parts[2] === 'zones') {\n await handleCloudflareZones(res, wizardState);\n return;\n }\n if (req.method === 'POST' && parts[2] === 'tunnel') {\n await handleCreateTunnel(res, body, wizardState, projectPath);\n return;\n }\n }\n\n // ── Settings API (T037-T038) ────────────────────────────────\n if (parts[1] === 'settings') {\n if (!checkAdminAuth(req, res, wizardState)) return;\n\n if (req.method === 'GET' && parts[2] === 'cloudflare') {\n handleSettingsCloudflareGet(res, wizardState);\n return;\n }\n if (req.method === 'PUT' && parts[2] === 'cloudflare') {\n await handleSettingsCloudflarePut(res, body, wizardState);\n return;\n }\n }\n\n json(res, 404, { success: false, error: 'Not found' });\n } catch (err) {\n logger.error('admin-server', 'Unhandled error', { error: String(err) });\n json(res, 500, { success: false, error: 'Internal server error' });\n }\n return;\n }\n\n res.writeHead(404);\n res.end('Not found');\n });\n\n return {\n server,\n start: () =>\n new Promise((resolve, reject) => {\n server.listen(port, '127.0.0.1', () => {\n logger.info('admin-server', `Listening on http://localhost:${port}`, { port });\n resolve(port);\n });\n server.once('error', reject);\n }),\n stop: () =>\n new Promise((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n }),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Admin password middleware (T031)\n// ---------------------------------------------------------------------------\n\nfunction checkAdminAuth(\n req: IncomingMessage,\n res: ServerResponse,\n state: WizardState | null,\n): boolean {\n if (!state?.admin?.password) {\n json(res, 401, { error: 'Unauthorized', message: 'Admin password not configured' });\n return false;\n }\n const provided = req.headers['x-admin-password'] as string | undefined;\n if (!provided || provided !== state.admin.password) {\n json(res, 401, { error: 'Unauthorized', message: 'Admin password required for this operation' });\n return false;\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Domain API handlers (T032-T036)\n// ---------------------------------------------------------------------------\n\nasync function handleDomainList(\n res: ServerResponse,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 200, { connections: [], tunnel: null, credentialsConfigured: false });\n return;\n }\n\n try {\n const mgr = new DomainManager(state.projectName);\n const connections = mgr.list().map((c) => ({\n ...c,\n externalUrl: `https://${c.hostname}`,\n }));\n\n let tunnel = null;\n const cf = state.domain.cloudflare;\n if (cf.tunnelId && cf.apiToken && cf.accountId) {\n try {\n const { getTunnelHealth } = await import('./cloudflare-client.js');\n const health = await getTunnelHealth(cf.apiToken, cf.accountId, cf.tunnelId);\n tunnel = { ...health, tunnelName: cf.tunnelName, tunnelId: cf.tunnelId };\n } catch { /* leave null */ }\n }\n\n const credentialsConfigured = !!(cf.apiToken && cf.accountId && cf.zoneId && cf.tunnelId);\n\n json(res, 200, { connections, tunnel, credentialsConfigured });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nfunction handleDomainApps(\n res: ServerResponse,\n state: WizardState | null,\n): void {\n if (!state) {\n json(res, 200, { apps: [] });\n return;\n }\n\n try {\n const mgr = new DomainManager(state.projectName);\n const apps = mgr.getConnectableApps();\n json(res, 200, { apps });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleDomainConnect(\n res: ServerResponse,\n body: string,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 500, { success: false, error: 'No project state' });\n return;\n }\n\n let parsed: { appName?: string; subdomain?: string; domain?: string };\n try {\n parsed = JSON.parse(body);\n } catch {\n json(res, 400, { success: false, error: 'INVALID_JSON', message: 'Request body must be valid JSON' });\n return;\n }\n\n const { appName, subdomain, domain } = parsed;\n if (!appName || !subdomain || !domain) {\n json(res, 400, { success: false, error: 'MISSING_FIELDS', message: 'appName, subdomain, and domain are required' });\n return;\n }\n\n // Validate subdomain format\n if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/.test(subdomain)) {\n json(res, 400, { success: false, error: 'INVALID_SUBDOMAIN', message: 'Subdomain must be a valid DNS label' });\n return;\n }\n\n // Phase 1: local conflict check — fast, no API call needed\n const localConflict = (state.domainConnections ?? []).find(\n (c) => c.subdomain === subdomain && c.domain === domain && c.appName !== appName,\n );\n if (localConflict) {\n json(res, 409, {\n success: false,\n error: 'SUBDOMAIN_CONFLICT_LOCAL',\n message: `Subdomain \"${subdomain}.${domain}\" is already connected to app \"${localConflict.appName}\"`,\n conflictingApp: localConflict.appName,\n });\n return;\n }\n\n try {\n const mgr = new DomainManager(state.projectName);\n const result = await mgr.connect(appName, subdomain, domain, {\n onLog: (line) => writeDomainLog(appName, line.replace('[domain-connect] ', '')),\n });\n\n if (!result.success) {\n // Map CNAME_CONFLICT (from Cloudflare DNS check) to SUBDOMAIN_CONFLICT_EXTERNAL\n if (result.error === 'CNAME_CONFLICT') {\n json(res, 409, {\n success: false,\n error: 'SUBDOMAIN_CONFLICT_EXTERNAL',\n message: `Subdomain \"${subdomain}.${domain}\" already has a DNS record in Cloudflare (not created by Brewnet)`,\n steps: result.steps,\n });\n return;\n }\n const statusCode = result.error?.startsWith('APP_NOT_RUNNING') ? 503 : 400;\n json(res, statusCode, { success: false, error: result.error, message: result.error, steps: result.steps });\n return;\n }\n\n json(res, 200, {\n success: true,\n hostname: result.hostname,\n externalUrl: result.externalUrl,\n steps: result.steps,\n });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleDomainDisconnect(\n res: ServerResponse,\n appName: string,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 500, { success: false, error: 'No project state' });\n return;\n }\n\n try {\n const mgr = new DomainManager(state.projectName);\n const result = await mgr.disconnect(appName);\n\n if (!result.success) {\n const statusCode = result.error?.startsWith('NOT_CONNECTED') ? 404 : 500;\n json(res, statusCode, { success: false, error: result.error?.split(':')[0], message: result.error });\n return;\n }\n\n json(res, 200, {\n success: true,\n appName: result.appName,\n removedHostname: result.removedHostname,\n steps: result.steps,\n });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\nasync function handleDomainStatus(\n res: ServerResponse,\n appName: string,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 404, { success: false, error: 'No project state' });\n return;\n }\n\n try {\n const mgr = new DomainManager(state.projectName);\n const statuses = await mgr.status(appName);\n\n if (statuses.length === 0) {\n json(res, 404, { success: false, error: 'NOT_CONNECTED', message: `No domain connection for app: ${appName}` });\n return;\n }\n\n json(res, 200, statuses[0]);\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cloudflare API handlers (006-domain-settings)\n// ---------------------------------------------------------------------------\n\nasync function handleCloudflareZones(\n res: ServerResponse,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 400, { success: false, error: 'NO_TOKEN', message: 'Cloudflare API token not configured. Complete Step 1 first.' });\n return;\n }\n\n const apiToken = state.domain.cloudflare.apiToken;\n if (!apiToken) {\n json(res, 400, { success: false, error: 'NO_TOKEN', message: 'Cloudflare API token not configured. Complete Step 1 first.' });\n return;\n }\n\n try {\n const { getZones } = await import('./cloudflare-client.js');\n const zones = await getZones(apiToken);\n\n // Extract accountId from the first zone (requires only Zone:Read).\n // This ensures accountId is set before tunnel creation even when\n // Account:Read permission is absent and getAccounts() returns [].\n if (!state.domain.cloudflare.accountId && zones.length > 0) {\n const firstAccountId = zones[0]?.accountId;\n if (firstAccountId) {\n state.domain.cloudflare.accountId = firstAccountId;\n const { saveState: save } = await import('../wizard/state.js');\n save(state);\n }\n }\n\n if (zones.length === 0) {\n json(res, 200, {\n success: true,\n zones: [],\n warning: 'No domains found. Ensure the token has Zone:Read permission and at least one domain is registered in your Cloudflare account.',\n });\n return;\n }\n\n json(res, 200, { success: true, zones });\n } catch (err) {\n json(res, 400, {\n success: false,\n error: 'TOKEN_INVALID',\n message: 'Stored API token is no longer valid. Please re-enter your token.',\n });\n }\n}\n\nasync function handleCreateTunnel(\n res: ServerResponse,\n body: string,\n state: WizardState | null,\n projectPath: string,\n): Promise<void> {\n if (!state) {\n json(res, 400, { success: false, error: 'CREDENTIALS_INCOMPLETE', message: 'API token and zone must be configured before creating a tunnel.' });\n return;\n }\n\n let parsed: { tunnelName?: string };\n try {\n parsed = JSON.parse(body);\n } catch {\n json(res, 400, { success: false, error: 'INVALID_JSON', message: 'Request body must be valid JSON' });\n return;\n }\n\n const { tunnelName } = parsed;\n if (!tunnelName || !tunnelName.trim()) {\n json(res, 400, { success: false, error: 'MISSING_TUNNEL_NAME', message: 'tunnelName is required' });\n return;\n }\n\n const cf = state.domain.cloudflare;\n if (!cf.apiToken || !cf.accountId || !cf.zoneId) {\n json(res, 400, { success: false, error: 'CREDENTIALS_INCOMPLETE', message: 'API token and zone must be configured before creating a tunnel.' });\n return;\n }\n\n try {\n const { createTunnel: cfCreateTunnel } = await import('./cloudflare-client.js');\n const result = await cfCreateTunnel(cf.apiToken, cf.accountId, tunnelName.trim());\n\n const { saveState: save } = await import('../wizard/state.js');\n state.domain.cloudflare.tunnelId = result.tunnelId;\n state.domain.cloudflare.tunnelToken = result.tunnelToken;\n state.domain.cloudflare.tunnelName = tunnelName.trim();\n state.domain.cloudflare.tunnelMode = 'named';\n state.domain.cloudflare.enabled = true;\n save(state);\n\n // Auto-patch docker-compose.yml and recreate cloudflared container so the\n // new named tunnel takes effect without manual intervention.\n let composeUpdated = false;\n let containerRestarted = false;\n\n const composePath = join(projectPath, 'docker-compose.yml');\n const { existsSync: fsExists } = await import('node:fs');\n if (fsExists(composePath)) {\n try {\n const { patchCloudflaredToNamedTunnel } = await import('./compose-generator.js');\n composeUpdated = patchCloudflaredToNamedTunnel(composePath, result.tunnelToken);\n logger.info('tunnel', `[${tunnelName}] compose patch: composeUpdated=${composeUpdated}`);\n } catch (e) {\n logger.warn('tunnel', `[${tunnelName}] compose patch failed: ${e instanceof Error ? e.message : e}`);\n }\n\n if (composeUpdated) {\n try {\n const { execa: execaTunnel } = await import('execa');\n const up = await execaTunnel(\n 'docker',\n ['compose', '-f', composePath, 'up', '-d', '--force-recreate', 'cloudflared'],\n { cwd: projectPath, reject: false },\n );\n containerRestarted = up.exitCode === 0;\n if (!containerRestarted) {\n logger.warn('tunnel', `[${tunnelName}] cloudflared recreate failed (exit ${up.exitCode}): ${up.stderr}`);\n } else {\n logger.info('tunnel', `[${tunnelName}] cloudflared container recreated`);\n }\n } catch (e) {\n logger.warn('tunnel', `[${tunnelName}] cloudflared recreate exception: ${e instanceof Error ? e.message : e}`);\n }\n }\n } else {\n logger.warn('tunnel', `[${tunnelName}] compose file not found at ${composePath} — cloudflared must be updated manually`);\n }\n\n json(res, 200, {\n success: true,\n tunnelId: result.tunnelId,\n tunnelName: tunnelName.trim(),\n composeUpdated,\n containerRestarted,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.toLowerCase().includes('already exists')) {\n json(res, 400, {\n success: false,\n error: 'TUNNEL_NAME_CONFLICT',\n message: `A tunnel named \"${tunnelName}\" already exists in your Cloudflare account. Choose a different name.`,\n });\n } else {\n json(res, 400, {\n success: false,\n error: 'TUNNEL_CREATE_FAILED',\n message: msg,\n });\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Settings API handlers (T037-T038)\n// ---------------------------------------------------------------------------\n\nfunction handleSettingsCloudflareGet(\n res: ServerResponse,\n state: WizardState | null,\n): void {\n if (!state) {\n json(res, 200, { configured: false });\n return;\n }\n\n const cf = state.domain.cloudflare;\n const mask = (s: string | undefined) => !s ? 'not set' : s.length > 6 ? s.slice(0, 3) + '***' + s.slice(-3) : '***set***';\n\n json(res, 200, {\n configured: !!(cf.apiToken && cf.accountId && cf.zoneId),\n accountId: mask(cf.accountId),\n zoneId: mask(cf.zoneId),\n zoneName: cf.zoneName || '',\n tunnelId: mask(cf.tunnelId),\n tunnelName: cf.tunnelName || '',\n apiTokenSet: !!cf.apiToken,\n apiTokenValid: !!cf.apiToken, // Validated on save, assumed valid if set\n projectName: state.projectName || '',\n });\n}\n\nasync function handleSettingsCloudflarePut(\n res: ServerResponse,\n body: string,\n state: WizardState | null,\n): Promise<void> {\n if (!state) {\n json(res, 500, { success: false, error: 'No project state' });\n return;\n }\n\n let parsed: { apiToken?: string; accountId?: string; zoneId?: string; tunnelId?: string };\n try {\n parsed = JSON.parse(body);\n } catch {\n json(res, 400, { success: false, error: 'INVALID_JSON', message: 'Request body must be valid JSON' });\n return;\n }\n\n const { accountId, zoneId, tunnelId } = parsed;\n // Allow zone/tunnel saves without re-sending the token when it's already stored in memory\n const apiToken = parsed.apiToken || state.domain.cloudflare.apiToken;\n if (!apiToken) {\n json(res, 400, { success: false, error: 'MISSING_TOKEN', message: 'apiToken is required' });\n return;\n }\n\n // Verify the token\n try {\n const result = await verifyToken(apiToken);\n if (!result.valid) {\n json(res, 400, {\n success: false,\n error: 'INVALID_TOKEN',\n message: 'API token verification failed. Ensure the token has Tunnel:Edit, DNS:Edit, Zone:Read permissions.',\n });\n return;\n }\n\n // Update state in-place so in-memory wizardState is also updated immediately\n const { saveState: save } = await import('../wizard/state.js');\n state.domain.cloudflare.apiToken = apiToken;\n\n // Prefer explicit accountId param, then existing stored value, then getAccounts() (requires Account:Read)\n let resolvedAccountId = accountId || state.domain.cloudflare.accountId\n || await (await import('./cloudflare-client.js')).getAccounts(apiToken)\n .then((a) => a[0]?.id ?? '').catch(() => '');\n\n if (zoneId) state.domain.cloudflare.zoneId = zoneId;\n if (tunnelId) state.domain.cloudflare.tunnelId = tunnelId;\n\n // Get zone name for response and extract accountId from zone when getAccounts() returned nothing\n let zoneName = state.domain.cloudflare.zoneName;\n if (zoneId) {\n try {\n const { getZones } = await import('./cloudflare-client.js');\n const zones = await getZones(apiToken);\n const found = zones.find((z) => z.id === zoneId);\n if (found) {\n zoneName = found.name;\n state.domain.cloudflare.zoneName = zoneName;\n // accountId from zone (requires only Zone:Read — always works)\n if (!resolvedAccountId && found.accountId) {\n resolvedAccountId = found.accountId;\n }\n }\n } catch { /* non-critical */ }\n }\n\n if (resolvedAccountId) state.domain.cloudflare.accountId = resolvedAccountId;\n save(state);\n\n json(res, 200, {\n success: true,\n verified: true,\n email: result.email ?? '',\n zoneName,\n });\n } catch (err) {\n json(res, 500, { success: false, error: String(err) });\n }\n}\n","// packages/cli/src/services/app-manager.ts\nimport { existsSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { randomBytes } from 'node:crypto';\nimport { execa } from 'execa';\nimport { GiteaClient } from './gitea-client.js';\nimport { readApps, addApp, updateApp, removeApp as registryRemoveApp, readDeployHistory, appendDeployHistory } from './app-registry.js';\nimport { getLastProject, loadState } from '../wizard/state.js';\nimport type { AppEntry, AppJob, AppJobStep, CreateAppOptions, DeployHistoryEntry, GitRepoEntry, AppGitInfo, DeploySettings } from '../types/app-entry.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst BREWNET_DIR = join(homedir(), '.brewnet');\nconst GITEA_TOKEN_PATH = join(BREWNET_DIR, 'gitea-token');\nconst DEPLOY_HISTORY_PATH = join(BREWNET_DIR, 'deploy-history.json');\n\n// ---------------------------------------------------------------------------\n// In-memory job store (ephemeral — cleared on server restart)\n// ---------------------------------------------------------------------------\n\nconst jobs = new Map<string, AppJob>();\n\n// ---------------------------------------------------------------------------\n// Exported helpers (testable in isolation)\n// ---------------------------------------------------------------------------\n\nexport function resolveAppsJsonPath(): string {\n return join(BREWNET_DIR, 'apps.json');\n}\n\n/** Parse a single KEY=VALUE line from a .env file. Returns '' if not found. */\nexport function readDotEnvValue(envPath: string, key: string): string {\n if (!existsSync(envPath)) return '';\n const lines = readFileSync(envPath, 'utf-8').split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed.startsWith(`${key}=`)) {\n return trimmed.slice(key.length + 1).trim();\n }\n }\n return '';\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nlet _boilerplateRegistered = false;\n\nexport async function listApps(): Promise<AppEntry[]> {\n const appsJson = resolveAppsJsonPath();\n const apps = readApps(appsJson);\n\n // Auto-register wizard boilerplates (once per process lifetime).\n // This bridges the gap between `brewnet init` (writes .brewnet-boilerplate.json)\n // and the Apps page (reads apps.json).\n if (_boilerplateRegistered) return apps;\n _boilerplateRegistered = true;\n try {\n const ctx = resolveContext();\n const bpPath = join(ctx.projectPath, '.brewnet-boilerplate.json');\n if (existsSync(bpPath)) {\n const raw = JSON.parse(readFileSync(bpPath, 'utf-8'));\n const bpMetas: Array<{ stackId: string; appDir: string; lang?: string; frameworkId?: string; status?: string; backendUrl?: string }> =\n Array.isArray(raw) ? raw : [raw];\n let changed = false;\n for (const bp of bpMetas) {\n if (!bp.stackId || !bp.appDir) continue;\n // Check if already registered by stackId or appDir\n const exists = apps.some((a) => a.appDir === bp.appDir || a.stackId === bp.stackId);\n if (!exists) {\n const port = bp.backendUrl ? parseInt(new URL(bp.backendUrl).port || '8080', 10) : 8080;\n const entry: AppEntry = {\n name: bp.stackId,\n mode: 'boilerplate',\n stackId: bp.stackId,\n appDir: bp.appDir,\n lang: bp.lang,\n framework: bp.frameworkId,\n port,\n status: (bp.status as AppEntry['status']) || 'running',\n createdAt: new Date().toISOString(),\n };\n apps.push(entry);\n changed = true;\n }\n }\n if (changed) {\n writeFileSync(appsJson, JSON.stringify(apps, null, 2), 'utf-8');\n }\n }\n } catch { /* non-critical — boilerplate auto-register is best-effort */ }\n\n return apps;\n}\n\nexport function getDeployHistory(appName?: string): DeployHistoryEntry[] {\n const entries = readDeployHistory(DEPLOY_HISTORY_PATH);\n if (!appName) return entries;\n return entries.filter((e) => e.appName === appName);\n}\n\nexport async function listGiteaRepos(): Promise<GitRepoEntry[]> {\n const ctx = resolveContext();\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n await gitea.prepare(); // validate/refresh token before listing\n return gitea.listRepos();\n}\n\nexport function getDeploySettings(appName: string): DeploySettings {\n const apps = readApps(resolveAppsJsonPath());\n const app = apps.find((a) => a.name === appName);\n const settings = (app as (AppEntry & { deploySettings?: DeploySettings }) | undefined)?.deploySettings;\n return settings ?? { autoDeploy: false, deployBranch: 'main' };\n}\n\nexport function updateDeploySettings(appName: string, settings: Partial<DeploySettings>): void {\n const appsJson = resolveAppsJsonPath();\n const apps = readApps(appsJson);\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n const existing = (app as AppEntry & { deploySettings?: DeploySettings }).deploySettings\n ?? { autoDeploy: false, deployBranch: 'main' };\n (app as AppEntry & { deploySettings?: DeploySettings }).deploySettings = { ...existing, ...settings };\n updateApp(appsJson, appName, app as Partial<AppEntry>);\n}\n\nexport async function getAppGitInfo(appName: string): Promise<AppGitInfo> {\n const ctx = resolveContext();\n const apps = readApps(resolveAppsJsonPath());\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n\n let branch = 'main';\n let latestCommit: AppGitInfo['latestCommit'] = null;\n let cloneUrlSsh = `ssh://git@localhost:2222/${ctx.giteaUser}/${appName}.git`;\n\n try {\n const repo = await gitea.getRepo(appName);\n branch = repo.default_branch || 'main';\n cloneUrlSsh = repo.ssh_url || cloneUrlSsh;\n // Auto-fix repos created before visibility change (private → public)\n if (repo.private) {\n await gitea.makeRepoPublic(appName).catch((e) => {\n console.warn(`[app-manager] makeRepoPublic failed for ${appName}: ${e instanceof Error ? e.message : String(e)}`);\n });\n }\n latestCommit = await gitea.getLatestCommit(appName, branch);\n } catch (e) {\n console.warn(`[app-manager] getAppGitInfo Gitea call failed (${appName}): ${e instanceof Error ? e.message : String(e)}`);\n }\n\n return {\n giteaUrl: `${ctx.giteaBaseUrl}/${ctx.giteaUser}/${appName}`,\n cloneUrlHttp: `${ctx.giteaBaseUrl}/${ctx.giteaUser}/${appName}.git`,\n cloneUrlSsh,\n localPath: app.appDir,\n branch,\n latestCommit,\n };\n}\n\nexport async function rollbackApp(appName: string, commitHash: string): Promise<string> {\n const job = newJob(appName, ['Checkout', 'Build & Start', 'Health check']);\n jobs.set(job.jobId, job);\n setImmediate(() => void _runRollback(job, appName, commitHash));\n return job.jobId;\n}\n\nasync function _runRollback(job: AppJob, appName: string, commitHash: string): Promise<void> {\n try {\n const apps = readApps(resolveAppsJsonPath());\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n\n const target = commitHash || 'HEAD~1';\n setStep(job, 0, 'running', `git checkout ${target.slice(0, 7)}`);\n await execa('git', ['checkout', target], { cwd: app.appDir });\n setStep(job, 0, 'done');\n\n await _injectQuickTunnelIfNeeded(app.appDir, appName, app.port);\n\n setStep(job, 1, 'running', 'docker compose up --build');\n await _dockerComposeUp(app.appDir, job);\n setStep(job, 1, 'done', 'containers started');\n\n setStep(job, 2, 'running');\n const healthUrl = _buildHealthUrl(app.appDir, app.port);\n setStep(job, 2, 'running', `polling ${healthUrl}`);\n await _pollHealth(healthUrl, 120_000, job);\n setStep(job, 2, 'done');\n\n updateApp(resolveAppsJsonPath(), appName, { status: 'running' });\n appendDeployHistory(DEPLOY_HISTORY_PATH, {\n appName,\n commitHash,\n commitMessage: `Rollback to ${commitHash.slice(0, 7)}`,\n status: 'success',\n deployedAt: new Date().toISOString(),\n });\n\n job.status = 'done';\n } catch (err) {\n job.status = 'failed';\n job.error = err instanceof Error ? err.message : String(err);\n for (const step of job.steps) {\n if (step.status === 'running' || step.status === 'pending') step.status = 'failed';\n }\n appendDeployHistory(DEPLOY_HISTORY_PATH, {\n appName,\n commitHash,\n commitMessage: `Rollback to ${commitHash.slice(0, 7)}`,\n status: 'failed',\n deployedAt: new Date().toISOString(),\n });\n }\n}\n\nexport async function getAppBranches(appName: string): Promise<string[]> {\n const ctx = resolveContext();\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n return gitea.listBranches(appName);\n}\n\nexport async function setupWebhook(appName: string, webhookUrl: string): Promise<void> {\n const ctx = resolveContext();\n const settings = getDeploySettings(appName);\n const secret = settings.webhookSecret ?? randomBytes(16).toString('hex');\n\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n\n await gitea.createWebhook(appName, webhookUrl, secret);\n updateDeploySettings(appName, { webhookSecret: secret });\n}\n\nexport async function deployApp(appName: string): Promise<string> {\n const job = newJob(appName, ['Pull', 'Build & Start', 'Health check']);\n jobs.set(job.jobId, job);\n setImmediate(() => void _runDeploy(job, appName));\n return job.jobId;\n}\n\nasync function _runDeploy(job: AppJob, appName: string): Promise<void> {\n const apps = readApps(resolveAppsJsonPath());\n const app = apps.find((a) => a.name === appName);\n if (!app) { job.status = 'failed'; job.error = `App \"${appName}\" not found`; return; }\n try {\n const settings = getDeploySettings(appName);\n\n setStep(job, 0, 'running');\n try {\n const ctx = resolveContext();\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n await gitea.prepare();\n const repoExists = await gitea.repoExists(appName);\n if (!repoExists) {\n appendLog(job, '[pull] Gitea repo not found — recreating and pushing local code');\n const cloneUrl = await gitea.createRepo(appName, `Brewnet app: ${appName}`);\n const authedUrl = gitea.authedCloneUrl(cloneUrl);\n await execa('git', ['remote', 'add', 'brewnet', authedUrl], { cwd: app.appDir }).catch(() =>\n execa('git', ['remote', 'set-url', 'brewnet', authedUrl], { cwd: app.appDir }),\n );\n await execa('git', ['push', 'brewnet', 'HEAD:main', '--force'], { cwd: app.appDir });\n appendLog(job, '[pull] Gitea repo recreated and code pushed ✓');\n } else if (!existsSync(app.appDir)) {\n // appDir was deleted — re-clone from Gitea\n appendLog(job, '[pull] appDir missing — cloning from Gitea');\n const authedUrl = gitea.authedCloneUrl(`${ctx.giteaBaseUrl}/${ctx.giteaUser}/${appName}.git`);\n await execa('git', ['clone', authedUrl, app.appDir]);\n appendLog(job, '[pull] re-cloned from Gitea ✓');\n } else if (await gitea.repoIsEmpty(appName)) {\n // Repo exists but was never pushed to (e.g. created during app setup but push was skipped)\n appendLog(job, '[pull] Gitea repo is empty — pushing local code');\n // Boilerplates are cloned --depth 1; unshallow before pushing to empty Gitea repo.\n // reinitGit wipes .git, so remote must be added AFTER this step (same order as _createModeA).\n const isShallow = await execa('git', ['rev-parse', '--is-shallow-repository'], { cwd: app.appDir })\n .then((r) => r.stdout.trim() === 'true').catch(() => false);\n if (isShallow) {\n appendLog(job, '[pull] shallow clone detected — unshallowing');\n await execa('git', ['fetch', '--unshallow', 'origin'], { cwd: app.appDir }).catch(async () => {\n const { reinitGit } = await import('./boilerplate-manager.js');\n await reinitGit(app.appDir);\n });\n }\n const authedUrl = gitea.authedCloneUrl(`${ctx.giteaBaseUrl}/${ctx.giteaUser}/${appName}.git`);\n await execa('git', ['remote', 'add', 'brewnet', authedUrl], { cwd: app.appDir }).catch(() =>\n execa('git', ['remote', 'set-url', 'brewnet', authedUrl], { cwd: app.appDir }),\n );\n await execa('git', ['push', 'brewnet', 'HEAD:main', '--force'], { cwd: app.appDir });\n appendLog(job, '[pull] code pushed to Gitea ✓');\n } else {\n await execa('git', ['pull', 'brewnet', settings.deployBranch], { cwd: app.appDir }).catch((e) => {\n appendLog(job, `[pull] git pull failed (non-critical): ${e instanceof Error ? e.message : String(e)}`);\n });\n }\n } catch (e) {\n appendLog(job, `[pull] Gitea sync failed (non-critical): ${e instanceof Error ? e.message : String(e)}`);\n }\n setStep(job, 0, 'done');\n\n // Check if deployable — must have docker-compose.yml (or auto-scaffold)\n const hasCompose = existsSync(join(app.appDir, 'docker-compose.yml')) || existsSync(join(app.appDir, 'compose.yml'));\n if (!hasCompose) {\n // Try auto-scaffold for known project types\n const projectType = _detectProjectType(app.appDir);\n if (projectType) {\n appendLog(job, `[scaffold] Detected ${projectType} project — generating Docker config`);\n _scaffoldDockerConfig(app.appDir, appName, app.port, job, projectType);\n } else {\n throw new Error(\n 'This project has no docker-compose.yml or Dockerfile. ' +\n 'Add a Dockerfile and docker-compose.yml to deploy, or use a Brewnet boilerplate.',\n );\n }\n }\n\n // Inject Traefik Quick Tunnel labels (idempotent — safe to call even if labels already present)\n await _injectQuickTunnelIfNeeded(app.appDir, appName, app.port);\n\n setStep(job, 1, 'running', 'docker compose up --build');\n await _dockerComposeUp(app.appDir, job);\n setStep(job, 1, 'done', 'containers started');\n\n setStep(job, 2, 'running');\n const healthUrlDeploy = _buildHealthUrl(app.appDir, app.port);\n setStep(job, 2, 'running', `polling ${healthUrlDeploy}`);\n await _pollHealth(healthUrlDeploy, 120_000, job);\n setStep(job, 2, 'done');\n\n updateApp(resolveAppsJsonPath(), appName, { status: 'running' });\n const headHash = await execa('git', ['rev-parse', 'HEAD'], { cwd: app.appDir })\n .then((r) => r.stdout.trim()).catch(() => '');\n const headMsg = await execa('git', ['log', '-1', '--format=%s'], { cwd: app.appDir })\n .then((r) => r.stdout.trim()).catch(() => 'Manual deploy');\n appendDeployHistory(DEPLOY_HISTORY_PATH, {\n appName,\n commitHash: headHash,\n commitMessage: headMsg,\n status: 'success',\n deployedAt: new Date().toISOString(),\n });\n\n job.status = 'done';\n } catch (err) {\n job.status = 'failed';\n job.error = err instanceof Error ? err.message : String(err);\n for (const step of job.steps) {\n if (step.status === 'running' || step.status === 'pending') step.status = 'failed';\n }\n const headHashFail = await execa('git', ['rev-parse', 'HEAD'], { cwd: app.appDir })\n .then((r) => r.stdout.trim()).catch(() => '');\n appendDeployHistory(DEPLOY_HISTORY_PATH, {\n appName,\n commitHash: headHashFail,\n commitMessage: 'Manual deploy',\n status: 'failed',\n deployedAt: new Date().toISOString(),\n });\n }\n}\n\nexport function getAppDir(appName: string): string | undefined {\n const apps = readApps(resolveAppsJsonPath());\n return apps.find((a) => a.name === appName)?.appDir;\n}\n\nexport function getJobStatus(jobId: string): AppJob | undefined {\n return jobs.get(jobId);\n}\n\n// ---------------------------------------------------------------------------\n// Internal: job helpers\n// ---------------------------------------------------------------------------\n\nfunction newJob(appName: string, stepLabels: string[]): AppJob {\n return {\n jobId: randomBytes(8).toString('hex'),\n appName,\n status: 'running',\n steps: stepLabels.map((label) => ({ label, status: 'pending' })),\n };\n}\n\nfunction setStep(job: AppJob, index: number, status: AppJobStep['status'], message?: string): void {\n const step = job.steps[index];\n if (step) { step.status = status; if (message) step.message = message; }\n}\n\n/** Append a log line to the job's rolling log buffer (max 200 lines). */\nfunction appendLog(job: AppJob, line: string): void {\n if (!job.logs) job.logs = [];\n job.logs.push(line);\n if (job.logs.length > 200) job.logs.splice(0, job.logs.length - 200);\n}\n\n// ---------------------------------------------------------------------------\n// Internal: read boilerplate meta from project\n// ---------------------------------------------------------------------------\n\ninterface BoilerplateMeta {\n stackId: string;\n appDir: string;\n backendUrl: string;\n port?: number;\n lang: string;\n frameworkId: string;\n status: string;\n}\n\nfunction readBoilerplateMeta(projectPath: string): BoilerplateMeta[] {\n const p = join(projectPath, '.brewnet-boilerplate.json');\n if (!existsSync(p)) return [];\n try {\n const raw = JSON.parse(readFileSync(p, 'utf-8'));\n return Array.isArray(raw) ? raw : [raw];\n } catch { return []; }\n}\n\n// ---------------------------------------------------------------------------\n// Internal: resolve wizard context\n// ---------------------------------------------------------------------------\n\ninterface AppContext {\n projectPath: string;\n giteaBaseUrl: string;\n giteaUser: string;\n giteaPassword: string;\n}\n\nfunction resolveContext(): AppContext {\n const last = getLastProject();\n const state = loadState(last ?? '');\n const raw = (state as { projectPath?: string } | null)?.projectPath ?? process.cwd();\n const projectPath = raw.startsWith('~') ? join(homedir(), raw.slice(1)) : raw;\n const envPath = join(projectPath, '.env');\n const giteaUser = readDotEnvValue(envPath, 'GITEA_ADMIN_USER') || (state?.admin as { username?: string } | undefined)?.username || 'admin';\n // GITEA_ADMIN_PASSWORD is a Docker secret, NOT in .env — read from secrets file first\n const secretsPath = join(projectPath, 'secrets', 'admin_password');\n const secretsPassword = existsSync(secretsPath) ? readFileSync(secretsPath, 'utf-8').trim() : '';\n const giteaPassword = secretsPassword || readDotEnvValue(envPath, 'GITEA_ADMIN_PASSWORD') || (state?.admin as { password?: string } | undefined)?.password || '';\n // Quick Tunnel: Gitea is path-prefix routed via Traefik (port 80, /git); port 3000 is internal only.\n // Other modes: Gitea port 3000 is host-exposed — use direct port to avoid subdomain DNS dependency.\n const tunnelMode = state?.domain?.cloudflare?.tunnelMode ?? '';\n const gitPort = state?.servers?.gitServer?.port ?? 3000;\n const giteaBaseUrl = tunnelMode === 'quick' ? 'http://localhost/git' : `http://localhost:${gitPort}`;\n return { projectPath, giteaBaseUrl, giteaUser, giteaPassword };\n}\n\n/** Inject Traefik Quick Tunnel labels if running in quick tunnel mode. */\nasync function _injectQuickTunnelIfNeeded(appDir: string, appName: string, port: number): Promise<void> {\n try {\n const last = getLastProject();\n const state = loadState(last ?? '');\n if (state?.domain?.cloudflare?.tunnelMode !== 'quick') return;\n const { injectTraefikForQuickTunnel } = await import('./boilerplate-manager.js');\n injectTraefikForQuickTunnel(appDir, appName, port);\n } catch (err) {\n // Log but don't fail — external access simply won't work\n console.error(`[Quick Tunnel] Failed to inject Traefik labels for ${appName}: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal: simple health poll\n// ---------------------------------------------------------------------------\n\n/**\n * Detect project type from files in the directory.\n */\nfunction _detectProjectType(dir: string): 'nextjs' | 'nodejs' | 'python' | 'go' | 'rust' | 'java' | 'static' | null {\n try {\n if (existsSync(join(dir, 'next.config.ts')) || existsSync(join(dir, 'next.config.mjs')) || existsSync(join(dir, 'next.config.js'))) return 'nextjs';\n if (existsSync(join(dir, 'package.json'))) return 'nodejs';\n if (existsSync(join(dir, 'requirements.txt')) || existsSync(join(dir, 'pyproject.toml'))) return 'python';\n if (existsSync(join(dir, 'go.mod'))) return 'go';\n if (existsSync(join(dir, 'Cargo.toml'))) return 'rust';\n if (existsSync(join(dir, 'pom.xml')) || existsSync(join(dir, 'build.gradle')) || existsSync(join(dir, 'build.gradle.kts'))) return 'java';\n // Static HTML — fallback if index.html exists or any .html files\n if (existsSync(join(dir, 'index.html'))) return 'static';\n if (readdirSync(dir).some((f: string) => f.endsWith('.html'))) return 'static';\n } catch { /* ignore */ }\n return null;\n}\n\n/**\n * Generate Dockerfile + docker-compose.yml for projects that don't have them.\n */\nfunction _scaffoldDockerConfig(dir: string, _appName: string, port: number, job?: AppJob, detectedType?: string): void {\n const type = detectedType || _detectProjectType(dir);\n if (!type) throw new Error(`Cannot auto-detect project type in ${dir}. Add a Dockerfile and docker-compose.yml manually.`);\n\n if (job && !detectedType) appendLog(job, `[scaffold] Detected ${type} project — generating Docker config`);\n\n let dockerfile = '';\n\n switch (type) {\n case 'nextjs':\n // Use simple single-stage build — external Next.js projects may not\n // have output:'standalone' configured, and modifying their config\n // can break cached Docker layers. Just npm install + build + start.\n dockerfile = [\n 'FROM node:22-alpine',\n 'WORKDIR /app',\n 'COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./',\n 'RUN npm install --legacy-peer-deps 2>/dev/null || yarn install 2>/dev/null || true',\n 'COPY . .',\n 'RUN npm run build',\n 'ENV PORT=3000 HOSTNAME=0.0.0.0',\n 'EXPOSE 3000',\n 'CMD [\"npm\", \"start\"]',\n ].join('\\n');\n break;\n case 'nodejs':\n dockerfile = [\n 'FROM node:22-alpine',\n 'WORKDIR /app',\n 'COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./',\n 'RUN npm install --legacy-peer-deps || true',\n 'COPY . .',\n 'RUN npm run build 2>/dev/null || true',\n 'EXPOSE ' + port,\n 'CMD [\"npm\", \"start\"]',\n ].join('\\n');\n break;\n case 'python':\n dockerfile = [\n 'FROM python:3.13-slim',\n 'WORKDIR /app',\n 'COPY requirements.txt* pyproject.toml* ./',\n 'RUN pip install --no-cache-dir -r requirements.txt 2>/dev/null || pip install --no-cache-dir . 2>/dev/null || true',\n 'COPY . .',\n 'EXPOSE ' + port,\n 'CMD [\"python\", \"-m\", \"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"' + port + '\"]',\n ].join('\\n');\n break;\n case 'go':\n dockerfile = [\n 'FROM golang:1.22-alpine AS builder',\n 'WORKDIR /app',\n 'COPY go.mod go.sum* ./',\n 'RUN go mod download',\n 'COPY . .',\n 'RUN CGO_ENABLED=0 go build -o server .',\n '',\n 'FROM alpine',\n 'WORKDIR /app',\n 'COPY --from=builder /app/server .',\n 'EXPOSE ' + port,\n 'CMD [\"./server\"]',\n ].join('\\n');\n break;\n case 'rust':\n dockerfile = [\n 'FROM rust:1.88 AS builder',\n 'WORKDIR /app',\n 'COPY . .',\n 'RUN cargo build --release',\n '',\n 'FROM debian:bookworm-slim',\n 'WORKDIR /app',\n 'COPY --from=builder /app/target/release/* /app/ 2>/dev/null || true',\n 'EXPOSE ' + port,\n 'CMD [\"./app\"]',\n ].join('\\n');\n break;\n case 'java':\n dockerfile = [\n 'FROM gradle:8.12-jdk21 AS builder',\n 'WORKDIR /app',\n 'COPY . .',\n 'RUN gradle build -x test 2>/dev/null || ./gradlew build -x test 2>/dev/null || mvn package -DskipTests 2>/dev/null || true',\n '',\n 'FROM eclipse-temurin:21-jre-alpine',\n 'WORKDIR /app',\n 'COPY --from=builder /app/build/libs/*.jar app.jar 2>/dev/null || true',\n 'COPY --from=builder /app/target/*.jar app.jar 2>/dev/null || true',\n 'EXPOSE ' + port,\n 'CMD [\"java\", \"-jar\", \"app.jar\"]',\n ].join('\\n');\n break;\n case 'static':\n dockerfile = [\n 'FROM nginx:1.27-alpine',\n 'COPY . /usr/share/nginx/html/',\n 'EXPOSE 80',\n ].join('\\n');\n break;\n }\n\n const internalPort = type === 'nextjs' ? 3000 : type === 'static' ? 80 : port;\n const compose = [\n 'services:',\n ' backend:',\n ' build: .',\n ' ports:',\n ` - \"${port}:${internalPort}\"`,\n ' restart: unless-stopped',\n ' healthcheck:',\n ` test: [\"CMD\", \"wget\", \"-q\", \"-O\", \"/dev/null\", \"http://127.0.0.1:${internalPort}/\"]`,\n ' interval: 10s',\n ' timeout: 5s',\n ' retries: 5',\n ].join('\\n');\n\n if (!existsSync(join(dir, 'Dockerfile'))) {\n writeFileSync(join(dir, 'Dockerfile'), dockerfile, 'utf-8');\n if (job) appendLog(job, '[scaffold] Generated Dockerfile');\n }\n writeFileSync(join(dir, 'docker-compose.yml'), compose, 'utf-8');\n if (job) appendLog(job, '[scaffold] Generated docker-compose.yml');\n\n // .dockerignore\n if (!existsSync(join(dir, '.dockerignore'))) {\n writeFileSync(join(dir, '.dockerignore'), 'node_modules\\n.next\\n.git\\n*.md\\n', 'utf-8');\n }\n}\n\n/**\n * Ensure a docker-compose.yml exists in `dir`.\n * If missing, auto-detect project type and scaffold Dockerfile + compose.\n */\nfunction ensureComposeFile(dir: string, appName: string, port: number, job?: AppJob): void {\n if (existsSync(join(dir, 'docker-compose.yml')) || existsSync(join(dir, 'compose.yml'))) return;\n _scaffoldDockerConfig(dir, appName, port, job);\n}\n\n/**\n * Resolve the backend health check port for a given app directory.\n * Reads BACKEND_PORT from .env (set by generateEnv).\n * Falls back to the provided port if .env is absent or has no BACKEND_PORT.\n * This ensures health checks always target the backend, not the frontend nginx.\n */\nfunction _resolveBackendPort(appDir: string, fallbackPort: number): number {\n const envPath = join(appDir, '.env');\n const val = readDotEnvValue(envPath, 'BACKEND_PORT');\n const parsed = val ? parseInt(val, 10) : NaN;\n return isNaN(parsed) ? fallbackPort : parsed;\n}\n\n/**\n * Detect Next.js basePath from next.config.ts/mjs/js in the app directory.\n * Returns the basePath string (e.g. '/apps/my-app') or '' if not set.\n * Next.js bakes basePath at build time — /health becomes /apps/my-app/health.\n */\nexport function detectBasePath(appDir: string): string {\n for (const name of ['next.config.ts', 'next.config.mjs', 'next.config.js']) {\n const p = join(appDir, name);\n if (existsSync(p)) {\n const content = readFileSync(p, 'utf-8');\n const match = content.match(/basePath\\s*:\\s*['\"`]([^'\"`]+)['\"`]/);\n if (match) return match[1]!;\n }\n }\n return '';\n}\n\n/**\n * Build the health check URL for an app.\n * - Brewnet boilerplates have /health endpoint → use /health\n * - Scaffolded/general projects → use / (root)\n * - Next.js with basePath → prefix accordingly\n */\nfunction _buildHealthUrl(appDir: string, fallbackPort: number): string {\n const healthPort = _resolveBackendPort(appDir, fallbackPort);\n const basePath = detectBasePath(appDir);\n // Check if this is a brewnet boilerplate (has .env.example with STACK_LANG)\n // or has an explicit /health route file\n const isBoilerplate = existsSync(join(appDir, '.env.example'))\n && readFileSync(join(appDir, '.env.example'), 'utf-8').includes('STACK_LANG');\n const hasHealthRoute = existsSync(join(appDir, 'src', 'app', 'health'))\n || existsSync(join(appDir, 'backend', 'src'));\n const healthPath = (isBoilerplate || hasHealthRoute) ? '/health' : '/';\n return `http://127.0.0.1:${healthPort}${basePath}${healthPath}`;\n}\n\nasync function _pollHealth(url: string, maxMs = 120_000, job?: AppJob): Promise<void> {\n const deadline = Date.now() + maxMs;\n let attempt = 0;\n while (Date.now() < deadline) {\n attempt++;\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(5000) });\n if (res.ok) {\n if (job) appendLog(job, `[health] ✓ ${url} → ${res.status} (attempt ${attempt})`);\n return;\n }\n if (job) appendLog(job, `[health] ${url} → ${res.status} (attempt ${attempt})`);\n } catch {\n if (job && attempt % 3 === 1) appendLog(job, `[health] waiting... ${url} (attempt ${attempt})`);\n }\n await new Promise((r) => setTimeout(r, 3000));\n }\n if (job) appendLog(job, `[health] ✗ timeout after ${maxMs / 1000}s`);\n throw new Error(`Health check timed out after ${maxMs / 1000}s: ${url}`);\n}\n\n/**\n * Run `docker compose up -d --build` with stdout/stderr streamed to job logs.\n */\nasync function _dockerComposeUp(cwd: string, job: AppJob): Promise<void> {\n appendLog(job, `[docker] $ docker compose up -d --build`);\n appendLog(job, `[docker] cwd: ${cwd}`);\n const proc = execa('docker', ['compose', 'up', '-d', '--build'], { cwd, reject: false });\n proc.stdout?.on('data', (chunk: Buffer) => {\n for (const line of chunk.toString().split('\\n').filter(Boolean)) {\n appendLog(job, `[docker] ${line}`);\n }\n });\n proc.stderr?.on('data', (chunk: Buffer) => {\n for (const line of chunk.toString().split('\\n').filter(Boolean)) {\n appendLog(job, `[docker] ${line}`);\n }\n });\n const result = await proc;\n if (result.exitCode !== 0) {\n appendLog(job, `[docker] ✗ exit code ${result.exitCode}`);\n throw new Error(`Command failed with exit code ${result.exitCode}: docker compose up -d --build\\n${result.stderr}`);\n }\n appendLog(job, `[docker] ✓ containers started`);\n}\n\n// ---------------------------------------------------------------------------\n// Public: createApp\n// ---------------------------------------------------------------------------\n\nexport async function createApp(opts: CreateAppOptions): Promise<string> {\n const job = newJob(opts.appName, ['Validating', 'Gitea setup', 'Gitea repo', 'Git push', 'Docker up', 'Health check']);\n jobs.set(job.jobId, job);\n\n // Run async — caller polls via getJobStatus\n setImmediate(() => void _runCreateApp(job, opts));\n\n return job.jobId;\n}\n\nasync function _runCreateApp(job: AppJob, opts: CreateAppOptions): Promise<void> {\n try {\n const ctx = resolveContext();\n const appsJson = resolveAppsJsonPath();\n\n // Step 0: Validating — mode-specific pre-checks\n setStep(job, 0, 'running');\n if (opts.mode === 'boilerplate') {\n if (!opts.stackId) throw new Error('stackId is required for boilerplate mode');\n const metas = readBoilerplateMeta(ctx.projectPath);\n const meta = metas.find((m) => m.stackId === opts.stackId);\n if (meta) {\n // Installed locally — use fast local copy path\n (opts as CreateAppOptions & { _meta?: unknown })._meta = meta;\n } else {\n // Not installed locally — fall back to fresh clone from catalog\n appendLog(job, `[info] Stack \"${opts.stackId}\" not installed locally — cloning fresh from catalog`);\n (opts as CreateAppOptions & { _resolvedStackId?: string })._resolvedStackId = opts.stackId;\n }\n } else if (opts.mode === 'git-clone') {\n if (!opts.gitUrl) throw new Error('gitUrl is required for Git Clone mode');\n } else if (opts.mode === 'new-project') {\n const { resolveStackId } = await import('../config/frameworks.js');\n const stackId = resolveStackId(opts.language ?? 'nodejs', opts.frameworkId ?? 'express');\n if (!stackId) throw new Error(`Unknown stack: ${opts.language}/${opts.frameworkId}`);\n (opts as CreateAppOptions & { _resolvedStackId?: string })._resolvedStackId = stackId;\n }\n setStep(job, 0, 'done');\n\n // Step 1: Gitea setup — ensure token exists, auto-fix mustChangePassword if needed\n setStep(job, 1, 'running');\n const gitea = new GiteaClient({\n baseUrl: ctx.giteaBaseUrl,\n username: ctx.giteaUser,\n password: ctx.giteaPassword,\n tokenPath: GITEA_TOKEN_PATH,\n });\n const giteaPrep = await gitea.prepare();\n setStep(job, 1, 'done', giteaPrep.message);\n\n if (opts.mode === 'boilerplate' && (opts as CreateAppOptions & { _meta?: unknown })._meta) {\n await _createModeA(job, opts, ctx, gitea, appsJson);\n } else if (opts.mode === 'git-clone') {\n await _createModeB(job, opts, ctx, gitea, appsJson);\n } else {\n // new-project OR boilerplate fallback (stack not installed locally)\n await _createModeC(job, opts, ctx, gitea, appsJson);\n }\n\n job.status = 'done';\n } catch (err) {\n job.status = 'failed';\n job.error = err instanceof Error ? err.message : String(err);\n for (const step of job.steps) {\n if (step.status === 'running' || step.status === 'pending') step.status = 'failed';\n }\n }\n}\n\nasync function _createModeA(\n job: AppJob,\n opts: CreateAppOptions,\n ctx: AppContext,\n gitea: GiteaClient,\n appsJson: string,\n): Promise<void> {\n // Step 0 already done in _runCreateApp — retrieve validated meta\n const meta = (opts as CreateAppOptions & { _meta?: BoilerplateMeta })._meta!;\n const port = opts.port ?? meta.port ?? parseInt(meta.backendUrl.split(':').pop() ?? '8080', 10);\n\n // Step 2: Gitea repo\n setStep(job, 2, 'running', `checking ${ctx.giteaUser}/${opts.appName}`);\n const alreadyExists = await gitea.repoExists(opts.appName);\n let cloneUrl: string;\n if (!alreadyExists) {\n setStep(job, 2, 'running', `creating ${ctx.giteaUser}/${opts.appName}`);\n cloneUrl = await gitea.createRepo(opts.appName, `Brewnet app: ${opts.appName}`);\n } else {\n cloneUrl = `${ctx.giteaBaseUrl}/${ctx.giteaUser}/${opts.appName}.git`;\n }\n setStep(job, 2, 'done');\n\n // Step 3: Git remote + push\n setStep(job, 3, 'running', `pushing HEAD:main → ${ctx.giteaUser}/${opts.appName}`);\n // Boilerplates are cloned --depth 1; Gitea rejects shallow pushes to empty repos.\n // Unshallow first (try origin), fall back to a fresh git init if origin is unreachable.\n const shallowCheck = await execa('git', ['rev-parse', '--is-shallow-repository'], { cwd: meta.appDir }).catch(() => ({ stdout: 'false' }));\n if (shallowCheck.stdout.trim() === 'true') {\n await execa('git', ['fetch', '--unshallow', 'origin'], { cwd: meta.appDir }).catch(async () => {\n const { reinitGit } = await import('./boilerplate-manager.js');\n await reinitGit(meta.appDir);\n });\n }\n const authedUrl = gitea.authedCloneUrl(cloneUrl);\n await execa('git', ['remote', 'add', 'brewnet', authedUrl], { cwd: meta.appDir }).catch(() => {\n return execa('git', ['remote', 'set-url', 'brewnet', authedUrl], { cwd: meta.appDir });\n });\n await execa('git', ['push', 'brewnet', 'HEAD:main', '--force'], { cwd: meta.appDir });\n setStep(job, 3, 'done');\n\n // Step 4: Docker up\n setStep(job, 4, 'running', 'docker compose up --build');\n ensureComposeFile(meta.appDir, opts.appName, port, job);\n await _injectQuickTunnelIfNeeded(meta.appDir, opts.appName, port);\n await _dockerComposeUp(meta.appDir, job);\n setStep(job, 4, 'done', 'containers started');\n\n // Step 5: Health check — accounts for Next.js basePath\n setStep(job, 5, 'running');\n const healthUrlA = _buildHealthUrl(meta.appDir, port);\n setStep(job, 5, 'running', `polling ${healthUrlA}`);\n await _pollHealth(healthUrlA, 120_000, job);\n setStep(job, 5, 'done');\n\n // Register\n addApp(appsJson, {\n name: opts.appName,\n mode: 'boilerplate',\n stackId: opts.stackId,\n appDir: meta.appDir,\n lang: meta.lang,\n framework: meta.frameworkId,\n port,\n giteaRepoUrl: `${ctx.giteaBaseUrl}/${ctx.giteaUser}/${opts.appName}`,\n status: 'running',\n createdAt: new Date().toISOString(),\n });\n // Register Gitea webhook for auto-deploy (non-blocking)\n await setupWebhook(opts.appName, 'http://localhost:8088/api/deploy/hook').catch((e: unknown) => {\n console.warn('[webhook] registration failed (non-critical):', e instanceof Error ? e.message : String(e));\n });\n}\n\nasync function _createModeB(\n job: AppJob,\n opts: CreateAppOptions,\n ctx: AppContext,\n gitea: GiteaClient,\n appsJson: string,\n): Promise<void> {\n // Step 0 already done in _runCreateApp\n const port = opts.port ?? 8080;\n const appDir = join(ctx.projectPath, 'apps', opts.appName);\n\n // Step 2: Clone external repo + create Gitea repo\n setStep(job, 2, 'running', 'Cloning external repository...');\n const { reinitGit: reinitGitB } = await import('./boilerplate-manager.js');\n // Clean existing directory from a previous failed run\n const { rmSync } = await import('node:fs');\n if (existsSync(appDir)) {\n rmSync(appDir, { recursive: true, force: true });\n }\n const cloneArgs = ['clone', '--depth', '1'];\n if (opts.branch) cloneArgs.push('-b', opts.branch);\n cloneArgs.push(opts.gitUrl!, appDir);\n await execa('git', cloneArgs);\n // Inject user-specified ports into .env so docker-compose picks them up\n // (prevents \"port already allocated\" when default 8080/3000 are in use)\n const envExPath = join(appDir, '.env.example');\n const envPath = join(appDir, '.env');\n if (existsSync(envExPath)) {\n const { findFreePort: findFreePortB } = await import('./boilerplate-manager.js');\n let envContent = readFileSync(envExPath, 'utf-8');\n envContent = envContent.replace(/^BACKEND_PORT=.*/m, `BACKEND_PORT=${port}`);\n // Start frontend port search AFTER the backend port to avoid collision\n const fePort = await findFreePortB(port + 1);\n envContent = envContent.replace(/^FRONTEND_PORT=.*/m, `FRONTEND_PORT=${fePort}`);\n writeFileSync(envPath, envContent, 'utf-8');\n } else if (existsSync(join(appDir, '.env'))) {\n let envContent = readFileSync(join(appDir, '.env'), 'utf-8');\n envContent = envContent.replace(/^BACKEND_PORT=.*/m, `BACKEND_PORT=${port}`);\n writeFileSync(join(appDir, '.env'), envContent, 'utf-8');\n }\n await reinitGitB(appDir);\n const alreadyExists = await gitea.repoExists(opts.appName);\n const cloneUrl = alreadyExists\n ? `${ctx.giteaBaseUrl}/${ctx.giteaUser}/${opts.appName}.git`\n : await gitea.createRepo(opts.appName, `Brewnet app: ${opts.appName}`);\n setStep(job, 2, 'done');\n\n setStep(job, 3, 'running');\n const authedUrl = gitea.authedCloneUrl(cloneUrl);\n await execa('git', ['remote', 'add', 'brewnet', authedUrl], { cwd: appDir });\n await execa('git', ['push', 'brewnet', 'HEAD:main', '--force'], { cwd: appDir });\n setStep(job, 3, 'done');\n\n // Git Clone mode: skip Docker up + Health check.\n // Clone + Gitea repo creation is sufficient — user deploys separately.\n // If docker-compose.yml exists (e.g. brewnet boilerplate), auto-start.\n const hasCompose = existsSync(join(appDir, 'docker-compose.yml')) || existsSync(join(appDir, 'compose.yml'));\n if (hasCompose) {\n setStep(job, 4, 'running', 'docker compose up --build');\n await _injectQuickTunnelIfNeeded(appDir, opts.appName, port);\n await _dockerComposeUp(appDir, job);\n setStep(job, 4, 'done', 'containers started');\n\n setStep(job, 5, 'running');\n const healthUrlB = _buildHealthUrl(appDir, port);\n setStep(job, 5, 'running', `polling ${healthUrlB}`);\n await _pollHealth(healthUrlB, 120_000, job);\n setStep(job, 5, 'done');\n } else {\n setStep(job, 4, 'done', 'skipped — no docker-compose.yml');\n setStep(job, 5, 'done', 'skipped — deploy separately');\n appendLog(job, '[clone] Gitea push completed — no docker-compose.yml, skipping Docker up');\n }\n\n addApp(appsJson, {\n name: opts.appName,\n mode: 'git-clone',\n sourceUrl: opts.gitUrl,\n appDir,\n port,\n giteaRepoUrl: `${ctx.giteaBaseUrl}/${ctx.giteaUser}/${opts.appName}`,\n status: hasCompose ? 'running' : 'stopped',\n createdAt: new Date().toISOString(),\n });\n}\n\nasync function _createModeC(\n job: AppJob,\n opts: CreateAppOptions,\n ctx: AppContext,\n gitea: GiteaClient,\n appsJson: string,\n): Promise<void> {\n const { cloneStack, generateEnv, reinitGit, findFreePort } = await import('./boilerplate-manager.js');\n const { getStackById } = await import('../config/stacks.js');\n\n // Step 0 already done in _runCreateApp — retrieve resolved stackId\n const stackId = (opts as CreateAppOptions & { _resolvedStackId?: string })._resolvedStackId!;\n const requestedPort = opts.port ?? 8080;\n const appDir = join(ctx.projectPath, 'apps', opts.appName);\n\n // Clone and scaffold (visible as part of Gitea repo step context)\n await cloneStack(stackId, appDir);\n // Auto-detect free host port starting from the requested port to prevent\n // \"port already allocated\" errors when other apps occupy the requested port.\n const port = await findFreePort(requestedPort);\n const stackInfo = getStackById(stackId);\n const frontendPort = (stackInfo && !stackInfo.isUnified) ? await findFreePort(port + 1) : undefined;\n generateEnv(appDir, stackId, 'sqlite3', { hostPort: port, frontendPort });\n await reinitGit(appDir);\n\n setStep(job, 2, 'running');\n const cloneUrl = await gitea.createRepo(opts.appName, `Brewnet app: ${opts.appName}`);\n setStep(job, 2, 'done');\n\n setStep(job, 3, 'running');\n const authedUrl = gitea.authedCloneUrl(cloneUrl);\n await execa('git', ['remote', 'add', 'brewnet', authedUrl], { cwd: appDir });\n await execa('git', ['push', 'brewnet', 'HEAD:main', '--force'], { cwd: appDir });\n setStep(job, 3, 'done');\n\n setStep(job, 4, 'running', 'docker compose up --build');\n ensureComposeFile(appDir, opts.appName, port, job);\n await _injectQuickTunnelIfNeeded(appDir, opts.appName, port);\n await _dockerComposeUp(appDir, job);\n setStep(job, 4, 'done', 'containers started');\n\n setStep(job, 5, 'running');\n const healthUrlC = _buildHealthUrl(appDir, port);\n setStep(job, 5, 'running', `polling ${healthUrlC}`);\n await _pollHealth(healthUrlC, 120_000, job);\n setStep(job, 5, 'done');\n\n addApp(appsJson, {\n name: opts.appName,\n mode: opts.mode === 'boilerplate' ? 'boilerplate' : 'new-project',\n stackId,\n appDir,\n lang: opts.language ?? stackInfo?.language,\n framework: opts.frameworkId ?? stackInfo?.framework,\n port,\n giteaRepoUrl: `${ctx.giteaBaseUrl}/${ctx.giteaUser}/${opts.appName}`,\n status: 'running',\n createdAt: new Date().toISOString(),\n });\n}\n\nexport async function startApp(appName: string): Promise<void> {\n const appsJson = resolveAppsJsonPath();\n const apps = readApps(appsJson);\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n await execa('docker', ['compose', 'up', '-d'], { cwd: app.appDir });\n updateApp(appsJson, appName, { status: 'running' });\n}\n\nexport async function stopApp(appName: string): Promise<void> {\n const appsJson = resolveAppsJsonPath();\n const apps = readApps(appsJson);\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n await execa('docker', ['compose', 'down'], { cwd: app.appDir });\n updateApp(appsJson, appName, { status: 'stopped' });\n}\n\nexport async function removeApp(appName: string): Promise<void> {\n const appsJson = resolveAppsJsonPath();\n const apps = readApps(appsJson);\n const app = apps.find((a) => a.name === appName);\n if (!app) throw new Error(`App \"${appName}\" not found`);\n await execa('docker', ['compose', 'down', '--volumes'], { cwd: app.appDir }).catch((e: unknown) => {\n console.warn('[removeApp] docker compose down failed:', e instanceof Error ? e.message : String(e));\n });\n registryRemoveApp(appsJson, appName);\n}\n","// packages/cli/src/services/gitea-client.ts\nimport { existsSync, readFileSync, writeFileSync, chmodSync, mkdirSync, unlinkSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport type { GitRepoEntry } from '../types/app-entry.js';\n\nexport interface GiteaClientConfig {\n /** Full base URL without trailing slash, e.g. \"http://localhost/git\" (via Traefik) */\n baseUrl: string;\n username: string;\n password: string;\n /** Path to persist the API token, e.g. ~/.brewnet/gitea-token */\n tokenPath: string;\n}\n\nexport class GiteaClient {\n private config: GiteaClientConfig;\n\n constructor(config: GiteaClientConfig) {\n this.config = config;\n }\n\n // ---------------------------------------------------------------------------\n // Token management\n // ---------------------------------------------------------------------------\n\n /**\n * Create a Gitea API token via Basic Auth.\n * If the admin account has mustChangePassword=true (403), auto-fixes via docker exec and retries.\n * Saves the token to tokenPath on success.\n */\n private async _createToken(): Promise<{ wasFixed: boolean }> {\n const { tokenPath, baseUrl, username, password } = this.config;\n const basic = Buffer.from(`${username}:${password}`).toString('base64');\n\n const makeRequest = () =>\n fetch(`${baseUrl}/api/v1/users/${username}/tokens`, {\n method: 'POST',\n headers: { Authorization: `Basic ${basic}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: `brewnet-${Date.now()}`,\n scopes: ['write:repository', 'read:repository', 'write:user', 'read:user'],\n }),\n signal: AbortSignal.timeout(8000),\n });\n\n let res = await makeRequest();\n let wasFixed = false;\n\n if (!res.ok) {\n const body = await res.text();\n if (res.status === 403 && body.includes('must change')) {\n // Auto-fix: reset mustChangePassword via docker exec, then retry\n try {\n execSync(\n `docker exec -u git brewnet-gitea gitea admin user change-password` +\n ` --username ${username} --password ${password} --must-change-password=false`,\n { stdio: 'pipe' },\n );\n } catch (e) {\n const stderr = (e as { stderr?: Buffer }).stderr?.toString().trim() ?? String(e);\n throw new Error(\n `Gitea admin requires password change — auto-fix failed:\\n ${stderr}\\n` +\n ` Manual fix: docker exec -u git brewnet-gitea gitea admin user change-password` +\n ` --username ${username} --password <password> --must-change-password=false`,\n );\n }\n wasFixed = true;\n res = await makeRequest();\n if (!res.ok) {\n throw new Error(\n `Gitea token creation failed after auto-fix: ${res.status} ${await res.text()}`,\n );\n }\n } else {\n throw new Error(`Gitea token creation failed: ${res.status} ${body}`);\n }\n }\n\n const data = (await res.json()) as { sha1: string };\n mkdirSync(dirname(tokenPath), { recursive: true });\n writeFileSync(tokenPath, data.sha1, 'utf-8');\n chmodSync(tokenPath, 0o600);\n return { wasFixed };\n }\n\n /**\n * Explicit setup step — call once before any API operations.\n * Validates any cached token; deletes and re-creates if stale (401).\n * Returns what happened so the caller can surface it in job step logs.\n */\n async prepare(): Promise<{ autoFixed: boolean; message: string }> {\n const { tokenPath, baseUrl } = this.config;\n if (existsSync(tokenPath)) {\n // Validate cached token — it may be stale if Gitea was reset or re-installed\n const token = readFileSync(tokenPath, 'utf-8').trim();\n try {\n const check = await fetch(`${baseUrl}/api/v1/user`, {\n headers: { Authorization: `token ${token}` },\n signal: AbortSignal.timeout(8000),\n });\n if (check.status !== 401) {\n return { autoFixed: false, message: 'token cached' };\n }\n // Token is stale — delete and fall through to re-create\n unlinkSync(tokenPath);\n } catch {\n // Network error — assume token is still valid, let API calls fail naturally\n return { autoFixed: false, message: 'token cached (network check skipped)' };\n }\n }\n const { wasFixed } = await this._createToken();\n return {\n autoFixed: wasFixed,\n message: wasFixed\n ? 'mustChangePassword was set — auto-fixed via docker exec; token created'\n : 'token created',\n };\n }\n\n private async ensureToken(): Promise<string> {\n const { tokenPath } = this.config;\n if (existsSync(tokenPath)) {\n return readFileSync(tokenPath, 'utf-8').trim();\n }\n await this._createToken();\n return readFileSync(tokenPath, 'utf-8').trim();\n }\n\n private async authHeaders(): Promise<Record<string, string>> {\n return {\n Authorization: `token ${await this.ensureToken()}`,\n 'Content-Type': 'application/json',\n };\n }\n\n // ---------------------------------------------------------------------------\n // Repository operations\n // ---------------------------------------------------------------------------\n\n async repoExists(name: string): Promise<boolean> {\n const { baseUrl, username } = this.config;\n const res = await fetch(\n `${baseUrl}/api/v1/repos/${username}/${name}`,\n { headers: await this.authHeaders() },\n );\n return res.status === 200;\n }\n\n /** Returns true if the repo exists but has no commits (empty: true from Gitea API). */\n async repoIsEmpty(name: string): Promise<boolean> {\n const { baseUrl, username } = this.config;\n const res = await fetch(\n `${baseUrl}/api/v1/repos/${username}/${name}`,\n { headers: await this.authHeaders() },\n );\n if (res.status !== 200) return false;\n const data = await res.json() as { empty?: boolean };\n return data.empty === true;\n }\n\n /** Creates a private repo and returns the clone URL. */\n async createRepo(name: string, description = ''): Promise<string> {\n const { baseUrl } = this.config;\n const res = await fetch(`${baseUrl}/api/v1/user/repos`, {\n method: 'POST',\n headers: await this.authHeaders(),\n body: JSON.stringify({ name, description, private: false, auto_init: false }),\n });\n\n if (!res.ok) {\n const body = await res.text();\n // 409 \"repository already exists\" — previous partial creation left the repo.\n // Fetch the existing repo's clone_url and continue.\n if (res.status === 409) {\n const existing = await fetch(`${baseUrl}/api/v1/repos/${this.config.username}/${name}`, {\n headers: await this.authHeaders(),\n });\n if (existing.ok) {\n const data = (await existing.json()) as { clone_url: string };\n return data.clone_url;\n }\n }\n // Gitea 500 \"repository files already exist\" — DB record was deleted\n // (e.g. by uninstall) but bare git files remain on disk in the volume.\n // Delete the orphan files and retry once.\n if (res.status === 500 && body.includes('files already exist')) {\n await this.deleteRepo(name).catch(() => { /* may 404 — that's fine */ });\n const retry = await fetch(`${baseUrl}/api/v1/user/repos`, {\n method: 'POST',\n headers: await this.authHeaders(),\n body: JSON.stringify({ name, description, private: false, auto_init: false }),\n });\n if (!retry.ok) {\n throw new Error(`Gitea createRepo retry failed: ${retry.status} ${await retry.text()}`);\n }\n const retryData = (await retry.json()) as { clone_url: string };\n return retryData.clone_url;\n }\n throw new Error(`Gitea createRepo failed: ${res.status} ${body}`);\n }\n\n const data = (await res.json()) as { clone_url: string };\n return data.clone_url;\n }\n\n /** Patch a repo from private to public visibility. No-op if already public. */\n async makeRepoPublic(name: string): Promise<void> {\n const { baseUrl, username } = this.config;\n const res = await fetch(`${baseUrl}/api/v1/repos/${username}/${name}`, {\n method: 'PATCH',\n headers: await this.authHeaders(),\n body: JSON.stringify({ private: false }),\n });\n if (!res.ok) throw new Error(`Gitea makeRepoPublic failed: ${res.status} ${await res.text()}`);\n }\n\n async deleteRepo(name: string): Promise<void> {\n const { baseUrl, username } = this.config;\n await fetch(`${baseUrl}/api/v1/repos/${username}/${name}`, {\n method: 'DELETE',\n headers: await this.authHeaders(),\n });\n }\n\n /** Returns all repos accessible to the authenticated user. */\n async listRepos(): Promise<GitRepoEntry[]> {\n const { baseUrl } = this.config;\n const res = await fetch(`${baseUrl}/api/v1/user/repos`, {\n headers: await this.authHeaders(),\n signal: AbortSignal.timeout(8000),\n });\n if (!res.ok) {\n throw new Error(`Gitea listRepos failed: ${res.status} ${await res.text()}`);\n }\n return (await res.json()) as GitRepoEntry[];\n }\n\n /** Fetch a single repo's detail (includes default_branch, ssh_url). */\n async getRepo(name: string): Promise<{\n id: number; name: string; clone_url: string; ssh_url: string;\n html_url: string; description: string; private: boolean; default_branch: string;\n }> {\n const { baseUrl, username } = this.config;\n const res = await fetch(`${baseUrl}/api/v1/repos/${username}/${name}`, {\n headers: await this.authHeaders(),\n });\n if (!res.ok) throw new Error(`Gitea getRepo failed: ${res.status} ${await res.text()}`);\n return res.json() as Promise<{\n id: number; name: string; clone_url: string; ssh_url: string;\n html_url: string; description: string; private: boolean; default_branch: string;\n }>;\n }\n\n /** Get the latest commit on a branch. Returns null for empty repos. */\n async getLatestCommit(\n repoName: string,\n branch: string,\n ): Promise<{ hash: string; shortHash: string; message: string; date: string } | null> {\n const { baseUrl, username } = this.config;\n const res = await fetch(\n `${baseUrl}/api/v1/repos/${username}/${repoName}/commits?sha=${encodeURIComponent(branch)}&limit=1`,\n { headers: await this.authHeaders() },\n );\n if (!res.ok) return null;\n const commits = (await res.json()) as Array<{ sha: string; commit: { message: string; committer: { date: string } } }>;\n if (!commits.length) return null;\n const c = commits[0]!;\n return {\n hash: c.sha,\n shortHash: c.sha.slice(0, 7),\n message: c.commit.message.split('\\n')[0]!,\n date: c.commit.committer.date,\n };\n }\n\n /** Register a push webhook on the repo. */\n async createWebhook(repoName: string, webhookUrl: string, secret: string): Promise<void> {\n const { baseUrl, username } = this.config;\n const res = await fetch(`${baseUrl}/api/v1/repos/${username}/${repoName}/hooks`, {\n method: 'POST',\n headers: await this.authHeaders(),\n body: JSON.stringify({\n type: 'gitea',\n config: { url: webhookUrl, content_type: 'json', secret },\n events: ['push'],\n active: true,\n }),\n });\n if (!res.ok) throw new Error(`Gitea createWebhook failed: ${res.status} ${await res.text()}`);\n }\n\n /** Returns branch names for a repo. Falls back to empty array on error. */\n async listBranches(repoName: string): Promise<string[]> {\n const { baseUrl, username } = this.config;\n const res = await fetch(\n `${baseUrl}/api/v1/repos/${username}/${repoName}/branches?limit=50`,\n { headers: await this.authHeaders(), signal: AbortSignal.timeout(8000) },\n );\n if (!res.ok) return [];\n const data = await res.json() as Array<{ name: string }>;\n return data.map((b) => b.name);\n }\n\n /** URL suitable for git remote add — includes credentials in URL (stored in .git/config which is chmod 600). */\n authedCloneUrl(cloneUrl: string): string {\n const { username, password } = this.config;\n // Percent-encode special chars so git URL parser handles them correctly\n const encUser = encodeURIComponent(username);\n const encPass = encodeURIComponent(password);\n return cloneUrl.replace('http://', `http://${encUser}:${encPass}@`);\n }\n}\n"],"mappings":"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","names":["join","existsSync","readFileSync","writeFileSync","homedir","existsSync","readFileSync","writeFileSync","data","existsSync","readFileSync","writeFileSync","getStackById","removeApp","join","existsSync","readFileSync","resolve","homedir","writeFileSync","removeApp","docker","port"]}