@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/index.ts","../src/commands/init.ts","../src/wizard/steps/admin-setup.ts","../src/wizard/steps/system-check.ts","../src/services/system-checker.ts","../src/services/docker-installer.ts","../src/utils/port-utils.ts","../src/wizard/steps/project-setup.ts","../src/utils/validation.ts","../src/wizard/steps/server-components.ts","../src/utils/password.ts","../src/utils/resources.ts","../src/wizard/steps/dev-stack.ts","../src/wizard/steps/domain-network.ts","../src/utils/tunnel-logger.ts","../src/services/quick-tunnel.ts","../src/wizard/steps/review.ts","../src/wizard/navigation.ts","../src/wizard/steps/generate.ts","../src/utils/service-verifier.ts","../src/services/env-generator.ts","../src/services/credential-manager.ts","../src/services/config-generator.ts","../src/services/health-checker.ts","../src/services/uninstall-manager.ts","../src/wizard/steps/complete.ts","../src/services/admin-launcher.ts","../src/wizard/run-minimal-install.ts","../src/commands/status.ts","../src/services/docker-manager.ts","../src/commands/add.ts","../src/commands/remove.ts","../src/commands/up.ts","../src/commands/down.ts","../src/commands/logs.ts","../src/commands/backup.ts","../src/commands/restore.ts","../src/commands/admin.ts","../src/commands/shutdown.ts","../src/commands/uninstall.ts","../src/commands/domain.ts","../src/commands/create-app.ts"],"sourcesContent":["/**\n * Brewnet CLI — Entry Point\n *\n * Exports `createProgram()` which builds and returns the Commander.js\n * program with all subcommands registered. The program is NOT parsed\n * here so that tests can inspect it without triggering side effects.\n *\n * @module cli/index\n */\n\nimport { Command } from 'commander';\n\ndeclare const __CLI_VERSION__: string;\nimport { registerInitCommand } from './commands/init.js';\nimport { registerStatusCommand } from './commands/status.js';\nimport { registerAddCommand } from './commands/add.js';\nimport { registerRemoveCommand } from './commands/remove.js';\nimport { registerUpCommand } from './commands/up.js';\nimport { registerDownCommand } from './commands/down.js';\nimport { registerLogsCommand } from './commands/logs.js';\nimport { registerBackupCommand } from './commands/backup.js';\nimport { registerRestoreCommand } from './commands/restore.js';\nimport { registerAdminCommand } from './commands/admin.js';\nimport { registerShutdownCommand } from './commands/shutdown.js';\nimport { registerUninstallCommand } from './commands/uninstall.js';\nimport { registerDomainCommand } from './commands/domain.js';\nimport { registerCreateAppCommand } from './commands/create-app.js';\n\n/**\n * Build and return a fully configured Commander.js program with all\n * Brewnet CLI subcommands registered.\n *\n * The caller is responsible for invoking `program.parse()` when ready\n * to execute (this keeps the function pure and testable).\n */\nexport function createProgram(): Command {\n const program = new Command();\n\n program\n .name('brewnet')\n .description('Your Home Server, Brewed Fresh')\n .version(__CLI_VERSION__)\n .showHelpAfterError('(run \"brewnet --help\" for usage information)');\n\n // Register all subcommands\n registerInitCommand(program);\n registerStatusCommand(program);\n registerAddCommand(program);\n registerRemoveCommand(program);\n registerUpCommand(program);\n registerDownCommand(program);\n registerLogsCommand(program);\n registerBackupCommand(program);\n registerRestoreCommand(program);\n registerAdminCommand(program);\n registerShutdownCommand(program);\n registerUninstallCommand(program);\n registerDomainCommand(program);\n registerCreateAppCommand(program);\n\n return program;\n}\n\n// ---------------------------------------------------------------------------\n// Direct execution (not imported as a module)\n// ---------------------------------------------------------------------------\n\n// Detect if this file is being run directly (not imported by tests).\n// Using import.meta.url to check if this is the entry point.\nconst isDirectRun =\n typeof process !== 'undefined' &&\n process.argv[1] &&\n import.meta.url.endsWith(process.argv[1].replace(/\\\\/g, '/'));\n\nif (isDirectRun) {\n const program = createProgram();\n program.parse(process.argv);\n}\n","/**\n * brewnet init — Interactive setup wizard (T047, T073, T074)\n *\n * Runs the 7-step wizard to initialize a new Brewnet project.\n * Supports --config for pre-populated config and --non-interactive\n * for CI/scripted usage.\n *\n * Steps:\n * Step 0: System Check\n * Step 1: Project Setup\n * Step 2: Server Components (T052-T058)\n * Step 3: Dev Stack & Runtime (T081)\n * Step 4: Domain & Network (T082)\n * Step 5: Review & Confirm (T070)\n * Step 6: Generate & Start (T071)\n * Step 7: Complete (T072)\n *\n * @module commands/init\n */\n\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { createRequire } from 'node:module';\nimport { fileURLToPath } from 'node:url';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { select } from '@inquirer/prompts';\nimport { runAdminSetupStep } from '../wizard/steps/admin-setup.js';\nimport { runSystemCheckStep } from '../wizard/steps/system-check.js';\nimport { runProjectSetupStep } from '../wizard/steps/project-setup.js';\nimport { runServerComponentsStep } from '../wizard/steps/server-components.js';\nimport { runDevStackStep } from '../wizard/steps/dev-stack.js';\nimport { runDomainNetworkStep } from '../wizard/steps/domain-network.js';\nimport { runReviewStep, importConfig } from '../wizard/steps/review.js';\nimport { runGenerateStep } from '../wizard/steps/generate.js';\nimport type { GenerateResult } from '../wizard/steps/generate.js';\nimport { cleanupForRestart } from '../services/uninstall-manager.js';\nimport { runCompleteStep } from '../wizard/steps/complete.js';\nimport { runMinimalInstall } from '../wizard/run-minimal-install.js';\nimport {\n WizardNavigation,\n WizardStep,\n setupCancelHandler,\n} from '../wizard/navigation.js';\nimport {\n createState,\n saveState,\n} from '../wizard/state.js';\nimport type { WizardState } from '@brewnet/shared';\nimport { generatePassword } from '../utils/password.js';\n\n// Read package.json at runtime from multiple candidate paths (handles both\n// source tree `src/commands/` and bundled `dist/` locations).\nconst PKG_CANDIDATES = [\n new URL('../../package.json', import.meta.url), // src/commands/ → packages/cli/\n new URL('../package.json', import.meta.url), // dist/ → packages/cli/\n new URL('../../../package.json', import.meta.url), // fallback\n];\nlet PKG_VERSION = '0.0.1';\nlet PKG_LICENSE = 'Apache-2.0';\nfor (const candidate of PKG_CANDIDATES) {\n try {\n const _require = createRequire(import.meta.url);\n const pkg = _require(fileURLToPath(candidate)) as { version?: string; license?: string };\n if (pkg.version) { PKG_VERSION = pkg.version; PKG_LICENSE = pkg.license ?? PKG_LICENSE; break; }\n } catch { /* try next */ }\n}\n\n// ---------------------------------------------------------------------------\n// Command Registration\n// ---------------------------------------------------------------------------\n\nexport interface InitOptions {\n config?: string;\n nonInteractive?: boolean;\n open?: boolean;\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command('init')\n .description('Interactive setup wizard')\n .option('-c, --config <path>', 'Path to a JSON config file for pre-populated values')\n .option('--non-interactive', 'Run in non-interactive mode (requires --config)')\n .option('--no-open', 'Skip auto-opening the status page in browser after setup')\n .action(async (options: InitOptions) => {\n await runInitWizard(options);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Wizard Orchestrator\n// ---------------------------------------------------------------------------\n\n/**\n * Main wizard orchestrator.\n *\n * Manages step navigation, state persistence, resume, and cancellation.\n * Supports --config flag to pre-populate state and --non-interactive\n * to skip prompts.\n */\nasync function runInitWizard(options: InitOptions = {}): Promise<void> {\n // -----------------------------------------------------------------------\n // 1. Display welcome banner\n // -----------------------------------------------------------------------\n console.log();\n console.log(chalk.cyan([\n ' ____ _ ) ) ) ',\n ' | __ ) _ __ _____ ___ __ ___| |_ ( ( ( ',\n ' | _ \\\\| \\'__/ _ \\\\ \\\\ /\\\\ / / \\'_ \\\\ / _ \\\\ __| __________',\n ' | |_) | | | __/\\\\ V V /| | | | __/ |_ | |]',\n ' |____/|_| \\\\___| \\\\_/\\\\_/ |_| |_|\\\\___|\\\\__| \\\\________/ ',\n ].join('\\n')));\n console.log();\n console.log(chalk.hex('#c8a96e').bold(' ☕ Set up your entire home server in the time it takes to brew a coffee.'));\n console.log(chalk.bold(' Brewnet') + chalk.dim(' — One command. Your entire server stack, on tap. Just brew it!'));\n console.log(chalk.dim(` v${PKG_VERSION} • ${PKG_LICENSE} License`));\n console.log(chalk.dim(' https://brewnet.dev • https://github.com/claude-code-expert/brewnet'));\n console.log(chalk.dim(' brewnet.dev@gmail.com'));\n console.log();\n\n // -----------------------------------------------------------------------\n // 2. Set up navigation and state\n // -----------------------------------------------------------------------\n const nav = new WizardNavigation(WizardStep.AdminSetup);\n let state: WizardState = createState();\n\n // -----------------------------------------------------------------------\n // 2a. Handle --config flag (T073)\n // -----------------------------------------------------------------------\n if (options.config) {\n const configPath = resolve(options.config);\n\n if (!existsSync(configPath)) {\n console.log(chalk.red(` Config file not found: ${configPath}`));\n console.log();\n return;\n }\n\n try {\n state = importConfig(configPath);\n\n // Re-generate passwords that were stripped during export\n if (!state.admin.password) {\n state.admin.password = generatePassword(20);\n }\n if (state.servers.dbServer.enabled && !state.servers.dbServer.dbPassword) {\n state.servers.dbServer.dbPassword = generatePassword(16);\n }\n\n console.log(chalk.green(` Loaded config from: ${configPath}`));\n console.log(chalk.dim(' Passwords have been auto-generated for missing secrets.'));\n console.log();\n } catch (err) {\n console.log(chalk.red(` Failed to load config: ${configPath}`));\n if (err instanceof Error) {\n console.log(chalk.dim(` ${err.message}`));\n }\n console.log();\n return;\n }\n }\n\n // -----------------------------------------------------------------------\n // 2b. Handle --non-interactive flag (T073)\n // -----------------------------------------------------------------------\n if (options.nonInteractive) {\n if (!options.config) {\n console.log(chalk.red(' --non-interactive requires --config'));\n console.log();\n return;\n }\n\n console.log(chalk.dim(' Running in non-interactive mode...'));\n console.log();\n\n // Save state so admin-server can load credentials\n try { saveState(state); } catch { /* non-critical */ }\n\n // Skip directly to generate step\n const result = await runGenerateStep(state);\n if (result === 'success') {\n await runCompleteStep(state, { noOpen: options.open === false });\n }\n return;\n }\n\n // -----------------------------------------------------------------------\n // 2c. Install type selection (skipped when --config or --non-interactive)\n // -----------------------------------------------------------------------\n if (!options.config && !options.nonInteractive) {\n const installType = await select({\n message: 'Select install type',\n choices: [\n {\n name: 'Full Install — Configure all services interactively',\n value: 'full',\n },\n {\n name: 'Minimal Install — Traefik + Gitea + Quick Tunnel only',\n value: 'minimal',\n },\n ],\n });\n\n if (installType === 'minimal') {\n return runMinimalInstall({ noOpen: options.open === false });\n }\n }\n\n // -----------------------------------------------------------------------\n // 3. Set up Ctrl+C cancel handler\n // -----------------------------------------------------------------------\n const cleanupCancel = setupCancelHandler(async () => {\n console.log();\n console.log(chalk.yellow(' Setup cancelled.'));\n\n // Save state so user can resume later\n try {\n saveState(state);\n console.log(chalk.dim(' Progress saved. Run `brewnet init` to resume.'));\n } catch {\n // Non-critical — just inform\n console.log(chalk.dim(' Could not save progress.'));\n }\n\n console.log();\n nav.cancel();\n process.exit(0);\n });\n\n // -----------------------------------------------------------------------\n // 4. Step loop (T074)\n // -----------------------------------------------------------------------\n try {\n while (!nav.isCancelled && nav.currentStep <= WizardStep.Complete) {\n switch (nav.currentStep) {\n // -----------------------------------------------------------------\n // Pre-Step: Admin Account\n // -----------------------------------------------------------------\n case WizardStep.AdminSetup: {\n state = await runAdminSetupStep(state);\n saveState(state);\n nav.goForward();\n break;\n }\n\n // -----------------------------------------------------------------\n // Step 0: System Check\n // -----------------------------------------------------------------\n case WizardStep.SystemCheck: {\n const checkResult = await runSystemCheckStep();\n\n if (!checkResult.passed) {\n // Critical failure or user declined to continue\n console.log(\n chalk.red(' Setup cannot continue. Please resolve the issues above.'),\n );\n console.log();\n cleanupCancel();\n return;\n }\n\n // Merge any port remapping choices into state\n if (Object.keys(checkResult.portRemapping).length > 0) {\n state = {\n ...state,\n portRemapping: { ...state.portRemapping, ...checkResult.portRemapping },\n };\n saveState(state);\n }\n\n nav.goForward();\n break;\n }\n\n // -----------------------------------------------------------------\n // Step 1: Project Setup\n // -----------------------------------------------------------------\n case WizardStep.ProjectSetup: {\n state = await runProjectSetupStep(state);\n\n // Save state after this step\n saveState(state);\n\n nav.goForward();\n break;\n }\n\n // -----------------------------------------------------------------\n // Step 2: Server Components\n // -----------------------------------------------------------------\n case WizardStep.ServerComponents: {\n state = await runServerComponentsStep(state);\n\n // Save state after this step\n saveState(state);\n\n nav.goForward();\n break;\n }\n\n // -----------------------------------------------------------------\n // Step 3: Dev Stack & Runtime (T078-T082)\n // -----------------------------------------------------------------\n case WizardStep.DevStack: {\n state = await runDevStackStep(state);\n\n // Save state after this step\n saveState(state);\n\n nav.goForward();\n break;\n }\n\n // -----------------------------------------------------------------\n // Step 4: Domain & Network (T085-T089)\n // -----------------------------------------------------------------\n case WizardStep.DomainNetwork: {\n state = await runDomainNetworkStep(state);\n\n // Save state after this step\n saveState(state);\n\n nav.goForward();\n break;\n }\n\n // -----------------------------------------------------------------\n // Step 5: Review & Confirm (T070)\n // -----------------------------------------------------------------\n case WizardStep.Review: {\n const reviewResult = await runReviewStep(state);\n\n saveState(state);\n\n if (reviewResult.action === 'generate') {\n nav.goForward();\n } else if (reviewResult.action === 'modify' && reviewResult.modifyStep !== undefined) {\n nav.goToStep(reviewResult.modifyStep);\n } else if (reviewResult.action === 'export') {\n // After export, stay on Review step so user can choose again\n // (do nothing — loop will re-run this step)\n }\n\n break;\n }\n\n // -----------------------------------------------------------------\n // Step 6: Generate & Start (T071)\n // -----------------------------------------------------------------\n case WizardStep.Generate: {\n const generateResult: GenerateResult = await runGenerateStep(state);\n // Persist runtime values (quickTunnelUrl, domain.name, etc.) captured during generation\n try { saveState(state); } catch (e) { console.warn('Failed to save state after generate:', e instanceof Error ? e.message : e); }\n\n switch (generateResult) {\n case 'success':\n nav.goForward();\n break;\n\n case 'error':\n console.log(chalk.yellow(' Returning to Review step.'));\n console.log();\n nav.goToStep(WizardStep.Review);\n break;\n\n case 'restart':\n console.log(chalk.cyan(' Restarting wizard from the beginning...'));\n console.log();\n nav.goToStep(WizardStep.AdminSetup);\n break;\n\n case 'clean-restart': {\n console.log(chalk.cyan(' Cleaning up before restart...'));\n try {\n await cleanupForRestart(state.projectPath);\n console.log(chalk.green(' Cleanup complete.'));\n } catch (err) {\n console.log(chalk.yellow(' Cleanup encountered errors (continuing anyway).'));\n if (err instanceof Error) {\n console.log(chalk.dim(` ${err.message}`));\n }\n }\n console.log();\n nav.goToStep(WizardStep.AdminSetup);\n break;\n }\n }\n\n break;\n }\n\n // -----------------------------------------------------------------\n // Step 7: Complete (T072)\n // -----------------------------------------------------------------\n case WizardStep.Complete: {\n await runCompleteStep(state, { noOpen: options.open === false });\n\n // Exit the loop — wizard is finished\n cleanupCancel();\n return;\n }\n\n default: {\n // Should not reach here — exit loop\n break;\n }\n }\n }\n } catch (err) {\n // Handle @inquirer/prompts ExitPromptError (Ctrl+C during prompt)\n if (\n err instanceof Error &&\n err.constructor.name === 'ExitPromptError'\n ) {\n console.log();\n console.log(chalk.yellow(' Setup cancelled.'));\n\n try {\n saveState(state);\n console.log(chalk.dim(' Progress saved. Run `brewnet init` to resume.'));\n } catch {\n // Non-critical\n }\n\n console.log();\n } else {\n // Unexpected error\n console.log();\n console.log(chalk.red(' An unexpected error occurred during setup.'));\n if (err instanceof Error) {\n console.log(chalk.dim(` ${err.message}`));\n }\n console.log();\n\n try {\n saveState(state);\n console.log(chalk.dim(' Progress saved. Run `brewnet init` to resume.'));\n } catch {\n // Non-critical\n }\n }\n } finally {\n cleanupCancel();\n }\n}\n","/**\n * Pre-Step: Admin Account Setup\n *\n * Collects admin username and password before any Docker installation.\n * The single credential set is propagated to all enabled services.\n *\n * @module wizard/steps/admin-setup\n */\n\nimport { input, password } from '@inquirer/prompts';\nimport chalk from 'chalk';\nimport type { WizardState } from '@brewnet/shared';\n\n// ---------------------------------------------------------------------------\n// Credential propagation targets (informational display)\n// ---------------------------------------------------------------------------\n\n// Services that receive admin credentials via docker-compose environment variables.\nconst AUTO_PROPAGATED_SERVICES = [\n 'Nextcloud (File Server)',\n 'MinIO (Object Storage)',\n 'pgAdmin (DB Admin UI)',\n 'SSH Server (OpenSSH)',\n];\n\n// Services that require separate account setup through their own UI/CLI.\nconst MANUAL_SETUP_SERVICES = [\n 'Gitea (Git server)',\n 'Jellyfin (Media server)',\n 'Mail Server (docker-mailserver)',\n 'FileBrowser',\n];\n\n// ---------------------------------------------------------------------------\n// Step runner\n// ---------------------------------------------------------------------------\n\n/**\n * Run Pre-Step: Admin Account Setup.\n *\n * Prompts for admin username and password.\n * Password is auto-generated (20 chars); user can accept or enter custom.\n * Shows which services will receive these credentials.\n *\n * @param state - Current wizard state\n * @returns Updated wizard state with admin credentials set\n */\nexport async function runAdminSetupStep(state: WizardState): Promise<WizardState> {\n const next = structuredClone(state);\n\n // -------------------------------------------------------------------------\n // 1. Header\n // -------------------------------------------------------------------------\n console.log();\n console.log(chalk.bold.cyan(' Pre-Step') + chalk.bold(' — Admin Account'));\n console.log(chalk.dim(' Set credentials before Docker installation'));\n console.log(chalk.dim(' These credentials are propagated to all enabled services'));\n console.log();\n\n // -------------------------------------------------------------------------\n // 2. Show credential propagation info\n // -------------------------------------------------------------------------\n console.log(chalk.yellow(' ⚠ Nextcloud, MinIO, pgAdmin, SSH Server를 사용할 경우'));\n console.log(chalk.yellow(' 적용되는 로그인 계정이므로 신중하게 입력하세요.'));\n console.log();\n console.log(chalk.dim(' 자동 적용 서비스:'));\n for (const svc of AUTO_PROPAGATED_SERVICES) {\n console.log(chalk.dim(` • ${svc}`));\n }\n console.log();\n console.log(chalk.dim(' 별도 설정 필요 서비스:'));\n for (const svc of MANUAL_SETUP_SERVICES) {\n console.log(chalk.dim(` • ${svc}`) + chalk.dim.italic(' — 자체 초기 설정에서 계정 생성'));\n }\n console.log();\n\n // -------------------------------------------------------------------------\n // 3. Username\n // -------------------------------------------------------------------------\n const adminUsername = await input({\n message: 'Admin username',\n default: next.admin.username || 'admin',\n });\n next.admin.username = adminUsername;\n\n // -------------------------------------------------------------------------\n // 4. Password — direct input (masked)\n // -------------------------------------------------------------------------\n console.log();\n console.log(chalk.dim(' 비밀번호는 모든 서비스에 동일하게 적용됩니다. 8자 이상.'));\n\n let adminPassword = '';\n while (true) {\n const pw = await password({\n message: 'Admin password',\n mask: '*',\n validate: (value: string) => {\n if (value.length < 8) return '8자 이상 입력하세요';\n return true;\n },\n });\n\n const pw2 = await password({\n message: 'Confirm password',\n mask: '*',\n });\n\n if (pw === pw2) {\n adminPassword = pw;\n break;\n }\n\n console.log(chalk.red(' 비밀번호가 일치하지 않습니다. 다시 입력하세요.'));\n console.log();\n }\n\n next.admin.password = adminPassword;\n next.admin.storage = 'local';\n\n console.log();\n console.log(chalk.green(' Admin account configured.'));\n console.log();\n\n return next;\n}\n","/**\n * T042 — Step 0: System Check (Wizard UI)\n *\n * Orchestrates the system pre-flight checks and displays results to the user.\n * Uses the system-checker service for the actual checks, and provides a\n * terminal UI with spinners, colored tables, and interactive prompts.\n *\n * Flow:\n * 1. Show header \"Step 0/7 — System Check\"\n * 2. If Docker not installed → auto-install with retry loop\n * 3. Run all checks with a spinner\n * 4. Display results in a formatted table\n * 5. Critical failures → show remediation hints + retry/quit prompt\n * 6. Warnings only → show remediation hints + confirm prompt\n * 7. All pass → proceed\n *\n * @module wizard/steps/system-check\n */\n\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { confirm, select, input } from '@inquirer/prompts';\nimport { execa } from 'execa';\nimport Table from 'cli-table3';\nimport { runAllChecks } from '../../services/system-checker.js';\nimport type { CheckResult } from '../../services/system-checker.js';\nimport {\n isDockerInstalled,\n isDaemonRunning,\n installDocker,\n waitForDockerDaemon,\n getDaemonDiagnostics,\n launchDockerDesktop,\n} from '../../services/docker-installer.js';\nimport { suggestAlternativePorts, getPortOccupant } from '../../utils/port-utils.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface SystemCheckStepResult {\n /** true if all critical checks pass and user confirms any warnings */\n passed: boolean;\n /** Individual check results */\n results: CheckResult[];\n /** Port remapping chosen during conflict resolution */\n portRemapping: Record<number, number>;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction statusIcon(status: CheckResult['status']): string {\n switch (status) {\n case 'pass': return chalk.green('✓');\n case 'fail': return chalk.red('✗');\n case 'warn': return chalk.yellow('⚠');\n }\n}\n\nfunction formatName(name: string, status: CheckResult['status']): string {\n switch (status) {\n case 'pass': return name;\n case 'fail': return chalk.red(name);\n case 'warn': return chalk.yellow(name);\n }\n}\n\nfunction formatMessage(message: string, status: CheckResult['status']): string {\n switch (status) {\n case 'pass': return chalk.green(message);\n case 'fail': return chalk.red(message);\n case 'warn': return chalk.yellow(message);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Docker 수동 실행 안내\n// ---------------------------------------------------------------------------\n\nfunction showDockerStartGuide(plat: string): void {\n console.log();\n console.log(chalk.bold(' Docker 수동 실행 방법:'));\n if (plat === 'darwin') {\n console.log(chalk.dim(' 1. Dock 또는 Applications 폴더에서 \"Docker\" 앱 실행'));\n console.log(chalk.dim(' 2. 메뉴바 상단 고래(🐳) 아이콘이 나타날 때까지 대기 (~30초)'));\n console.log(chalk.dim(' 3. 위 메뉴에서 \"60초 더 대기\"를 선택하세요'));\n } else {\n console.log(chalk.dim(' sudo systemctl start docker # systemd (Ubuntu/Debian/CentOS)'));\n console.log(chalk.dim(' sudo service docker start # SysV (구형 Ubuntu)'));\n console.log(chalk.dim(' sudo dockerd & # 수동 기동 (최후 수단)'));\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Docker Desktop 첫 실행 안내 (신규 설치 직후)\n// ---------------------------------------------------------------------------\n\nfunction showDockerFirstLaunchGuide(): void {\n console.log();\n console.log(chalk.bold(' Docker Desktop을 실행해주세요:'));\n console.log(chalk.dim(' 1. Dock 또는 Applications 폴더에서 \"Docker\" 앱 클릭'));\n console.log(chalk.dim(' 2. 라이선스 동의 및 초기 설정 완료 (~1분)'));\n console.log(chalk.dim(' 3. 메뉴바 상단 고래(🐳) 아이콘이 나타나면 준비 완료'));\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Docker 수동 설치 안내\n// ---------------------------------------------------------------------------\n\nfunction showDockerInstallGuide(plat: string): void {\n console.log();\n console.log(chalk.bold(' Docker 수동 설치 방법:'));\n if (plat === 'darwin') {\n console.log(chalk.dim(' 1. https://docs.docker.com/desktop/mac/ 에서 Docker Desktop 다운로드'));\n console.log(chalk.dim(' 2. Docker.dmg 열고 Applications 폴더에 드래그'));\n console.log(chalk.dim(' 3. Docker 앱 실행 → 메뉴바 고래 아이콘 확인'));\n console.log(chalk.dim(' 4. 완료 후: brewnet init'));\n } else {\n console.log(chalk.dim(' 1. curl -fsSL https://get.docker.com | sudo sh'));\n console.log(chalk.dim(' 2. sudo systemctl start docker'));\n console.log(chalk.dim(' 3. sudo usermod -aG docker $USER && newgrp docker'));\n console.log(chalk.dim(' 4. docker info (정상 응답 확인)'));\n console.log(chalk.dim(' 5. 완료 후: brewnet init'));\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Docker 데몬 대기 — 타임아웃 시 재시도 메뉴\n// ---------------------------------------------------------------------------\n\nasync function waitForDaemonWithRetry(plat: string): Promise<boolean> {\n const INITIAL_MS = plat === 'darwin' ? 90_000 : 30_000;\n const RETRY_MS = 60_000;\n\n const daemonSpinner = ora({\n text: `Docker 데몬 시작 대기 중... (최대 ${INITIAL_MS / 1000}초)`,\n indent: 2,\n }).start();\n\n let ready = await waitForDockerDaemon(INITIAL_MS);\n if (ready) {\n daemonSpinner.succeed('Docker 데몬이 준비됐습니다.');\n return true;\n }\n\n // 타임아웃 → 인터랙티브 루프\n while (true) {\n daemonSpinner.stop();\n console.log();\n console.log(chalk.red(' ✖ Docker 데몬이 응답하지 않습니다.'));\n console.log(chalk.dim(' Docker는 설치됐지만 아직 실행되지 않았습니다.'));\n\n // 실제 오류 원인 표시 (docker info stderr)\n const diagInfo = await getDaemonDiagnostics();\n if (diagInfo) {\n console.log();\n console.log(chalk.yellow(' 원인 (docker info):'));\n const lines = diagInfo.split('\\n').filter((l) => l.trim()).slice(0, 6);\n for (const line of lines) {\n console.log(chalk.dim(` ${line.trim()}`));\n }\n }\n console.log();\n\n const choices: Array<{ value: string; name: string }> = [\n { value: 'retry', name: `⏱ 60초 더 대기` },\n ];\n if (plat === 'darwin') {\n choices.push({ value: 'open', name: '🔧 Docker Desktop 직접 열기' });\n }\n choices.push(\n { value: 'manual', name: '📋 수동 실행 방법 보기' },\n { value: 'quit', name: '✗ 종료' },\n );\n\n const action = await select({ message: '어떻게 하시겠습니까?', choices });\n\n if (action === 'retry') {\n daemonSpinner.start('Docker 데몬 대기 중...');\n ready = await waitForDockerDaemon(RETRY_MS);\n if (ready) {\n daemonSpinner.succeed('Docker 데몬이 준비됐습니다.');\n return true;\n }\n // 루프 계속\n\n } else if (action === 'open') {\n console.log();\n console.log(chalk.dim(' Docker Desktop을 실행합니다...'));\n const lr = await launchDockerDesktop();\n if (!lr.success) {\n console.log(chalk.red(` Docker Desktop 실행 실패: ${lr.error ?? '알 수 없는 오류'}`));\n console.log(chalk.dim(' Dock 또는 Applications 폴더에서 직접 실행해주세요.'));\n console.log();\n }\n daemonSpinner.start('Docker Desktop 기동 대기 중...');\n ready = await waitForDockerDaemon(RETRY_MS);\n if (ready) {\n daemonSpinner.succeed('Docker 데몬이 준비됐습니다.');\n return true;\n }\n // 루프 계속\n\n } else if (action === 'manual') {\n showDockerStartGuide(plat);\n // 루프 반복 → 다시 선택\n\n } else {\n // quit\n console.log();\n console.log(chalk.dim(' Docker 실행 후 brewnet init을 다시 시도하세요.'));\n console.log();\n return false;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main Step Function\n// ---------------------------------------------------------------------------\n\n/**\n * Run Step 1: System Check.\n * This function never throws.\n */\nexport async function runSystemCheckStep(): Promise<SystemCheckStepResult> {\n const portRemapping: Record<number, number> = {};\n\n try {\n // -----------------------------------------------------------------------\n // 1. Display header\n // -----------------------------------------------------------------------\n console.log();\n console.log(chalk.bold.cyan(' Step 1/8') + chalk.bold(' — System Check'));\n console.log(chalk.dim(' Verifying that your system meets the requirements for Brewnet'));\n console.log();\n\n // -----------------------------------------------------------------------\n // 2. Docker 사전 설치 (없는 경우 자동 설치 — 재시도 루프 포함)\n // -----------------------------------------------------------------------\n const dockerInstalled = await isDockerInstalled();\n\n if (dockerInstalled) {\n const daemonRunning = await isDaemonRunning();\n if (daemonRunning) {\n // Docker가 이미 실행 중 — 그대로 사용\n console.log(chalk.green(' ✓ Docker가 실행 중입니다. 기존 설치를 사용합니다.'));\n console.log();\n } else {\n // Docker CLI는 있지만 데몬이 꺼진 경우 — 기동 안내\n const plat = process.platform;\n console.log(chalk.yellow(' ⚠ Docker가 설치되어 있지만 실행되지 않았습니다.'));\n console.log(chalk.dim(\n plat === 'darwin'\n ? ' Docker Desktop을 자동으로 실행합니다...'\n : ' Docker 데몬을 시작합니다. (sudo 권한이 필요할 수 있습니다)',\n ));\n console.log();\n\n if (plat === 'darwin') {\n const lr = await launchDockerDesktop();\n if (!lr.success) {\n console.log(chalk.red(` Docker Desktop 실행 실패: ${lr.error ?? '알 수 없는 오류'}`));\n console.log(chalk.dim(' Dock 또는 Applications 폴더에서 직접 실행해주세요.'));\n console.log();\n }\n } else {\n await execa('sudo', ['systemctl', 'start', 'docker'], {\n stdio: 'inherit',\n reject: false,\n });\n }\n\n const daemonReady = await waitForDaemonWithRetry(plat);\n if (!daemonReady) {\n return { passed: false, results: [], portRemapping };\n }\n console.log();\n }\n }\n\n if (!dockerInstalled) {\n const plat = process.platform;\n\n console.log(chalk.yellow(' ⚠ Docker가 설치되지 않았습니다. 자동으로 설치합니다.'));\n console.log(chalk.dim(\n plat === 'darwin'\n ? ' [macOS] Homebrew를 통해 Docker Desktop을 설치합니다.'\n : ' [Linux] 공식 Docker 설치 스크립트를 실행합니다. (sudo 권한 필요)',\n ));\n console.log();\n\n // 설치 시도 — 실패 시 재시도/수동안내/종료 메뉴\n let requiresRelogin = false;\n while (true) {\n const installResult = await installDocker();\n\n if (installResult.success) {\n console.log();\n const platformLabel = plat === 'darwin' ? 'macOS' : 'Linux';\n console.log(chalk.green(` ✓ Docker 설치 완료 (${platformLabel})`));\n requiresRelogin = installResult.requiresRelogin ?? false;\n break;\n }\n\n // 설치 실패\n console.log(chalk.red(` ✗ Docker 자동 설치 실패: ${installResult.message}`));\n console.log();\n\n const action = await select({\n message: '어떻게 하시겠습니까?',\n choices: [\n { value: 'retry', name: '🔄 설치 다시 시도' },\n { value: 'manual', name: '📋 수동 설치 방법 보기' },\n { value: 'quit', name: '✗ 종료' },\n ],\n });\n\n if (action === 'retry') {\n console.log();\n continue;\n }\n if (action === 'manual') {\n showDockerInstallGuide(plat);\n }\n return { passed: false, results: [], portRemapping };\n }\n\n // Docker 설치 완료 — macOS: 사용자가 직접 실행하도록 안내 후 확인 대기\n // (자동 실행을 시도하지 않음 — 첫 실행 시 라이선스 동의 등 사용자 상호작용 필요)\n if (plat === 'darwin') {\n showDockerFirstLaunchGuide();\n await confirm({\n message: 'Docker Desktop을 실행하고 메뉴바에 고래(🐳) 아이콘이 나타났나요?',\n default: true,\n });\n console.log();\n }\n\n // Docker 설치 완료 — 데몬 기동 대기 (재시도 루프 포함)\n const daemonReady = await waitForDaemonWithRetry(process.platform);\n if (!daemonReady) {\n return { passed: false, results: [], portRemapping };\n }\n\n if (requiresRelogin) {\n console.log();\n console.log(chalk.dim(\n ' ℹ docker 그룹이 추가됐습니다. sudo 없이 사용하려면 새 터미널 세션을 열어주세요.',\n ));\n }\n console.log();\n }\n\n // -----------------------------------------------------------------------\n // 3. Run all checks with spinner\n // -----------------------------------------------------------------------\n const spinner = ora({ text: 'Running system checks...', indent: 2 }).start();\n const { results, hasCriticalFailure, warnings } = await runAllChecks();\n spinner.stop();\n console.log();\n\n // -----------------------------------------------------------------------\n // 4. Display results table\n // -----------------------------------------------------------------------\n const table = new Table({\n head: [chalk.bold('Check'), chalk.bold('Result'), chalk.bold('Details')],\n colWidths: [35, 40, 26],\n style: { head: [], border: ['dim'] },\n wordWrap: true,\n });\n\n for (const result of results) {\n table.push([\n `${statusIcon(result.status)} ${formatName(result.name, result.status)}`,\n formatMessage(result.message, result.status),\n result.details ? chalk.dim(result.details) : chalk.dim('—'),\n ]);\n }\n\n console.log(table.toString());\n console.log();\n\n // -----------------------------------------------------------------------\n // 4a. Port conflict resolution — suggest alternatives and collect remapping\n // -----------------------------------------------------------------------\n const portConflicts = results.filter(\n (r) => r.status === 'warn' && r.name.startsWith('Port '),\n );\n\n if (portConflicts.length > 0) {\n console.log(chalk.yellow(\n ` ${portConflicts.length} port conflict(s) detected. Select an alternative for each.`,\n ));\n console.log();\n\n for (const conflict of portConflicts) {\n // name format: \"Port 80 — Traefik (Web Server)\" or legacy \"Port 80\"\n const portMatch = conflict.name.match(/^Port (\\d+)/);\n const port = portMatch ? parseInt(portMatch[1], 10) : 0;\n const serviceMatch = conflict.name.match(/— (.+)$/);\n const serviceName = serviceMatch ? serviceMatch[1] : null;\n const occupant = getPortOccupant(port);\n const alternatives = suggestAlternativePorts(port);\n\n const choices: Array<{ value: number | 'keep' | 'custom'; name: string }> = alternatives.map((p) => ({\n value: p as number | 'keep' | 'custom',\n name: `Port ${p}`,\n }));\n choices.push({ value: 'custom', name: 'Enter custom port number' });\n choices.push({ value: 'keep', name: `Keep port ${port} (conflict will persist)` });\n\n const occupantHint = occupant ? `, used by ${occupant}` : '';\n const serviceHint = serviceName ? ` [${serviceName}]` : '';\n const choice = await select({\n message: `Port ${port}${serviceHint} is in use${occupantHint}. Choose an alternative:`,\n choices,\n });\n\n if (choice === 'custom') {\n const raw = await input({\n message: 'Enter port number (1024-65535)',\n validate: (v) => {\n const n = parseInt(v, 10);\n if (isNaN(n) || n < 1024 || n > 65535) return 'Enter a number between 1024 and 65535';\n return true;\n },\n });\n const customPort = parseInt(raw, 10);\n portRemapping[port] = customPort;\n console.log(chalk.dim(` → Port ${port} remapped to ${customPort}`));\n } else if (choice !== 'keep') {\n portRemapping[port] = choice as number;\n console.log(chalk.dim(` → Port ${port} remapped to ${choice as number}`));\n } else {\n console.log(chalk.dim(` → Keeping port ${port} (may cause issues)`));\n }\n console.log();\n }\n }\n\n // -----------------------------------------------------------------------\n // 5. Critical failures → remediation 힌트 + 컨텍스트별 해결 메뉴\n // -----------------------------------------------------------------------\n if (hasCriticalFailure) {\n const criticalFailures = results.filter((r) => r.status === 'fail' && r.critical);\n\n console.log(chalk.red.bold(' 필수 요구사항을 충족하지 못했습니다:'));\n console.log();\n for (const f of criticalFailures) {\n console.log(chalk.red(` ✗ ${f.name}: ${f.message}`));\n if (f.remediation) {\n console.log(chalk.dim(` → ${f.remediation}`));\n }\n }\n console.log();\n\n // Docker 데몬 미실행(BN001) 여부 — 전용 재시도 메뉴 제공\n const isDockerDaemonFailure = criticalFailures.some(\n (f) => f.name === 'Docker' && f.message.includes('BN001'),\n );\n\n if (isDockerDaemonFailure) {\n const plat = process.platform;\n const choices: Array<{ value: string; name: string }> = [\n { value: 'wait', name: '⏱ Docker 기동 대기 (60초)' },\n ];\n if (plat === 'darwin') {\n choices.push({ value: 'open', name: '🐳 Docker Desktop 직접 열기 후 대기' });\n }\n choices.push(\n { value: 'manual', name: '📋 수동 실행 방법 보기' },\n { value: 'recheck', name: '🔄 다시 검사 (Docker 이미 실행 중인 경우)' },\n { value: 'quit', name: '✗ 종료' },\n );\n\n while (true) {\n const action = await select({ message: '어떻게 하시겠습니까?', choices });\n console.log();\n\n if (action === 'wait') {\n const ready = await waitForDaemonWithRetry(plat);\n if (ready) return runSystemCheckStep();\n // 루프 반복\n\n } else if (action === 'open') {\n console.log(chalk.dim(' Docker Desktop을 실행합니다...'));\n const lr = await launchDockerDesktop();\n if (!lr.success) {\n console.log(chalk.red(` Docker Desktop 실행 실패: ${lr.error ?? '알 수 없는 오류'}`));\n console.log(chalk.dim(' Dock 또는 Applications 폴더에서 직접 실행해주세요.'));\n console.log();\n }\n const ready = await waitForDaemonWithRetry(plat);\n if (ready) return runSystemCheckStep();\n // 루프 반복\n\n } else if (action === 'manual') {\n showDockerStartGuide(plat);\n // 루프 반복\n\n } else if (action === 'recheck') {\n return runSystemCheckStep();\n\n } else {\n return { passed: false, results, portRemapping };\n }\n }\n }\n\n // Docker 외 일반 critical failure → 직접 해결 후 재검사\n const action = await select({\n message: '어떻게 하시겠습니까?',\n choices: [\n { value: 'retry', name: '🔄 문제 해결 후 다시 검사' },\n { value: 'quit', name: '✗ 종료' },\n ],\n });\n\n console.log();\n\n if (action === 'retry') {\n return runSystemCheckStep();\n }\n\n return { passed: false, results, portRemapping };\n }\n\n // -----------------------------------------------------------------------\n // 6. Non-port warnings → remediation 힌트 + 계속할지 확인\n // -----------------------------------------------------------------------\n const nonPortWarnings = warnings.filter((w) => !w.name.startsWith('Port '));\n\n if (nonPortWarnings.length > 0) {\n console.log(chalk.yellow(\n ` ${nonPortWarnings.length}개의 경고가 있습니다. 필수는 아니지만 일부 기능에 영향을 줄 수 있습니다.`,\n ));\n console.log();\n for (const w of nonPortWarnings) {\n console.log(chalk.yellow(` ⚠ ${w.name}: ${w.message}`));\n if (w.remediation) {\n console.log(chalk.dim(` → ${w.remediation}`));\n }\n }\n console.log();\n\n const shouldContinue = await confirm({\n message: '경고를 무시하고 계속 진행하시겠습니까?',\n default: true,\n });\n\n console.log();\n return { passed: shouldContinue, results, portRemapping };\n }\n\n // -----------------------------------------------------------------------\n // 7. All pass\n // -----------------------------------------------------------------------\n console.log(chalk.green.bold(' 모든 시스템 체크를 통과했습니다!'));\n console.log();\n\n return { passed: true, results, portRemapping };\n\n } catch (err) {\n console.log();\n console.log(chalk.red(' 시스템 체크 중 예상치 못한 오류가 발생했습니다.'));\n if (err instanceof Error) {\n console.log(chalk.dim(` ${err.message}`));\n }\n console.log();\n return { passed: false, results: [], portRemapping };\n }\n}\n","/**\n * Brewnet CLI — System Checker (Step 0 of the Init Wizard)\n *\n * Runs a series of pre-flight checks to verify that the host system meets\n * the minimum requirements for running Brewnet services:\n *\n * - OS platform (macOS / Linux)\n * - Docker Engine + Docker Compose\n * - Node.js version (>= 20)\n * - Available disk space\n * - Available system memory (RAM)\n * - Port availability (80, 443, 2222)\n * - Git installation\n *\n * Every individual check function catches its own errors and returns a\n * {@link CheckResult} — they never throw. The {@link runAllChecks}\n * orchestrator runs ALL checks (no short-circuiting) and aggregates the\n * results.\n *\n * @module services/system-checker\n */\n\nimport { execa } from 'execa';\nimport { createServer } from 'node:net';\nimport os from 'node:os';\nconst { platform, release, totalmem } = os;\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface CheckResult {\n name: string;\n status: 'pass' | 'fail' | 'warn';\n message: string;\n details?: string;\n critical: boolean;\n /** One-line actionable fix hint shown on failure */\n remediation?: string;\n}\n\nexport interface RunAllChecksResult {\n results: CheckResult[];\n hasCriticalFailure: boolean;\n warnings: CheckResult[];\n}\n\n// ---------------------------------------------------------------------------\n// T036 — checkOS\n// ---------------------------------------------------------------------------\n\nexport async function checkOS(): Promise<CheckResult> {\n try {\n const plat = platform();\n const rel = release();\n\n if (plat === 'darwin' || plat === 'linux') {\n const label = plat === 'darwin' ? 'macOS' : 'Linux';\n return {\n name: 'OS',\n status: 'pass',\n message: `${label} detected — supported platform`,\n details: `${plat} ${rel}`,\n critical: true,\n };\n }\n\n return {\n name: 'OS',\n status: 'fail',\n message: `Unsupported platform: ${plat}. Brewnet requires macOS or Linux.`,\n details: `${plat} ${rel}`,\n critical: true,\n remediation: 'macOS 12+ 또는 Ubuntu 20.04+에서 실행하세요.',\n };\n } catch (err) {\n return {\n name: 'OS',\n status: 'fail',\n message: 'Unable to determine operating system',\n details: String(err),\n critical: true,\n remediation: 'macOS 12+ 또는 Ubuntu 20.04+에서 실행하세요.',\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// T037 — checkDocker\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a major version number from a Docker version string.\n * Examples:\n * \"Docker version 27.0.3, build 7d4bcd8\" → 27\n * \"Docker version 20.10.7, build f0df350\" → 20\n */\nfunction parseDockerMajor(stdout: string): number {\n const match = stdout.match(/(\\d+)\\.\\d+/);\n return match ? parseInt(match[1], 10) : 0;\n}\n\nexport async function checkDocker(): Promise<CheckResult> {\n try {\n // 1) Check docker --version\n const versionResult = await execa('docker', ['--version']);\n const versionString = versionResult.stdout;\n const major = parseDockerMajor(versionString);\n\n // 2) Check docker compose version (verifies Docker Compose V2 is available)\n try {\n await execa('docker', ['compose', 'version']);\n } catch {\n // Docker Compose not available — Docker daemon may be down or compose missing\n return {\n name: 'Docker',\n status: 'fail',\n message: 'Docker daemon is not running or Docker Compose is not available (BN001)',\n details: versionString,\n critical: true,\n remediation: 'macOS: Docker Desktop 앱 실행 | Linux: sudo systemctl start docker',\n };\n }\n\n // 3) Version check — require >= 24.0\n if (major < 24) {\n return {\n name: 'Docker',\n status: 'fail',\n message: `Docker version ${major}.x is too old. Brewnet requires Docker 24.0+.`,\n details: versionString,\n critical: true,\n remediation: 'https://docs.docker.com/engine/install/ 에서 최신 Docker 설치 후 재시도하세요.',\n };\n }\n\n return {\n name: 'Docker',\n status: 'pass',\n message: 'Docker is installed and running',\n details: versionString,\n critical: true,\n };\n } catch {\n return {\n name: 'Docker',\n status: 'fail',\n message: 'Docker not found or not installed. Please install Docker and try again.',\n details: 'Install Docker: https://docs.docker.com/get-docker/',\n critical: true,\n remediation: 'macOS: Docker Desktop 앱 실행 | Linux: sudo systemctl start docker',\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// T038 — checkNodeVersion\n// ---------------------------------------------------------------------------\n\nconst MIN_NODE_MAJOR = 20;\n\nexport async function checkNodeVersion(): Promise<CheckResult> {\n try {\n // Try execa('node', ['--version']) first for testability,\n // fall back to process.version if execa fails.\n let version: string;\n try {\n const result = await execa('node', ['--version']);\n version = result.stdout.trim();\n } catch {\n version = process.version;\n }\n\n const match = version.match(/^v?(\\d+)/);\n const major = match ? parseInt(match[1], 10) : 0;\n\n if (major >= MIN_NODE_MAJOR) {\n return {\n name: 'Node.js',\n status: 'pass',\n message: `Node.js ${version} detected`,\n details: version,\n critical: true,\n };\n }\n\n return {\n name: 'Node.js',\n status: 'fail',\n message: `Node.js ${version} is too old. Please install or upgrade to Node.js >= 20.`,\n details: `Found ${version}, minimum required is v20.0.0`,\n critical: true,\n remediation: 'https://nodejs.org/en/download/ 에서 v20+ 설치 후 재시도하세요.',\n };\n } catch (err) {\n return {\n name: 'Node.js',\n status: 'fail',\n message: 'Unable to determine Node.js version',\n details: String(err),\n critical: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// T039 — checkDiskSpace\n// ---------------------------------------------------------------------------\n\n/**\n * Parse available disk space in GB from `df` output.\n *\n * Supports two common output formats:\n * - GB-based: header contains \"G-blocks\" → Available column value is in GB\n * - KB-based: header contains \"K-blocks\" or \"1K\" → convert from KB to GB\n * - Default fallback: if the value is > 10000, treat as KB; otherwise GB\n */\nfunction parseDfAvailableGB(stdout: string): number {\n const lines = stdout.trim().split('\\n');\n if (lines.length < 2) return -1;\n\n const header = lines[0];\n const dataLine = lines[lines.length - 1]; // last line with actual data\n const fields = dataLine.trim().split(/\\s+/);\n\n // Available is typically the 4th column (index 3)\n if (fields.length < 4) return -1;\n\n const availableRaw = parseInt(fields[3], 10);\n if (isNaN(availableRaw)) return -1;\n\n // Determine unit from header\n if (header.match(/G-blocks|1G/i)) {\n // Values are already in GB\n return availableRaw;\n }\n\n if (header.match(/K-blocks|1K/i)) {\n // Values are in 1K blocks — convert to GB\n return availableRaw / (1024 * 1024);\n }\n\n // Heuristic fallback: if the number is very large, assume KB\n if (availableRaw > 100000) {\n return availableRaw / (1024 * 1024);\n }\n\n // Otherwise assume GB\n return availableRaw;\n}\n\nexport async function checkDiskSpace(minGB: number = 20): Promise<CheckResult> {\n try {\n const plat = platform();\n const dfArgs = plat === 'darwin' ? ['-g', '/'] : ['-BG', '/'];\n\n const result = await execa('df', dfArgs);\n const availableGB = parseDfAvailableGB(result.stdout);\n\n if (availableGB < 0) {\n return {\n name: 'Disk Space',\n status: 'warn',\n message: 'Unable to determine available disk space',\n details: result.stdout,\n critical: false,\n };\n }\n\n const roundedGB = Math.round(availableGB * 10) / 10;\n\n if (availableGB >= minGB) {\n return {\n name: 'Disk Space',\n status: 'pass',\n message: `${roundedGB}GB available disk space`,\n details: `${roundedGB}GB available (minimum: ${minGB}GB)`,\n critical: false,\n };\n }\n\n return {\n name: 'Disk Space',\n status: 'warn',\n message: `Low disk space: ${roundedGB}GB available (recommended: ${minGB}GB)`,\n details: `${roundedGB}GB available, ${minGB}GB recommended`,\n critical: false,\n remediation: '불필요한 파일을 삭제해 공간을 확보하세요. (df -h 로 확인)',\n };\n } catch {\n return {\n name: 'Disk Space',\n status: 'warn',\n message: 'Unable to check disk space',\n critical: false,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// T039 — checkMemory\n// ---------------------------------------------------------------------------\n\nexport async function checkMemory(minGB: number = 2): Promise<CheckResult> {\n try {\n const totalBytes = totalmem();\n const totalGB = totalBytes / (1024 * 1024 * 1024);\n const roundedGB = Math.round(totalGB * 10) / 10;\n\n if (totalGB >= minGB) {\n return {\n name: 'Memory',\n status: 'pass',\n message: `${roundedGB}GB RAM detected`,\n details: `${roundedGB}GB total (minimum: ${minGB}GB)`,\n critical: false,\n };\n }\n\n return {\n name: 'Memory',\n status: 'warn',\n message: `Low memory: ${roundedGB}GB RAM (recommended: ${minGB}GB)`,\n details: `${roundedGB}GB total, ${minGB}GB recommended`,\n critical: false,\n remediation: '실행 중인 앱을 종료해 메모리를 확보하세요.',\n };\n } catch (err) {\n return {\n name: 'Memory',\n status: 'warn',\n message: 'Unable to determine system memory',\n details: String(err),\n critical: false,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// T040 — checkPort\n// ---------------------------------------------------------------------------\n\nexport async function checkPort(port: number, service?: string): Promise<CheckResult> {\n const label = service ? `Port ${port} — ${service}` : `Port ${port}`;\n\n return new Promise<CheckResult>((resolve) => {\n try {\n const server = createServer();\n let resolved = false;\n\n const handleError = (err: NodeJS.ErrnoException): void => {\n if (resolved) return;\n resolved = true;\n\n if (err.code === 'EADDRINUSE') {\n resolve({\n name: label,\n status: 'warn',\n message: `Port ${port} is already in use`,\n details: service\n ? `${service} needs port ${port}, but another process is using it.`\n : `Port ${port} is in use (EADDRINUSE). Another process is bound to this port.`,\n critical: false,\n remediation: `lsof -i :${port} 으로 프로세스 확인 후 종료하세요.`,\n });\n } else if (err.code === 'EACCES') {\n resolve({\n name: label,\n status: 'warn',\n message: `Port ${port} requires elevated permissions`,\n details: service\n ? `${service} needs port ${port}, but permission denied (EACCES).`\n : `Port ${port} permission denied (EACCES). Try running with sudo or use a port >= 1024.`,\n critical: false,\n remediation: `sudo brewnet init 으로 재시도하거나 포트를 변경하세요.`,\n });\n } else {\n resolve({\n name: label,\n status: 'warn',\n message: `Port ${port} check failed: ${err.message}`,\n details: `Port ${port}: ${err.code ?? err.message}`,\n critical: false,\n });\n }\n };\n\n server.on('error', handleError);\n\n server.listen(port, () => {\n if (resolved) return;\n resolved = true;\n server.close(() => {\n resolve({\n name: label,\n status: 'pass',\n message: `Port ${port} is available`,\n details: service ? `${service} — Port ${port}` : `Port ${port}`,\n critical: false,\n });\n });\n });\n } catch (err) {\n resolve({\n name: `Port ${port}`,\n status: 'warn',\n message: `Port ${port} check failed`,\n details: String(err),\n critical: false,\n });\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// T041 — checkGit\n// ---------------------------------------------------------------------------\n\nexport async function checkGit(): Promise<CheckResult> {\n try {\n const result = await execa('git', ['--version']);\n const version = result.stdout.trim();\n\n return {\n name: 'Git',\n status: 'pass',\n message: `Git is installed`,\n details: version,\n critical: false,\n };\n } catch {\n return {\n name: 'Git',\n status: 'warn',\n message: 'Git not found. Install Git for deployment features.',\n details: 'Git is not installed or not in PATH',\n critical: false,\n remediation: 'macOS: xcode-select --install | Linux: sudo apt-get install -y git',\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Orchestrator — runAllChecks\n// ---------------------------------------------------------------------------\n\n/** Maps default ports to the Brewnet service that needs them. */\nconst DEFAULT_PORTS: Array<{ port: number; service: string }> = [\n { port: 80, service: 'Traefik (Web Server)' },\n { port: 443, service: 'Traefik (HTTPS)' },\n { port: 2222, service: 'SSH Server' },\n];\n\nexport async function runAllChecks(): Promise<RunAllChecksResult> {\n const results: CheckResult[] = [];\n\n // Run all checks — never short-circuit\n try {\n results.push(await checkOS());\n } catch {\n results.push({\n name: 'OS',\n status: 'fail',\n message: 'OS check failed unexpectedly',\n critical: true,\n });\n }\n\n try {\n results.push(await checkDocker());\n } catch {\n results.push({\n name: 'Docker',\n status: 'fail',\n message: 'Docker check failed unexpectedly',\n critical: true,\n });\n }\n\n try {\n results.push(await checkNodeVersion());\n } catch {\n results.push({\n name: 'Node.js',\n status: 'fail',\n message: 'Node.js check failed unexpectedly',\n critical: true,\n });\n }\n\n try {\n results.push(await checkMemory());\n } catch {\n results.push({\n name: 'Memory',\n status: 'warn',\n message: 'Memory check failed unexpectedly',\n critical: false,\n });\n }\n\n try {\n results.push(await checkDiskSpace());\n } catch {\n results.push({\n name: 'Disk Space',\n status: 'warn',\n message: 'Disk space check failed unexpectedly',\n critical: false,\n });\n }\n\n // Check default ports\n for (const { port, service } of DEFAULT_PORTS) {\n try {\n results.push(await checkPort(port, service));\n } catch {\n results.push({\n name: `Port ${port} — ${service}`,\n status: 'warn',\n message: `Port ${port} check failed unexpectedly`,\n critical: false,\n });\n }\n }\n\n try {\n results.push(await checkGit());\n } catch {\n results.push({\n name: 'Git',\n status: 'warn',\n message: 'Git check failed unexpectedly',\n critical: false,\n });\n }\n\n // Compute aggregates\n const hasCriticalFailure = results.some(\n (r) => r.status === 'fail' && r.critical === true,\n );\n\n const warnings = results.filter((r) => r.status === 'warn');\n\n return {\n results,\n hasCriticalFailure,\n warnings,\n };\n}\n","/**\n * Brewnet CLI — Docker Installer\n *\n * Handles automatic Docker installation when `brewnet init` is run on a system\n * without Docker. Supports macOS (via Homebrew) and Linux (via the official\n * Docker convenience script).\n *\n * Platform-specific flows:\n *\n * macOS:\n * 1. Check for Homebrew — install if missing\n * 2. brew install --cask docker (skipped if /Applications/Docker.app exists)\n * 3. open -a Docker (launch Docker Desktop)\n * 4. Poll until Docker daemon is ready (max 90s)\n *\n * Linux:\n * 1. Download get.docker.com convenience script\n * 2. sudo sh /tmp/get-docker.sh\n * 3. sudo systemctl start docker && sudo systemctl enable docker\n * 4. sudo usermod -aG docker $USER\n * 5. Poll until Docker daemon is ready (max 30s)\n *\n * All commands that require user interaction (sudo password, Homebrew install)\n * use `stdio: 'inherit'` to pass through to the user's terminal.\n *\n * @module services/docker-installer\n */\n\nimport { execa } from 'execa';\nimport os from 'node:os';\nimport ora from 'ora';\nimport chalk from 'chalk';\nconst { platform } = os;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface InstallResult {\n success: boolean;\n message: string;\n /** true if user needs to re-login for docker group changes (Linux only) */\n requiresRelogin?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// PATH augmentation\n// ---------------------------------------------------------------------------\n\n/**\n * Build an env object with an augmented PATH that includes all common\n * Docker / Homebrew install locations.\n *\n * macOS Node.js processes often inherit only /usr/bin:/bin:/usr/sbin:/sbin —\n * /usr/local/bin (Docker Desktop CLI) and /opt/homebrew/bin (Apple Silicon\n * Homebrew) are missing. We prepend them so every execa('docker', ...) and\n * execa('brew', ...) call can resolve the binary regardless of how brewnet\n * was launched.\n */\nfunction augmentedEnv(): NodeJS.ProcessEnv {\n const base = process.env['PATH'] ?? '/usr/bin:/bin:/usr/sbin:/sbin';\n const extra = '/usr/local/bin:/opt/homebrew/bin';\n // Avoid duplicating entries that are already in base\n const combined = extra\n .split(':')\n .filter((p) => !base.split(':').includes(p))\n .concat(base.split(':'))\n .join(':');\n return { ...process.env, PATH: combined };\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Check if the `docker` CLI is available anywhere on this machine.\n *\n * Uses an augmented PATH so detection works even when Node.js was started\n * with a restricted PATH (common when launched from npm global scripts).\n */\nexport async function isDockerInstalled(): Promise<boolean> {\n try {\n await execa('docker', ['--version'], { env: augmentedEnv() });\n return true;\n } catch { /* not found in augmented PATH */ }\n\n // Final fallback: Docker Desktop app bundle exists (CLI symlink not yet created)\n try {\n await execa('test', ['-d', '/Applications/Docker.app']);\n return true;\n } catch { /* no app bundle */ }\n\n return false;\n}\n\n/**\n * Check if Homebrew is available on macOS.\n */\nasync function checkHomebrew(): Promise<boolean> {\n try {\n await execa('brew', ['--version'], { env: augmentedEnv() });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Install Homebrew using the official installer script.\n * Passes through to the terminal so the user can provide their password.\n */\nasync function installHomebrew(): Promise<void> {\n await execa(\n '/bin/bash',\n ['-c', 'curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | bash'],\n { stdio: 'inherit' },\n );\n}\n\n/**\n * Launch Docker Desktop on macOS.\n *\n * Tries direct app bundle path first (`open /Applications/Docker.app`),\n * which works reliably even right after installation when Spotlight hasn't\n * indexed the new app yet. Falls back to `open -a Docker` as a secondary\n * attempt (covers non-standard install locations).\n *\n * Never throws.\n */\nexport async function launchDockerDesktop(): Promise<{ success: boolean; error?: string }> {\n // Attempt 1: direct bundle path — unaffected by Spotlight indexing delay\n try {\n const r = await execa('open', ['/Applications/Docker.app'], {\n env: augmentedEnv(),\n reject: false,\n });\n if (r.exitCode === 0) return { success: true };\n // exitCode !== 0 means the .app doesn't exist at this path — try -a fallback\n } catch { /* fall through */ }\n\n // Attempt 2: Spotlight-based lookup (covers Docker installed elsewhere)\n try {\n const r = await execa('open', ['-a', 'Docker'], {\n env: augmentedEnv(),\n reject: false,\n });\n if (r.exitCode === 0) return { success: true };\n const errMsg = r.stderr?.trim() || r.stdout?.trim() || `exit code ${r.exitCode}`;\n return { success: false, error: errMsg };\n } catch (err) {\n return { success: false, error: err instanceof Error ? err.message : String(err) };\n }\n}\n\n/**\n * Run `docker info` and return the error output for diagnostic purposes.\n * Useful for showing the user exactly why the daemon isn't responding.\n * Never throws — returns an empty string if Docker is running normally.\n */\nexport async function getDaemonDiagnostics(): Promise<string> {\n try {\n const result = await execa('docker', ['info'], {\n env: augmentedEnv(),\n reject: false,\n timeout: 5000,\n });\n if (result.exitCode === 0) return '';\n return result.stderr?.trim() || result.stdout?.trim() || '';\n } catch (err) {\n return err instanceof Error ? err.message : String(err);\n }\n}\n\n/**\n * Check if the Docker daemon is currently running (single instant check).\n * Returns true only if `docker info` exits with 0 right now.\n */\nexport async function isDaemonRunning(): Promise<boolean> {\n try {\n const result = await execa('docker', ['info'], {\n env: augmentedEnv(),\n reject: false,\n });\n return result.exitCode === 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Poll the Docker daemon until it is ready or the timeout expires.\n *\n * @param timeoutMs Maximum wait time in milliseconds\n * @param intervalMs Poll interval in milliseconds (default 2s)\n * @returns true if daemon became ready, false if timed out\n */\nexport async function waitForDockerDaemon(\n timeoutMs: number,\n intervalMs = 2000,\n): Promise<boolean> {\n const start = Date.now();\n\n while (Date.now() - start < timeoutMs) {\n try {\n const result = await execa('docker', ['info'], {\n env: augmentedEnv(),\n reject: false,\n });\n if (result.exitCode === 0) return true;\n } catch {\n // Ignore errors — daemon not ready yet\n }\n await sleep(intervalMs);\n }\n\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// macOS\n// ---------------------------------------------------------------------------\n\nasync function installDockerMacOS(): Promise<InstallResult> {\n // ── Stage 1/3: Homebrew ──────────────────────────────────────────────────\n const s1 = ora({ text: '[1/3] Homebrew 확인 중...', indent: 2 }).start();\n const hasBrew = await checkHomebrew();\n\n if (!hasBrew) {\n s1.warn('[1/3] Homebrew 미설치 — 먼저 설치합니다');\n console.log();\n console.log(chalk.dim(' (관리자 비밀번호 입력이 필요할 수 있습니다)'));\n console.log();\n\n try {\n await installHomebrew();\n } catch {\n return {\n success: false,\n message: 'Homebrew 설치에 실패했습니다. https://brew.sh 에서 수동으로 설치해주세요.',\n };\n }\n\n const brewAvailable = await checkHomebrew();\n if (!brewAvailable) {\n return {\n success: false,\n message:\n 'Homebrew 설치 후에도 brew 명령을 찾을 수 없습니다. ' +\n '새 터미널을 열고 다시 시도하거나 https://brew.sh 를 참고하세요.',\n };\n }\n\n console.log(chalk.green(' ✔ Homebrew 설치 완료'));\n console.log();\n } else {\n s1.succeed('[1/3] Homebrew 확인됨');\n }\n\n // ── Stage 2/3: brew install --cask docker ───────────────────────────────\n // Skip if Docker.app already exists — avoids brew hang when Docker was\n // installed via .dmg or a previous brew run that succeeded partially.\n const dockerAppExists = await (async () => {\n try { await execa('test', ['-d', '/Applications/Docker.app']); return true; }\n catch { return false; }\n })();\n\n if (dockerAppExists) {\n console.log(chalk.dim(' [2/3] Docker Desktop이 이미 설치되어 있습니다. 건너뜁니다.'));\n } else {\n const s2 = ora({\n text: '[2/3] Docker Desktop 다운로드 중... (~600MB, 수 분 소요)',\n indent: 2,\n }).start();\n\n // Accumulate brew stderr so we can surface it on failure.\n const brewErrLines: string[] = [];\n\n try {\n // stdin: 'inherit' lets macOS system dialogs pass through to the terminal.\n const child = execa('brew', ['install', '--cask', 'docker-desktop'], {\n env: augmentedEnv(),\n stdin: 'inherit',\n });\n\n const onBrewLine = (line: string) => {\n if (/==> Downloading/i.test(line)) {\n s2.text = '[2/3] Docker Desktop 다운로드 중... (~600MB)';\n } else if (/^\\s*#{3,}/.test(line)) {\n const pct = line.match(/(\\d+(?:\\.\\d+)?)%/);\n if (pct) s2.text = `[2/3] Docker Desktop 다운로드 중... ${Math.round(Number(pct[1]))}%`;\n } else if (/==> Verifying/i.test(line)) {\n s2.text = '[2/3] 파일 무결성 검증 중...';\n } else if (/==> Installing/i.test(line)) {\n s2.text = '[2/3] Docker Desktop 설치 중...';\n } else if (/==> Moving/i.test(line)) {\n s2.text = '[2/3] Applications 폴더로 이동 중...';\n } else if (/already installed/i.test(line)) {\n s2.text = '[2/3] Docker Desktop 이미 설치됨 (brew 기록)';\n }\n };\n\n child.stdout?.on('data', (chunk: Buffer) => {\n chunk.toString().split('\\n').forEach(onBrewLine);\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n const lines = chunk.toString().split('\\n');\n lines.forEach(onBrewLine);\n lines.forEach((l) => { if (l.trim()) brewErrLines.push(l.trim()); });\n });\n\n await child;\n\n // Verify the app bundle actually landed in /Applications.\n // brew exits 0 in several cases that leave no app:\n // - \"already installed\" (brew DB has a record but .app was manually deleted)\n // - macOS Gatekeeper blocked the move\n // - partial previous install left brew thinking it is done\n const appNowExists = await (async () => {\n try { await execa('test', ['-d', '/Applications/Docker.app']); return true; }\n catch { return false; }\n })();\n\n if (!appNowExists) {\n const detail = brewErrLines.slice(-3).join(' | ') || 'brew 출력 없음';\n s2.fail(`[2/3] Docker Desktop 설치 실패 — brew 완료 후 앱 번들 없음`);\n console.log(chalk.dim(` brew 출력: ${detail}`));\n return {\n success: false,\n message:\n 'brew install --cask docker 가 exit 0을 반환했지만 /Applications/Docker.app 이 없습니다.\\n' +\n ' 아래 명령으로 직접 재설치해주세요:\\n' +\n ' brew reinstall --cask docker\\n' +\n ' 또는 수동 설치: https://docs.docker.com/desktop/mac/',\n };\n }\n\n s2.succeed('[2/3] Docker Desktop 설치 완료');\n } catch (err) {\n const detail = brewErrLines.slice(-3).join(' | ')\n || (err instanceof Error ? err.message : String(err));\n s2.fail(`[2/3] Docker Desktop 설치 실패: ${detail}`);\n return {\n success: false,\n message: `brew install --cask docker-desktop 실패: ${detail}\\n수동 설치: https://docs.docker.com/desktop/mac/`,\n };\n }\n }\n\n return { success: true, message: 'Docker Desktop 설치 완료' };\n}\n\n// ---------------------------------------------------------------------------\n// Linux\n// ---------------------------------------------------------------------------\n\nasync function installDockerLinux(): Promise<InstallResult> {\n // ── Stage 1/3: 설치 스크립트 실행 (sudo — TTY 필요) ────────────────────\n console.log();\n console.log(chalk.bold(' [1/3] Docker 설치 스크립트 실행 중...'));\n console.log(chalk.dim(' (sudo 비밀번호 입력이 필요할 수 있습니다)'));\n console.log();\n\n try {\n await execa(\n 'sh',\n ['-c', 'curl -fsSL https://get.docker.com -o /tmp/get-docker.sh && sudo sh /tmp/get-docker.sh'],\n { stdio: 'inherit' },\n );\n } catch {\n return {\n success: false,\n message: 'Docker 설치 스크립트 실행 실패. 수동으로 설치해주세요: https://docs.docker.com/engine/install/',\n };\n }\n\n console.log();\n console.log(chalk.green(' ✔ [1/3] Docker 설치 완료'));\n console.log();\n\n // ── Stage 2/3: 서비스 시작 ───────────────────────────────────────────────\n const s2 = ora({ text: '[2/3] Docker 서비스 시작 중...', indent: 2 }).start();\n try {\n await execa('sudo', ['systemctl', 'start', 'docker'], { reject: false });\n await execa('sudo', ['systemctl', 'enable', 'docker'], { reject: false });\n s2.succeed('[2/3] Docker 서비스 시작됨');\n } catch {\n s2.warn('[2/3] Docker 서비스 자동 시작 실패 — 수동으로 시작: sudo systemctl start docker');\n }\n\n // ── Stage 3/3: docker 그룹 추가 ─────────────────────────────────────────\n const currentUser = process.env['USER'] ?? process.env['LOGNAME'];\n if (currentUser) {\n const s3 = ora({ text: `[3/3] docker 그룹에 ${currentUser} 추가 중...`, indent: 2 }).start();\n try {\n await execa('sudo', ['usermod', '-aG', 'docker', currentUser], { reject: false });\n s3.succeed(`[3/3] docker 그룹에 ${currentUser} 추가됨`);\n } catch {\n s3.warn('[3/3] docker 그룹 추가 실패 — 수동으로 실행: sudo usermod -aG docker $USER');\n }\n }\n\n return {\n success: true,\n message: 'Docker 설치 완료',\n requiresRelogin: true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Install Docker for the current platform.\n *\n * Handles macOS (Docker Desktop via Homebrew) and Linux\n * (official convenience script + systemctl).\n *\n * After a successful install, the caller should invoke\n * {@link waitForDockerDaemon} to confirm the daemon is ready.\n */\nexport async function installDocker(): Promise<InstallResult> {\n const plat = platform();\n\n if (plat === 'darwin') {\n return installDockerMacOS();\n }\n\n if (plat === 'linux') {\n return installDockerLinux();\n }\n\n return {\n success: false,\n message: `지원되지 않는 플랫폼: ${plat}. macOS 또는 Linux에서 실행해주세요.`,\n };\n}\n","/**\n * @module port-utils\n * @description Port conflict detection and alternative port suggestion utilities.\n * Used by the System Check wizard step to resolve port conflicts gracefully.\n */\n\nimport * as net from 'net';\nimport { execSync } from 'node:child_process';\nimport os from 'node:os';\n\n// ---------------------------------------------------------------------------\n// Port occupant detection\n// ---------------------------------------------------------------------------\n\n/**\n * Detect which process is occupying a port.\n * Uses `lsof` (macOS/Linux). Returns a human-readable string like\n * \"nginx (PID 1234)\" or null if detection fails.\n */\nexport function getPortOccupant(port: number): string | null {\n try {\n const plat = os.platform();\n if (plat !== 'darwin' && plat !== 'linux') return null;\n\n const raw = execSync(`lsof -iTCP:${port} -sTCP:LISTEN -P -n 2>/dev/null`, {\n encoding: 'utf-8',\n timeout: 3000,\n }).trim();\n\n if (!raw) return null;\n\n // Skip header, parse first LISTEN line: COMMAND PID USER ...\n const lines = raw.split('\\n').slice(1);\n if (lines.length === 0) return null;\n\n const parts = lines[0].split(/\\s+/);\n const command = parts[0] ?? 'unknown';\n const pid = parts[1] ?? '?';\n return `${command} (PID ${pid})`;\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Port alternative suggestions\n// ---------------------------------------------------------------------------\n\n/** Maps known default ports to ordered lists of alternative candidates. */\nconst PORT_ALTERNATIVES: Record<number, number[]> = {\n 80: [8080, 8088, 8000, 8008],\n 443: [8443, 4443, 9443],\n 2222: [2223, 2220, 22222],\n 3000: [3001, 3010, 3080],\n 3022: [3023, 3033, 3030],\n 5432: [5433, 5434],\n 3306: [3307, 3308],\n 6379: [6380, 6381],\n 8096: [8097, 8098, 9096],\n 9000: [9001, 9090, 9900],\n 5050: [5051, 5052],\n 8085: [8086, 8087],\n};\n\n/**\n * Returns a list of alternative port numbers for a given port.\n * Falls back to a generic range if the port is not in the registry.\n */\nexport function suggestAlternativePorts(port: number): number[] {\n if (PORT_ALTERNATIVES[port]) {\n return PORT_ALTERNATIVES[port];\n }\n // Generic fallback: port+1, port+10, port+100\n return [port + 1, port + 10, port + 100].filter((p) => p <= 65535);\n}\n\n// ---------------------------------------------------------------------------\n// Port availability check\n// ---------------------------------------------------------------------------\n\n/**\n * Checks whether a given port is available on all interfaces.\n * Returns true if the port can be bound, false if it's in use.\n */\nexport function isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.once('error', () => {\n resolve(false);\n });\n server.once('listening', () => {\n server.close(() => resolve(true));\n });\n server.listen(port, '0.0.0.0');\n });\n}\n\n/**\n * Find the first available alternative port for the given conflict port.\n * Returns undefined if none of the candidates are available.\n */\nexport async function findFirstAvailableAlternative(port: number): Promise<number | undefined> {\n const candidates = suggestAlternativePorts(port);\n for (const candidate of candidates) {\n if (await isPortAvailable(candidate)) {\n return candidate;\n }\n }\n return undefined;\n}\n","/**\n * T045-T046 — Step 1: Project Setup (Wizard UI)\n *\n * Collects project name, project path, and setup type (Full / Partial).\n * Applies the corresponding install defaults to the wizard state.\n *\n * Flow:\n * 1. Show header \"Step 1/7 — Project Setup\"\n * 2. Prompt for project name (validated)\n * 3. Prompt for project path (default: ~/brewnet/<name>)\n * 4. Prompt for setup type (Full / Partial)\n * 5. Create project directory if it does not exist\n * 6. Apply defaults based on setup type\n * 7. Return updated state\n *\n * @module wizard/steps/project-setup\n */\n\nimport { input, select } from '@inquirer/prompts';\nimport chalk from 'chalk';\nimport { mkdirSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { homedir } from 'node:os';\nimport { validateProjectName } from '../../utils/validation.js';\nimport {\n applyFullInstallDefaults,\n applyPartialInstallDefaults,\n} from '../../config/defaults.js';\nimport type { WizardState, SetupType } from '@brewnet/shared';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface ProjectSetupResult {\n projectName: string;\n projectPath: string;\n setupType: SetupType;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Expand a path that starts with `~` to the user's home directory.\n */\nfunction expandTilde(p: string): string {\n if (p.startsWith('~/') || p === '~') {\n return p.replace(/^~/, homedir());\n }\n return p;\n}\n\n// ---------------------------------------------------------------------------\n// Main Step Function\n// ---------------------------------------------------------------------------\n\n/**\n * Run Step 1: Project Setup.\n *\n * Collects project name, path, and setup type from the user.\n * Creates the project directory if needed, and applies the\n * Full/Partial install defaults to the state.\n *\n * Throws if the user presses Ctrl+C during a prompt (ExitPromptError\n * from @inquirer/prompts). The caller should catch this and handle\n * cancellation.\n */\nexport async function runProjectSetupStep(\n state: WizardState,\n): Promise<WizardState> {\n // -------------------------------------------------------------------------\n // 1. Display header\n // -------------------------------------------------------------------------\n console.log();\n console.log(\n chalk.bold.cyan(' Step 2/8') + chalk.bold(' — Project Setup'),\n );\n console.log(\n chalk.dim(\n ' Configure your project name, location, and installation type',\n ),\n );\n console.log();\n\n // -------------------------------------------------------------------------\n // 2. Prompt for project name\n // -------------------------------------------------------------------------\n const projectName = await input({\n message: 'Project name',\n default: state.projectName || 'my-homeserver',\n validate: (value: string) => {\n const result = validateProjectName(value);\n return result.valid ? true : (result.error ?? 'Invalid project name');\n },\n });\n\n // -------------------------------------------------------------------------\n // 3. Prompt for project path\n // -------------------------------------------------------------------------\n const defaultPath = `~/brewnet/${projectName}`;\n\n const rawPath = await input({\n message: 'Project path',\n default: state.projectPath !== `~/brewnet/${state.projectName}`\n ? state.projectPath\n : defaultPath,\n });\n\n const projectPath = rawPath;\n\n // -------------------------------------------------------------------------\n // 4. Prompt for setup type\n // -------------------------------------------------------------------------\n const setupType = await select<SetupType>({\n message: 'Installation type',\n choices: [\n {\n name: 'Full Install (recommended)',\n value: 'full',\n description: 'Web Server + Git Server + Database + Cache',\n },\n {\n name: 'Partial Install',\n value: 'partial',\n description: 'Web Server + Git Server only — add components later',\n },\n ],\n default: state.setupType || 'full',\n });\n\n // -------------------------------------------------------------------------\n // 5. Create project directory if needed\n // -------------------------------------------------------------------------\n const resolvedPath = expandTilde(projectPath);\n if (!existsSync(resolvedPath)) {\n mkdirSync(resolvedPath, { recursive: true });\n console.log();\n console.log(\n chalk.dim(` Created directory: ${resolvedPath}`),\n );\n }\n\n // -------------------------------------------------------------------------\n // 6. Apply defaults based on setup type\n // -------------------------------------------------------------------------\n let updatedState: WizardState = {\n ...state,\n projectName,\n projectPath,\n setupType,\n };\n\n if (setupType === 'full') {\n updatedState = applyFullInstallDefaults(updatedState);\n } else {\n updatedState = applyPartialInstallDefaults(updatedState);\n }\n\n // -------------------------------------------------------------------------\n // 7. Summary\n // -------------------------------------------------------------------------\n console.log();\n console.log(chalk.green(' Project setup complete:'));\n console.log(chalk.dim(` Name: ${projectName}`));\n console.log(chalk.dim(` Path: ${resolvedPath}`));\n console.log(\n chalk.dim(\n ` Type: ${setupType === 'full' ? 'Full Install' : 'Partial Install'}`,\n ),\n );\n console.log();\n\n return updatedState;\n}\n","/**\n * Brewnet CLI — Input Validation Helpers (T017)\n *\n * Pure functions for validating user-supplied values:\n * project names, domain names, tunnel tokens, and free-domain TLDs.\n *\n * Each validator returns `{ valid: true }` or `{ valid: false, error: string }`.\n *\n * @module utils/validation\n */\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Project Name\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a Brewnet project name.\n *\n * Rules:\n * - Minimum 2 characters\n * - Maximum 63 characters (DNS label limit)\n * - Only lowercase alphanumeric characters and hyphens\n * - Must start and end with an alphanumeric character\n * - No consecutive hyphens\n *\n * Pattern: /^[a-z0-9][a-z0-9-]*[a-z0-9]$/ (for length >= 2)\n *\n * @example\n * ```ts\n * validateProjectName('my-server') // { valid: true }\n * validateProjectName('-bad') // { valid: false, error: '...' }\n * ```\n */\nexport function validateProjectName(name: string): ValidationResult {\n if (typeof name !== 'string' || name.length === 0) {\n return { valid: false, error: 'Project name is required' };\n }\n\n if (name.length < 2) {\n return { valid: false, error: 'Project name must be at least 2 characters' };\n }\n\n if (name.length > 63) {\n return { valid: false, error: 'Project name must be at most 63 characters' };\n }\n\n if (!/^[a-z0-9]/.test(name)) {\n return { valid: false, error: 'Project name must start with a lowercase letter or digit' };\n }\n\n if (!/[a-z0-9]$/.test(name)) {\n return { valid: false, error: 'Project name must end with a lowercase letter or digit' };\n }\n\n if (!/^[a-z0-9-]+$/.test(name)) {\n return {\n valid: false,\n error: 'Project name may only contain lowercase letters (a-z), digits (0-9), and hyphens (-)',\n };\n }\n\n if (/--/.test(name)) {\n return { valid: false, error: 'Project name must not contain consecutive hyphens (--)' };\n }\n\n return { valid: true };\n}\n\n// ---------------------------------------------------------------------------\n// Domain Name\n// ---------------------------------------------------------------------------\n\n/**\n * Regex for a valid domain name.\n *\n * - Each label: 1-63 alphanumeric chars or hyphens (no leading/trailing hyphen)\n * - TLD: 2-63 alphabetic characters\n * - Overall max 253 characters (enforced separately)\n */\nconst DOMAIN_LABEL = /^(?!-)[a-zA-Z0-9-]{1,63}(?<!-)$/;\nconst DOMAIN_TLD = /^[a-zA-Z]{2,63}$/;\n\n/**\n * Validate a domain name (e.g. `myserver.example.com`).\n *\n * Follows RFC 1035 / RFC 1123 rules:\n * - Labels separated by dots\n * - Each label: 1-63 chars, alphanumeric + hyphens, no leading/trailing hyphen\n * - TLD must be alphabetic (2-63 chars)\n * - Total length <= 253\n *\n * Does not accept IP addresses, wildcards, or trailing dots.\n */\nexport function validateDomainName(name: string): ValidationResult {\n if (typeof name !== 'string' || name.length === 0) {\n return { valid: false, error: 'Domain name is required' };\n }\n\n if (name.length > 253) {\n return { valid: false, error: 'Domain name must be at most 253 characters' };\n }\n\n // Remove trailing dot if present (FQDN notation)\n const normalized = name.endsWith('.') ? name.slice(0, -1) : name;\n\n const labels = normalized.split('.');\n\n if (labels.length < 2) {\n return { valid: false, error: 'Domain name must have at least two labels (e.g. example.com)' };\n }\n\n // Validate each label\n for (let i = 0; i < labels.length; i++) {\n const label = labels[i]!;\n if (label.length === 0) {\n return { valid: false, error: 'Domain name must not contain empty labels (consecutive dots)' };\n }\n\n // Last label is the TLD — must be alphabetic only\n if (i === labels.length - 1) {\n if (!DOMAIN_TLD.test(label)) {\n return { valid: false, error: `Invalid TLD \"${label}\": must be 2-63 alphabetic characters` };\n }\n } else {\n if (!DOMAIN_LABEL.test(label)) {\n return {\n valid: false,\n error: `Invalid label \"${label}\": must be 1-63 alphanumeric characters or hyphens, no leading/trailing hyphen`,\n };\n }\n }\n }\n\n return { valid: true };\n}\n\n// ---------------------------------------------------------------------------\n// Cloudflare Tunnel Token\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a Cloudflare Tunnel token.\n *\n * Cloudflare issues tunnel tokens as base64-encoded JSON (JWT format),\n * which always start with `eyJ` (the base64 encoding of `{\"` ).\n *\n * Additional checks:\n * - Must be at least 50 characters (real tokens are ~200+ chars)\n * - Must contain only valid base64url characters\n */\nexport function validateTunnelToken(token: string): ValidationResult {\n if (typeof token !== 'string' || token.length === 0) {\n return { valid: false, error: 'Tunnel token is required' };\n }\n\n if (!token.startsWith('eyJ')) {\n return {\n valid: false,\n error: 'Tunnel token must start with \"eyJ\" (JWT format). Get your token from the Cloudflare Zero Trust dashboard.',\n };\n }\n\n if (token.length < 50) {\n return {\n valid: false,\n error: 'Tunnel token appears too short. Please copy the full token from the Cloudflare dashboard.',\n };\n }\n\n // JWT tokens use base64url encoding: alphanumeric, hyphens, underscores, dots, and equals\n if (!/^[A-Za-z0-9\\-_=.]+$/.test(token)) {\n return {\n valid: false,\n error: 'Tunnel token contains invalid characters. It should only contain alphanumeric characters, hyphens, underscores, dots, and equals signs.',\n };\n }\n\n return { valid: true };\n}\n\n","/**\n * T050 — Step 2: Server Component Toggle Rules (Pure Logic)\n *\n * Pure functions that enforce business rules for server component selection.\n * These functions take a WizardState and return an updated WizardState,\n * with no side effects. Used by the Step 2 wizard UI and by tests.\n *\n * Rules:\n * - Web Server is always enabled (required)\n * - Git Server is always enabled (required)\n * - File Server or Media Server enabled → SFTP auto-suggested\n * - DB Server enabled + empty dbPassword → auto-generate password\n * - SSH Server default → passwordAuth = false (key-only)\n * - Language/frontend selected → App Server auto-enabled\n * - App Server auto-enabled → FileBrowser auto-enabled\n *\n * @module wizard/steps/server-components\n */\n\nimport { input, select, confirm } from '@inquirer/prompts';\nimport chalk from 'chalk';\nimport type {\n WizardState,\n WebServerService,\n FileServerService,\n DbPrimary,\n} from '@brewnet/shared';\nimport { DB_VERSIONS } from '@brewnet/shared';\nimport { generatePassword } from '../../utils/password.js';\nimport { estimateResources } from '../../utils/resources.js';\n\n// ---------------------------------------------------------------------------\n// Pure Rule Functions\n// ---------------------------------------------------------------------------\n\n/**\n * Apply all component toggle rules to the given wizard state.\n * Ensures invariants (required components, auto-suggestions, auto-generation).\n *\n * @param state - Current wizard state\n * @returns Updated wizard state with rules applied\n */\nexport function applyComponentRules(state: WizardState): WizardState {\n const next = structuredClone(state);\n\n // Required components — always enabled\n next.servers.webServer.enabled = true;\n next.servers.gitServer.enabled = true;\n\n // SSH Server defaults to key-only auth\n if (next.servers.sshServer.enabled && next.servers.sshServer.passwordAuth === undefined) {\n next.servers.sshServer.passwordAuth = false;\n }\n\n // SFTP auto-suggestion when File Server or Media is enabled\n if (shouldAutoSuggestSftp(next)) {\n next.servers.sshServer.sftp = true;\n }\n\n // DB password auto-generation\n if (next.servers.dbServer.enabled && !next.servers.dbServer.dbPassword) {\n next.servers.dbServer.dbPassword = generatePassword(16);\n }\n\n return next;\n}\n\n/**\n * Check whether SFTP should be auto-suggested (checked by default).\n * SFTP is auto-suggested when File Server or Media Server is enabled.\n *\n * @param state - Current wizard state\n * @returns true if SFTP should be auto-suggested\n */\nexport function shouldAutoSuggestSftp(state: WizardState): boolean {\n return state.servers.fileServer.enabled || state.servers.media.enabled;\n}\n\n/**\n * Apply devStack-based auto-enables.\n * When languages or frontend technologies are selected, App Server\n * and FileBrowser are automatically enabled.\n *\n * @param state - Current wizard state\n * @returns Updated wizard state with auto-enables applied\n */\nexport function applyDevStackAutoEnables(state: WizardState): WizardState {\n const next = structuredClone(state);\n\n const hasLanguages = next.devStack.languages.length > 0;\n const hasFrontend = next.devStack.frontend !== null;\n const hasDevStack = hasLanguages || hasFrontend;\n\n next.servers.appServer.enabled = hasDevStack;\n\n // FileBrowser auto-enabled alongside App Server\n if (hasDevStack) {\n next.servers.fileBrowser.enabled = true;\n } else {\n // When no devStack, FileBrowser reverts to its previous state\n // (only auto-disable if it was auto-enabled by devStack)\n next.servers.fileBrowser.enabled = false;\n }\n\n return next;\n}\n\n\n// ---------------------------------------------------------------------------\n// Interactive Step Function (T052-T058)\n// ---------------------------------------------------------------------------\n\n/**\n * Run Step 3: Server Components.\n *\n * Displays admin account summary (set in Pre-Step) and collects\n * server component selections.\n * Applies component rules at the end to enforce invariants.\n *\n * Flow:\n * 1. Display header \"Step 3/8 — Server Components\"\n * 2. Admin Account summary (read-only, set in Pre-Step)\n * 3. Web Server (always ON, select service)\n * 4. File Server (toggle, select service)\n * 5. Git Server (always ON, show info)\n * 6. DB Server (toggle, primary, version, password)\n * 7. Media (toggle jellyfin)\n * 8. SSH Server (toggle, port, passwordAuth, SFTP auto-suggest)\n * 9. Apply component rules\n * 10. Show resource estimation\n * 11. Return updated state\n *\n * @param state - Current wizard state\n * @returns Updated wizard state with server component selections\n */\nexport async function runServerComponentsStep(\n state: WizardState,\n): Promise<WizardState> {\n const next = structuredClone(state);\n\n // -------------------------------------------------------------------------\n // 1. Display header\n // -------------------------------------------------------------------------\n console.log();\n console.log(\n chalk.bold.cyan(' Step 3/8') + chalk.bold(' — Server Components'),\n );\n console.log(\n chalk.dim(\n ' Select server components to install',\n ),\n );\n console.log();\n\n // -------------------------------------------------------------------------\n // 2. Admin Account summary (read-only — configured in Pre-Step)\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Admin Account') + chalk.dim(' (configured in Pre-Step)'));\n console.log(chalk.dim(` Username: ${next.admin.username || 'admin'}`));\n console.log(chalk.dim(` Password: ${'*'.repeat(Math.min(next.admin.password?.length ?? 0, 12))} (set)`));\n console.log();\n\n // -------------------------------------------------------------------------\n // 3. Web Server (always ON — select service)\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Web Server') + chalk.green(' (required)'));\n console.log(chalk.dim(' 모든 서비스 앞단에서 HTTPS 처리 및 도메인 라우팅을 담당하는 리버스 프록시'));\n console.log();\n\n const webService = await select<WebServerService>({\n message: 'Reverse proxy',\n choices: [\n { name: 'Traefik (recommended)', value: 'traefik', description: '자동 SSL 갱신 + Docker 레이블 기반 라우팅. 서비스 추가 시 설정 불필요' },\n { name: 'Nginx', value: 'nginx', description: '업계 표준 웹서버 겸 프록시. 안정적이며 범용 설정 지원' },\n { name: 'Caddy', value: 'caddy', description: '간결한 설정 파일, Let\\'s Encrypt 자동화 내장' },\n ],\n default: next.servers.webServer.service || 'traefik',\n });\n next.servers.webServer.service = webService;\n console.log();\n\n // -------------------------------------------------------------------------\n // 4. File Server (toggle + select service)\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' File Server'));\n console.log(chalk.dim(' 파일 저장·공유·동기화 서버. Dropbox / S3 같은 자체 호스팅 스토리지'));\n console.log();\n\n const fileServerEnabled = await confirm({\n message: 'Enable File Server?',\n default: next.servers.fileServer.enabled,\n });\n next.servers.fileServer.enabled = fileServerEnabled;\n\n if (fileServerEnabled) {\n const fileService = await select<FileServerService>({\n message: 'File server service',\n choices: [\n { name: 'Nextcloud', value: 'nextcloud', description: '파일 동기화 + 캘린더·연락처·사진 앱 포함 올인원 협업 Suite' },\n { name: 'MinIO (S3-compatible)', value: 'minio', description: 'AWS S3 호환 오브젝트 스토리지. 대용량 파일·백업·미디어 저장에 최적' },\n ],\n default: next.servers.fileServer.service || 'nextcloud',\n });\n next.servers.fileServer.service = fileService;\n } else {\n next.servers.fileServer.service = '';\n }\n console.log();\n\n // -------------------------------------------------------------------------\n // 5. Git Server (always ON — info display)\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Git Server') + chalk.green(' (required)'));\n console.log(\n chalk.dim(` Gitea — Web UI port ${next.servers.gitServer.port}, SSH port ${next.servers.gitServer.sshPort}`),\n );\n console.log();\n\n // -------------------------------------------------------------------------\n // 6. DB Server (toggle + primary + version + password)\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Database Server'));\n console.log(chalk.dim(' 앱 데이터를 영구 저장하는 관계형 DB. 대부분의 서비스에 필수'));\n console.log();\n\n const dbEnabled = await confirm({\n message: 'Enable Database Server?',\n default: next.servers.dbServer.enabled,\n });\n next.servers.dbServer.enabled = dbEnabled;\n\n if (dbEnabled) {\n // Primary database\n const dbPrimary = await select<DbPrimary>({\n message: 'Primary database',\n choices: [\n { name: 'PostgreSQL (recommended)', value: 'postgresql', description: '기능이 풍부한 오픈소스 RDBMS. JSON·전문검색 지원, 대규모 서비스에 적합' },\n { name: 'MySQL', value: 'mysql', description: '세계 최다 사용 DB. WordPress·Drupal 등 PHP 생태계와 높은 호환성' },\n { name: 'SQLite (embedded)', value: 'sqlite', description: '파일 기반 경량 DB. 외부 서버 불필요, 소규모·단일 서비스용' },\n ],\n default: next.servers.dbServer.primary || 'postgresql',\n });\n next.servers.dbServer.primary = dbPrimary;\n\n // Version selection (skip for SQLite — it only has version \"3\")\n if (dbPrimary !== 'sqlite') {\n const versions = DB_VERSIONS[dbPrimary] ?? [];\n if (versions.length > 0) {\n const dbVersion = await select<string>({\n message: `${dbPrimary === 'postgresql' ? 'PostgreSQL' : 'MySQL'} version`,\n choices: versions.map((v) => ({ name: v, value: v })),\n default: next.servers.dbServer.primaryVersion || versions[0],\n });\n next.servers.dbServer.primaryVersion = dbVersion;\n }\n\n // Admin UI (pgAdmin / phpMyAdmin)\n const adminUILabel = dbPrimary === 'postgresql' ? 'pgAdmin' : 'phpMyAdmin';\n const adminUI = await confirm({\n message: `Enable ${adminUILabel} (database admin UI)?`,\n default: next.servers.dbServer.adminUI,\n });\n next.servers.dbServer.adminUI = adminUI;\n\n if (adminUI && dbPrimary === 'postgresql') {\n console.log(chalk.dim(' ℹ pgAdmin password = brewnet admin password (set in Pre-Step)'));\n const pgadminEmail = await input({\n message: 'pgAdmin login email',\n default: next.servers.dbServer.pgadminEmail || '',\n validate: (v) => v.includes('@') || 'Please enter a valid email address',\n });\n next.servers.dbServer.pgadminEmail = pgadminEmail;\n }\n } else {\n next.servers.dbServer.primaryVersion = '3';\n next.servers.dbServer.adminUI = false;\n }\n\n // DB name and user\n const dbName = await input({\n message: 'Database name',\n default: next.servers.dbServer.dbName || 'brewnet_db',\n });\n next.servers.dbServer.dbName = dbName;\n\n const dbUser = await input({\n message: 'Database user',\n default: next.servers.dbServer.dbUser || 'brewnet',\n });\n next.servers.dbServer.dbUser = dbUser;\n\n // DB password — auto-generate if empty\n if (!next.servers.dbServer.dbPassword) {\n next.servers.dbServer.dbPassword = generatePassword(16);\n console.log(\n chalk.dim(' Database password auto-generated.'),\n );\n }\n\n // Cache layer — removed (no longer offered)\n } else {\n // Reset DB fields when disabled\n next.servers.dbServer.primary = '';\n next.servers.dbServer.primaryVersion = '';\n next.servers.dbServer.dbName = '';\n next.servers.dbServer.dbUser = '';\n next.servers.dbServer.dbPassword = '';\n next.servers.dbServer.adminUI = false;\n next.servers.dbServer.cache = '';\n }\n console.log();\n\n // -------------------------------------------------------------------------\n // 7. Media (toggle jellyfin)\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Media Server'));\n console.log(chalk.dim(' 영화·드라마·음악·사진을 스트리밍하는 자체 Netflix. 브라우저·모바일·TV 앱 지원'));\n console.log();\n\n const mediaEnabled = await confirm({\n message: 'Enable Media Server (Jellyfin)?',\n default: next.servers.media.enabled,\n });\n next.servers.media.enabled = mediaEnabled;\n next.servers.media.services = mediaEnabled ? ['jellyfin'] : [];\n console.log();\n\n // -------------------------------------------------------------------------\n // 8. SSH Server (toggle + passwordAuth + SFTP auto-suggest)\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' SSH Server'));\n console.log(chalk.dim(' 터미널로 서버에 원격 접속하거나 SFTP로 파일을 전송하는 보안 채널'));\n console.log();\n\n const sshEnabled = await confirm({\n message: 'Enable SSH Server?',\n default: next.servers.sshServer.enabled,\n });\n next.servers.sshServer.enabled = sshEnabled;\n\n if (sshEnabled) {\n next.servers.sshServer.port = 2222;\n console.log(chalk.dim(' SSH port: 2222'));\n\n const passwordAuth = await confirm({\n message: 'Allow password authentication? (key-only auth recommended)',\n default: next.servers.sshServer.passwordAuth ?? false,\n });\n next.servers.sshServer.passwordAuth = passwordAuth;\n\n // SFTP auto-suggestion\n if (shouldAutoSuggestSftp(next)) {\n console.log(\n chalk.dim(' SFTP auto-enabled (File Server or Media Server is active)'),\n );\n next.servers.sshServer.sftp = true;\n } else {\n const sftpEnabled = await confirm({\n message: 'Enable SFTP subsystem?',\n default: next.servers.sshServer.sftp,\n });\n next.servers.sshServer.sftp = sftpEnabled;\n }\n }\n console.log();\n\n // -------------------------------------------------------------------------\n // 9. Apply component rules\n // -------------------------------------------------------------------------\n const finalState = applyComponentRules(next);\n\n // -------------------------------------------------------------------------\n // 10. Resource estimation summary\n // -------------------------------------------------------------------------\n const resources = estimateResources(finalState);\n\n console.log(chalk.bold(' Resource Estimation'));\n console.log(chalk.dim(` Containers: ${resources.containers}`));\n console.log(chalk.dim(` RAM: ~${resources.ramGB}`));\n console.log(chalk.dim(` Disk: ~${resources.diskGB} GB`));\n console.log();\n\n // -------------------------------------------------------------------------\n // 11. Summary\n // -------------------------------------------------------------------------\n console.log(chalk.green(' Server components configured.'));\n console.log();\n\n return finalState;\n}\n","/**\n * Brewnet CLI — Confusion-Free Password Generator (T016)\n *\n * Generates cryptographically random passwords using a charset\n * that excludes visually ambiguous characters:\n *\n * Lowercase: a-k, m-n, p-z (excludes 'l', 'o')\n * Uppercase: A-H, J-N, P-Z (excludes 'I', 'O')\n * Digits: 2-9 (excludes '0', '1')\n *\n * This prevents user confusion when reading credentials on screen\n * or copying them from terminal output.\n *\n * @module utils/password\n */\n\nimport { randomBytes } from 'node:crypto';\n\n/**\n * The confusion-free character set (56 characters).\n *\n * - Lowercase (24): abcdefghijkmnpqrstuvwxyz (excludes 'l', 'o')\n * - Uppercase (24): ABCDEFGHJKLMNPQRSTUVWXYZ (excludes 'I', 'O')\n * - Digits (8): 23456789 (excludes '0', '1')\n */\nconst CHARSET =\n 'abcdefghijkmnpqrstuvwxyz' +\n 'ABCDEFGHJKLMNPQRSTUVWXYZ' +\n '23456789';\n\nconst CHARSET_LEN = CHARSET.length; // 56\n\n/**\n * Generate a confusion-free random password.\n *\n * Uses `node:crypto.randomBytes` for cryptographic randomness.\n * Rejection sampling ensures uniform distribution: each random byte\n * is only accepted if it falls within a range that is evenly\n * divisible by the charset length, eliminating modulo bias.\n *\n * @param length - Password length. Defaults to 16, admin passwords use 20.\n * @returns A random password string containing only confusion-free characters.\n *\n * @example\n * ```ts\n * const userPassword = generatePassword(); // 16 chars\n * const adminPassword = generatePassword(20); // 20 chars\n * ```\n */\nexport function generatePassword(length: number = 16): string {\n if (length < 1) {\n throw new RangeError('Password length must be at least 1');\n }\n\n // Largest multiple of CHARSET_LEN that fits in a byte (0-255).\n // For CHARSET_LEN=57: floor(256/57)*57 = 4*57 = 228.\n // Any random byte >= 228 is rejected to avoid modulo bias.\n const limit = Math.floor(256 / CHARSET_LEN) * CHARSET_LEN;\n\n const result: string[] = [];\n\n while (result.length < length) {\n // Request a batch of random bytes (over-allocate to minimize iterations)\n const batchSize = Math.max(length - result.length, 32);\n const bytes = randomBytes(batchSize);\n\n for (let i = 0; i < bytes.length && result.length < length; i++) {\n const byte = bytes[i]!;\n if (byte < limit) {\n result.push(CHARSET[byte % CHARSET_LEN]!);\n }\n // else: reject (modulo bias range) and try the next byte\n }\n }\n\n return result.join('');\n}\n","/**\n * Brewnet CLI — Resource Estimation & Service Collection (T018)\n *\n * Computes container counts, RAM/disk estimates, service lists,\n * credential propagation targets, and Docker image names from\n * the wizard state.\n *\n * All RAM/disk values are sourced from the canonical RESOURCE_ESTIMATES\n * table (see docs/IMPLEMENT_SPEC.md and public/demo/js/wizard.js).\n *\n * @module utils/resources\n */\n\nimport type { WizardState } from '@brewnet/shared';\n\n// ---------------------------------------------------------------------------\n// Resource estimate maps (canonical values from data-model / IMPLEMENT_SPEC)\n// ---------------------------------------------------------------------------\n\n/**\n * Estimated RAM usage per service (in MB).\n */\nexport const SERVICE_RAM_MAP: Readonly<Record<string, number>> = {\n // Web Servers\n traefik: 45,\n nginx: 20,\n caddy: 30,\n\n // File Servers\n nextcloud: 256,\n minio: 256,\n\n // Media\n jellyfin: 256,\n\n // Databases\n postgresql: 120,\n mysql: 256,\n sqlite: 0,\n\n // DB Admin\n pgadmin: 128,\n\n // App Server (generic user-app container)\n app: 85,\n\n // SSH Server\n 'openssh-server': 16,\n\n // FileBrowser\n filebrowser: 32,\n\n // Cloudflare Tunnel\n cloudflared: 32,\n\n // Git Server\n gitea: 256,\n};\n\n/**\n * Estimated disk usage per service (in GB).\n */\nexport const SERVICE_DISK_MAP: Readonly<Record<string, number>> = {\n // Web Servers\n traefik: 0.1,\n nginx: 0.1,\n caddy: 0.1,\n\n // File Servers\n nextcloud: 0.5,\n minio: 0.5,\n\n // Media\n jellyfin: 0.5,\n\n // Databases\n postgresql: 0.5,\n mysql: 0.5,\n sqlite: 0.01,\n\n // DB Admin\n pgadmin: 0.2,\n\n // App\n app: 0.2,\n\n // SSH Server\n 'openssh-server': 0.05,\n\n // FileBrowser\n filebrowser: 0.1,\n\n // Cloudflare Tunnel\n cloudflared: 0.05,\n\n // Git Server\n gitea: 1.0,\n};\n\n/**\n * Maps service IDs to their canonical Docker image references.\n */\nconst DOCKER_IMAGE_MAP: Readonly<Record<string, string>> = {\n traefik: 'traefik:v3.0',\n nginx: 'nginx:1.25-alpine',\n caddy: 'caddy:2-alpine',\n nextcloud: 'nextcloud:29-apache',\n minio: 'minio/minio:latest',\n jellyfin: 'jellyfin/jellyfin:latest',\n postgresql: 'postgres:18.3-alpine',\n mysql: 'mysql:8.4',\n pgadmin: 'dpage/pgadmin4:latest',\n 'openssh-server': 'linuxserver/openssh-server:latest',\n filebrowser: 'filebrowser/filebrowser:latest',\n cloudflared: 'cloudflare/cloudflared:latest',\n gitea: 'gitea/gitea:latest',\n};\n\n// ---------------------------------------------------------------------------\n// Resource estimate result\n// ---------------------------------------------------------------------------\n\nexport interface ResourceEstimate {\n /** Total number of Docker containers. */\n containers: number;\n /** Estimated total RAM in MB. */\n ramMB: number;\n /** Formatted RAM string (e.g. \"1.2 GB\"). */\n ramGB: string;\n /** Estimated total disk in GB. */\n diskGB: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: safe map lookups\n// ---------------------------------------------------------------------------\n\nfunction ramFor(serviceId: string): number {\n return SERVICE_RAM_MAP[serviceId] ?? 0;\n}\n\nfunction diskFor(serviceId: string): number {\n return SERVICE_DISK_MAP[serviceId] ?? 0;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Count the number of Docker containers that will be created\n * for the given wizard state. Does not include Cloudflare Tunnel\n * or Gitea (use `collectAllServices` for the full list).\n *\n * Note: Gitea is always counted. Cloudflare Tunnel is counted\n * in `estimateResources` but not here — this mirrors the demo\n * wizard's `countSelectedServices` behaviour.\n */\nexport function countSelectedServices(state: WizardState): number {\n const s = state.servers;\n const ds = state.devStack;\n let count = 1; // Web server is always included\n\n // Gitea (required)\n count++;\n\n // File Server\n if (s.fileServer.enabled && s.fileServer.service) count++;\n\n // App Server (only counts if languages selected)\n if (s.appServer.enabled && (ds?.languages ?? []).length > 0) count++;\n\n // Database (non-sqlite)\n if (s.dbServer.enabled && s.dbServer.primary && s.dbServer.primary !== 'sqlite') {\n count++;\n // Admin UI (pgAdmin)\n if (s.dbServer.adminUI) count++;\n }\n\n // Media\n if (s.media.enabled) count += (s.media.services ?? []).length;\n\n // SSH Server\n if (s.sshServer?.enabled) count++;\n\n // FileBrowser (standalone mode only — directory mode is served by the web server)\n if (s.fileBrowser?.enabled && s.fileBrowser.mode === 'standalone') count++;\n\n return count;\n}\n\n/**\n * Estimate total RAM and disk usage for all selected services.\n * Includes Cloudflare Tunnel and Gitea.\n */\nexport function estimateResources(state: WizardState): ResourceEstimate {\n let ram = 0;\n let disk = 0;\n let containers = 0;\n const s = state.servers;\n\n // Web Server (always present)\n const proxy = s.webServer.service || 'traefik';\n ram += ramFor(proxy) || 40; // fallback 40 MB\n disk += diskFor(proxy) || 0.1;\n containers++;\n\n // Gitea (required)\n ram += ramFor('gitea');\n disk += diskFor('gitea');\n containers++;\n\n // File Server\n if (s.fileServer.enabled && s.fileServer.service) {\n ram += ramFor(s.fileServer.service) || 128;\n disk += diskFor(s.fileServer.service) || 0.5;\n containers++;\n }\n\n // Media\n if (s.media.enabled) {\n for (const svcId of s.media.services ?? []) {\n ram += ramFor(svcId) || 128;\n disk += diskFor(svcId) || 0.5;\n containers++;\n }\n }\n\n // Database\n if (s.dbServer.enabled && s.dbServer.primary) {\n if (s.dbServer.primary !== 'sqlite') {\n ram += ramFor(s.dbServer.primary) || 120;\n disk += diskFor(s.dbServer.primary) || 0.5;\n containers++;\n\n // Admin UI\n if (s.dbServer.adminUI) {\n ram += ramFor('pgadmin');\n disk += diskFor('pgadmin');\n containers++;\n }\n }\n\n }\n\n // App Server\n if (s.appServer.enabled && (state.devStack?.languages ?? []).length > 0) {\n ram += ramFor('app');\n disk += diskFor('app');\n containers++;\n }\n\n // FileBrowser (standalone mode)\n if (s.fileBrowser?.enabled && s.fileBrowser.mode === 'standalone') {\n ram += ramFor('filebrowser');\n disk += diskFor('filebrowser');\n containers++;\n }\n\n // SSH Server\n if (s.sshServer?.enabled) {\n ram += ramFor('openssh-server');\n disk += diskFor('openssh-server');\n containers++;\n }\n\n // Cloudflare Tunnel\n if (state.domain?.cloudflare?.enabled) {\n ram += ramFor('cloudflared');\n disk += diskFor('cloudflared');\n containers++;\n }\n\n return {\n containers,\n ramMB: Math.round(ram),\n ramGB: `${(ram / 1024).toFixed(1)} GB`,\n diskGB: parseFloat(disk.toFixed(1)),\n };\n}\n\n/**\n * Collect all service IDs that should appear in docker-compose.yml.\n * Returns an array of service ID strings in deterministic order.\n */\nexport function collectAllServices(state: WizardState): string[] {\n const ids: string[] = [];\n const s = state.servers;\n\n // Web server (always)\n ids.push(s.webServer.service || 'traefik');\n\n // Gitea (required)\n ids.push('gitea');\n\n // File server\n if (s.fileServer.enabled && s.fileServer.service) {\n ids.push(s.fileServer.service);\n }\n\n // Media\n if (s.media.enabled) {\n for (const m of s.media.services ?? []) {\n ids.push(m);\n }\n }\n\n // Database (non-sqlite)\n if (s.dbServer.enabled && s.dbServer.primary && s.dbServer.primary !== 'sqlite') {\n ids.push(s.dbServer.primary);\n if (s.dbServer.adminUI) {\n ids.push('pgadmin');\n }\n }\n\n // SSH Server\n if (s.sshServer?.enabled) {\n ids.push('openssh-server');\n }\n\n // FileBrowser (standalone)\n if (s.fileBrowser?.enabled && s.fileBrowser.mode === 'standalone') {\n ids.push('filebrowser');\n }\n\n // Cloudflare Tunnel\n if (state.domain?.cloudflare?.enabled) {\n ids.push('cloudflared');\n }\n\n return ids;\n}\n\n/**\n * List services that receive the admin credentials during setup.\n *\n * These services have their own user/admin accounts that Brewnet\n * will auto-configure with the same username/password from Step 2.\n */\nexport function getCredentialTargets(state: WizardState): string[] {\n const targets: string[] = [];\n const s = state.servers;\n\n // Gitea always receives admin credentials\n targets.push('Gitea');\n\n if (s.fileServer.enabled && s.fileServer.service === 'nextcloud') {\n targets.push('Nextcloud');\n }\n if (s.fileServer.enabled && s.fileServer.service === 'minio') {\n targets.push('MinIO');\n }\n if (s.dbServer.enabled && s.dbServer.adminUI && s.dbServer.primary !== 'sqlite') {\n targets.push('pgAdmin');\n }\n if (s.media.enabled && (s.media.services ?? []).includes('jellyfin')) {\n targets.push('Jellyfin');\n }\n if (s.sshServer?.enabled) {\n targets.push('SSH Server');\n }\n if (s.fileBrowser?.enabled) {\n targets.push('FileBrowser');\n }\n\n return targets;\n}\n\n/**\n * Get the canonical Docker image reference for a given service ID.\n *\n * @param serviceId - A service identifier (e.g. 'traefik', 'postgresql', 'gitea').\n * @returns The Docker image string (e.g. 'traefik:v3.0').\n * Falls back to `<serviceId>:latest` for unknown services.\n */\nexport function getImageName(serviceId: string): string {\n return DOCKER_IMAGE_MAP[serviceId] ?? `${serviceId}:latest`;\n}\n","/**\n * T078-T080 — Step 3: Dev Stack & Runtime\n *\n * Pure functions and interactive wizard step for configuring the development\n * stack: backend languages, per-language frameworks, frontend technologies,\n * FileBrowser mode, and boilerplate generation settings.\n *\n * Pure functions:\n * - buildDevStackState — Build a DevStackConfig, stripping stale frameworks\n * - applySkipDevStack — Clear devStack and disable appServer / fileBrowser\n * - getFilteredFrameworks — Return framework options for selected languages only\n * - isDevStackEmpty — Check whether devStack has any selections\n *\n * Interactive:\n * - runDevStackStep — Step 3 wizard UI\n *\n * @module wizard/steps/dev-stack\n */\n\nimport { select, confirm } from '@inquirer/prompts';\nimport chalk from 'chalk';\nimport type {\n WizardState,\n DevStackConfig,\n Language,\n FrontendTech,\n FileBrowserMode,\n DevMode,\n} from '@brewnet/shared';\nimport {\n LANGUAGE_REGISTRY,\n FRONTEND_REGISTRY,\n getFrameworksForLanguage,\n type FrameworkOption,\n} from '../../config/frameworks.js';\nimport { applyDevStackAutoEnables } from './server-components.js';\nimport { BOILERPLATE_REPO_URL } from '@brewnet/shared';\n\n// ---------------------------------------------------------------------------\n// Pure Functions\n// ---------------------------------------------------------------------------\n\n/**\n * Build a DevStackConfig from raw selections, stripping any framework entries\n * for languages that are no longer in the `languages` array.\n *\n * Returns a new object with no shared references to the input.\n *\n * @param selections - Raw user selections\n * @returns A clean DevStackConfig\n */\nexport function buildDevStackState(selections: {\n languages: Language[];\n frameworks: Record<string, string>;\n frontend: FrontendTech | null;\n}): DevStackConfig {\n const languages = [...selections.languages];\n const frontend = selections.frontend;\n\n // Only keep framework entries whose key is in the selected languages\n const frameworks: Record<string, string> = {};\n for (const lang of languages) {\n if (lang in selections.frameworks) {\n frameworks[lang] = selections.frameworks[lang];\n }\n }\n\n return { languages, frameworks, frontend };\n}\n\n/**\n * Apply the \"Skip Dev Stack\" action to a wizard state.\n * Clears all devStack selections and disables appServer and fileBrowser.\n *\n * Does NOT mutate the input state.\n *\n * @param state - Current wizard state\n * @returns New WizardState with devStack cleared and related servers disabled\n */\nexport function applySkipDevStack(state: WizardState): WizardState {\n const next = structuredClone(state);\n\n next.devStack.languages = [];\n next.devStack.frameworks = {};\n next.devStack.frontend = null;\n\n next.servers.appServer.enabled = false;\n next.servers.fileBrowser.enabled = false;\n\n return next;\n}\n\n/**\n * Return a record mapping each selected language to its available frameworks\n * from LANGUAGE_REGISTRY. Languages not in selectedLanguages are excluded.\n *\n * @param selectedLanguages - Languages the user has selected\n * @returns Record of language key to FrameworkOption[]\n */\nexport function getFilteredFrameworks(\n selectedLanguages: Language[],\n): Record<string, FrameworkOption[]> {\n const result: Record<string, FrameworkOption[]> = {};\n\n for (const lang of selectedLanguages) {\n result[lang] = getFrameworksForLanguage(lang);\n }\n\n return result;\n}\n\n/**\n * Check whether the devStack has any meaningful selections.\n * Returns true when no languages AND no frontend technology is selected.\n * Ignores stale framework entries — only languages and frontend matter.\n *\n * @param state - Current wizard state\n * @returns true if devStack is empty\n */\nexport function isDevStackEmpty(state: WizardState): boolean {\n return state.devStack.languages.length === 0 && state.devStack.frontend === null;\n}\n\n// ---------------------------------------------------------------------------\n// Interactive Step Function (T078-T080)\n// ---------------------------------------------------------------------------\n\n/**\n * Run Step 3: Dev Stack & Runtime.\n *\n * Interactively collects language, framework, frontend, FileBrowser, and\n * boilerplate preferences. Applies devStack auto-enables at the end.\n *\n * Flow:\n * 1. Display header \"Step 4/8 — Dev Stack & Runtime\"\n * 2. Skip prompt (if yes, call applySkipDevStack and return early)\n * 3. Language single-select (all 8 from LANGUAGE_REGISTRY + Skip option)\n * 4. Per-language framework selection (─── Framework Selection ─── separator)\n * 5. Frontend tech single-select (from FRONTEND_REGISTRY)\n * 6. Apply devStack auto-enables (appServer / fileBrowser)\n * 7. FileBrowser mode selection (if appServer auto-enabled)\n * 8. Boilerplate config (generate, sampleData, devMode)\n * 9. Show summary\n * 10. Return updated state\n *\n * @param state - Current wizard state\n * @returns Updated wizard state with devStack selections\n */\nexport async function runDevStackStep(\n state: WizardState,\n): Promise<WizardState> {\n let next = structuredClone(state);\n\n // -------------------------------------------------------------------------\n // 1. Display header\n // -------------------------------------------------------------------------\n console.log();\n console.log(\n chalk.bold.cyan(' Step 4/8') + chalk.bold(' — Developer Configuration & Runtime Setup'),\n );\n console.log(\n chalk.dim(\n ' Select backend languages, frameworks, and frontend technologies.',\n ),\n );\n console.log(\n chalk.dim(\n ' Skip any section by pressing Enter without selecting, or choose Skip at the end.',\n ),\n );\n console.log();\n\n // -------------------------------------------------------------------------\n // 3. Language single-select\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Backend Language'));\n console.log(chalk.dim(' ↑↓: 이동 Enter: 확정 (추후 추가설정 가능합니다)'));\n console.log();\n\n const languageChoices: Array<{ name: string; value: Language | 'skip' }> = [\n ...(Object.keys(LANGUAGE_REGISTRY) as Language[]).map((key) => ({\n name: LANGUAGE_REGISTRY[key].name,\n value: key as Language | 'skip',\n })),\n { name: 'Skip — 언어 선택 건너뛰기', value: 'skip' as const },\n ];\n\n const selectedLang = await select<Language | 'skip'>({\n message: 'Backend Language',\n choices: languageChoices,\n default: next.devStack.languages[0] ?? 'skip',\n });\n\n const selectedLanguages: Language[] =\n selectedLang === 'skip' ? [] : [selectedLang];\n\n if (selectedLanguages.length === 0) {\n console.log(chalk.dim(' (선택 없음 — 프레임워크 선택을 건너뜁니다)'));\n }\n console.log();\n\n // -------------------------------------------------------------------------\n // 4. Per-language framework selection (T019-T022 bug fix)\n // -------------------------------------------------------------------------\n const filteredFrameworks = getFilteredFrameworks(selectedLanguages);\n const frameworkSelections: Record<string, string> = {};\n\n if (selectedLanguages.length > 0) {\n console.log(chalk.bold(' ─── Framework Selection ───'));\n console.log(chalk.dim(' Choose a framework for each selected language'));\n console.log();\n }\n\n for (const lang of selectedLanguages) {\n const frameworks = filteredFrameworks[lang];\n\n // T019 guard: skip select() if frameworks array is empty\n if (frameworks.length === 0) {\n continue;\n }\n\n // T021: per-language version header\n const versionInfo: Record<Language, string> = {\n python: 'Python 3.13',\n nodejs: 'Node.js 22 LTS',\n java: 'Java 21 LTS',\n rust: 'Rust 1.88',\n go: 'Go 1.22+',\n kotlin: 'Kotlin 2.1',\n };\n console.log(chalk.bold(` ${versionInfo[lang] ?? LANGUAGE_REGISTRY[lang].name}`));\n\n const frameworkChoice = await select<string>({\n message: `${LANGUAGE_REGISTRY[lang].name} framework`,\n choices: frameworks.map((fw) => ({\n name: `${fw.name} — ${fw.description}`,\n value: fw.id,\n })),\n // T020 fix: defensive access to frameworks[0]?.id\n default: next.devStack.frameworks[lang] ?? frameworks[0]?.id ?? '',\n });\n frameworkSelections[lang] = frameworkChoice;\n console.log();\n }\n\n // -------------------------------------------------------------------------\n // 5. Frontend tech single-select (T023-T024)\n // Skip if a unified stack (Next.js) is selected — it has its own frontend.\n // -------------------------------------------------------------------------\n const hasUnifiedStack = Object.values(frameworkSelections).some(\n (fw) => fw === 'nextjs' || fw === 'nextjs-app',\n );\n\n let selectedFrontend: FrontendTech | null = null;\n\n if (hasUnifiedStack) {\n console.log(chalk.bold(' Frontend Technology'));\n console.log(chalk.dim(' Next.js 통합 스택이 선택되어 별도 프론트엔드가 필요하지 않습니다.'));\n console.log(chalk.green(' ✔ Skipped (Next.js includes frontend)'));\n console.log();\n } else {\n console.log(chalk.bold(' Frontend Technology'));\n console.log(chalk.dim(' Select a frontend framework (optional)'));\n console.log();\n\n const frontendChoices = (Object.keys(FRONTEND_REGISTRY) as FrontendTech[]).map(\n (key) => ({\n name: `${FRONTEND_REGISTRY[key].name} — ${FRONTEND_REGISTRY[key].description}`,\n value: key,\n }),\n );\n\n const selectedFrontendRaw = await select<FrontendTech>({\n message: 'Frontend',\n choices: frontendChoices,\n default: next.devStack.frontend ?? 'none',\n });\n\n // T024: 'none' maps to null\n selectedFrontend = selectedFrontendRaw === 'none' ? null : selectedFrontendRaw;\n }\n\n console.log();\n\n // -------------------------------------------------------------------------\n // Build devStack state (strips stale frameworks)\n // -------------------------------------------------------------------------\n next.devStack = buildDevStackState({\n languages: selectedLanguages,\n frameworks: frameworkSelections,\n frontend: selectedFrontend,\n });\n\n // -------------------------------------------------------------------------\n // 6. Apply devStack auto-enables (appServer / fileBrowser)\n // -------------------------------------------------------------------------\n next = applyDevStackAutoEnables(next);\n\n // -------------------------------------------------------------------------\n // 7. FileBrowser mode (if appServer auto-enabled)\n // -------------------------------------------------------------------------\n if (next.servers.appServer.enabled && next.servers.fileBrowser.enabled) {\n console.log(chalk.bold(' FileBrowser'));\n console.log(chalk.dim(' App Server detected — configure file browser mode'));\n console.log();\n\n const fileBrowserMode = await select<FileBrowserMode>({\n message: 'FileBrowser mode',\n choices: [\n {\n name: 'Directory — serve web server static files',\n value: 'directory' as FileBrowserMode,\n },\n {\n name: 'Standalone — dedicated FileBrowser container',\n value: 'standalone' as FileBrowserMode,\n },\n ],\n default: next.servers.fileBrowser.mode || 'directory',\n });\n next.servers.fileBrowser.mode = fileBrowserMode;\n console.log();\n }\n\n // -------------------------------------------------------------------------\n // 8. Boilerplate configuration\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Boilerplate'));\n console.log(chalk.dim(' Project scaffolding and template settings'));\n console.log(chalk.dim(` Templates: ${BOILERPLATE_REPO_URL}`));\n console.log();\n\n const generateBoilerplate = await confirm({\n message: 'Generate boilerplate project files?',\n default: next.boilerplate.generate,\n });\n next.boilerplate.generate = generateBoilerplate;\n\n if (generateBoilerplate) {\n next.boilerplate.sampleData = false;\n\n const devMode = await select<DevMode>({\n message: 'Development mode',\n choices: [\n {\n name: 'Hot-reload — auto-restart on file changes (choose this if you are still developing your app)',\n value: 'hot-reload' as DevMode,\n },\n {\n name: 'Production — optimized build (choose this if you are deploying a production-ready service)',\n value: 'production' as DevMode,\n },\n ],\n default: next.boilerplate.devMode || 'hot-reload',\n });\n next.boilerplate.devMode = devMode;\n } else {\n next.boilerplate.sampleData = false;\n next.boilerplate.devMode = 'hot-reload';\n }\n\n console.log();\n\n // -------------------------------------------------------------------------\n // 9. Summary (T028)\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Dev Stack Summary'));\n\n if (next.devStack.languages.length > 0) {\n const langNames = next.devStack.languages\n .map((l) => LANGUAGE_REGISTRY[l].name)\n .join(', ');\n console.log(chalk.dim(` Languages: ${langNames}`));\n\n // Show selected frameworks\n for (const lang of next.devStack.languages) {\n if (next.devStack.frameworks[lang]) {\n const fw = getFrameworksForLanguage(lang as Language).find(\n (f) => f.id === next.devStack.frameworks[lang],\n );\n if (fw) {\n console.log(chalk.dim(` ${LANGUAGE_REGISTRY[lang as Language].name} framework: ${fw.name}`));\n }\n }\n }\n } else {\n console.log(chalk.dim(' Languages: (none)'));\n }\n\n // T028: show single frontend name or \"(none)\"\n if (next.devStack.frontend !== null) {\n console.log(chalk.dim(` Frontend: ${FRONTEND_REGISTRY[next.devStack.frontend].name}`));\n } else {\n console.log(chalk.dim(' Frontend: (none)'));\n }\n\n console.log(chalk.dim(` App Server: ${next.servers.appServer.enabled ? 'enabled' : 'disabled'}`));\n\n if (next.servers.fileBrowser.enabled) {\n console.log(chalk.dim(` FileBrowser: ${next.servers.fileBrowser.mode || 'directory'}`));\n }\n\n if (next.boilerplate.generate) {\n console.log(chalk.dim(` Boilerplate: yes (mode: ${next.boilerplate.devMode})`));\n } else {\n console.log(chalk.dim(' Boilerplate: no'));\n }\n\n // -------------------------------------------------------------------------\n // 10. Apply or Skip — final action prompt\n // -------------------------------------------------------------------------\n console.log();\n\n const action = await select<'apply' | 'skip'>({\n message: 'Developer Configuration',\n choices: [\n {\n name: 'Apply — save configuration and continue to next step',\n value: 'apply',\n },\n {\n name: 'Skip — continue without Dev Stack (no App Server will be deployed)',\n value: 'skip',\n },\n ],\n default: 'apply',\n });\n\n console.log();\n\n if (action === 'skip') {\n const skipped = applySkipDevStack(next);\n console.log(chalk.yellow(' Dev Stack skipped. App Server and FileBrowser disabled.'));\n console.log();\n return skipped;\n }\n\n console.log(chalk.green(' Dev Stack configured.'));\n console.log();\n\n return next;\n}\n","/**\n * Step 4: Network Access — 5-Scenario Cloudflare Tunnel Setup\n *\n * Scenarios:\n * 1. Quick Tunnel — no account, instant *.trycloudflare.com URL\n * 2. Named Tunnel — existing CF domain, full API automation\n * 3. Named Tunnel — guided domain purchase + optional Quick Tunnel bridge\n * 4. Named Tunnel — create tunnel only, attach domain later\n * 5. Local only — no external access\n *\n * Pure functions:\n * - applyDomainDefaults — Apply provider-specific defaults to wizard state\n * - buildDomainConfig — Clean / normalize a DomainConfig object\n *\n * Interactive:\n * - runDomainNetworkStep — Step 4 wizard UI\n *\n * @module wizard/steps/domain-network\n */\n\nimport { input, select, confirm } from '@inquirer/prompts';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { execa } from 'execa';\nimport type {\n WizardState,\n DomainConfig,\n DomainProvider,\n} from '@brewnet/shared';\nimport {\n verifyToken,\n getAccounts,\n getZones,\n createTunnel,\n configureTunnelIngress,\n createDnsRecord,\n buildTokenCreationUrl,\n getActiveServiceRoutes,\n deleteTunnel,\n getTunnelHealth,\n} from '../../services/cloudflare-client.js';\nimport { TunnelLogger } from '../../utils/tunnel-logger.js';\nimport { QuickTunnelManager } from '../../services/quick-tunnel.js';\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nasync function tryOpenUrl(url: string): Promise<void> {\n try {\n if (process.platform === 'darwin') {\n await execa('open', [url]);\n } else {\n await execa('xdg-open', [url]);\n }\n } catch {\n // Non-fatal — URL already printed to console\n }\n}\n\n// ---------------------------------------------------------------------------\n// Pure Functions\n// ---------------------------------------------------------------------------\n\n/**\n * Apply provider-specific defaults to the wizard state's domain configuration.\n */\nexport function applyDomainDefaults(\n state: WizardState,\n provider: DomainProvider,\n): WizardState {\n const next = structuredClone(state);\n next.domain.provider = provider;\n\n switch (provider) {\n case 'local': {\n next.domain.ssl = 'self-signed';\n next.domain.cloudflare.enabled = false;\n next.domain.cloudflare.tunnelMode = 'none';\n next.domain.cloudflare.quickTunnelUrl = '';\n next.domain.cloudflare.tunnelToken = '';\n next.domain.cloudflare.tunnelName = '';\n next.domain.cloudflare.tunnelId = '';\n next.domain.cloudflare.accountId = '';\n next.domain.cloudflare.apiToken = '';\n next.domain.cloudflare.zoneId = '';\n next.domain.cloudflare.zoneName = '';\n next.domain.name = `${next.projectName}.local`;\n break;\n }\n case 'quick-tunnel': {\n next.domain.ssl = 'cloudflare';\n next.domain.cloudflare.enabled = true;\n next.domain.cloudflare.tunnelMode = 'quick';\n break;\n }\n case 'tunnel': {\n next.domain.ssl = 'cloudflare';\n next.domain.cloudflare.enabled = true;\n next.domain.cloudflare.tunnelMode = 'named';\n break;\n }\n }\n\n return next;\n}\n\n/**\n * Build a clean DomainConfig from raw selections.\n * Enforces provider-specific invariants. Always returns a new object.\n */\nexport function buildDomainConfig(config: DomainConfig): DomainConfig {\n const result: DomainConfig = {\n ...config,\n cloudflare: { ...config.cloudflare },\n };\n\n if (result.provider === 'local') {\n result.cloudflare.enabled = false;\n result.cloudflare.tunnelMode = 'none';\n result.cloudflare.quickTunnelUrl = '';\n result.cloudflare.tunnelToken = '';\n result.cloudflare.tunnelName = '';\n result.cloudflare.tunnelId = '';\n result.cloudflare.accountId = '';\n result.cloudflare.apiToken = '';\n result.cloudflare.zoneId = '';\n result.cloudflare.zoneName = '';\n } else {\n result.cloudflare.enabled = true;\n // Ensure apiToken is always cleared before returning\n result.cloudflare.apiToken = '';\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Interactive Step Function\n// ---------------------------------------------------------------------------\n\n/**\n * Run Step 4: Network Access.\n *\n * @param state - Current wizard state\n * @returns Updated wizard state with network configuration\n */\nexport async function runDomainNetworkStep(\n state: WizardState,\n): Promise<WizardState> {\n const next = structuredClone(state);\n const tunnelLogger = new TunnelLogger();\n\n // -------------------------------------------------------------------------\n // 1. Header\n // -------------------------------------------------------------------------\n console.log();\n console.log(\n chalk.bold.cyan(' Step 5/8') + chalk.bold(' — Network Access'),\n );\n console.log(\n chalk.dim(' 외부 접근 방식을 선택하세요.'),\n );\n console.log();\n\n // -------------------------------------------------------------------------\n // 2. 5-option scenario selector\n // -------------------------------------------------------------------------\n type ScenarioChoice = '1-quick' | '2-named-existing' | '3-named-buy' | '4-named-only' | '5-local';\n\n const scenario = await select<ScenarioChoice>({\n message: '외부 접근 방식을 선택하세요',\n choices: [\n {\n name: '1. Quick Tunnel (즉시 사용, 임시 URL — 도메인 불필요)',\n value: '1-quick' as const,\n description: '계정 없이 바로 시작. 단, 서버 재시작 시 URL이 변경됩니다.',\n },\n {\n name: '2. Named Tunnel — 기존 Cloudflare 도메인 연결 (영구 URL)',\n value: '2-named-existing' as const,\n description: 'Cloudflare 계정 + 도메인이 이미 있는 경우. API 토큰 1회 입력.',\n },\n {\n name: '3. Named Tunnel — 도메인 먼저 구입 후 연결 (안내 포함)',\n value: '3-named-buy' as const,\n description: '도메인 구입 가이드 제공. 임시 Quick Tunnel로 즉시 접근 가능.',\n },\n {\n name: '4. Named Tunnel만 생성 — 도메인은 나중에 연결',\n value: '4-named-only' as const,\n description: '터널만 준비. `brewnet domain connect`로 도메인 추가 가능.',\n },\n {\n name: '5. 로컬 전용 (외부 접근 없음)',\n value: '5-local' as const,\n description: '내부 네트워크에서만 접근. brewnet.local 도메인 사용.',\n },\n ],\n });\n\n console.log();\n\n // -------------------------------------------------------------------------\n // 3. Dispatch by scenario\n // -------------------------------------------------------------------------\n\n if (scenario === '5-local') {\n return runLocalScenario(next);\n }\n\n if (scenario === '1-quick') {\n return runQuickTunnelScenario(next, tunnelLogger);\n }\n\n if (scenario === '2-named-existing') {\n return runNamedTunnelWithDomainScenario(next, tunnelLogger);\n }\n\n if (scenario === '3-named-buy') {\n return runGuidedDomainPurchaseScenario(next, tunnelLogger);\n }\n\n // scenario === '4-named-only'\n return runNamedTunnelOnlyScenario(next, tunnelLogger);\n}\n\n// ---------------------------------------------------------------------------\n// Scenario 5: Local Only\n// ---------------------------------------------------------------------------\n\nfunction runLocalScenario(next: WizardState): WizardState {\n next.domain.provider = 'local';\n next.domain.name = `${next.projectName}.local`;\n next.domain.ssl = 'self-signed';\n next.domain.cloudflare.enabled = false;\n next.domain.cloudflare.tunnelMode = 'none';\n next.domain.cloudflare.quickTunnelUrl = '';\n next.domain.cloudflare.accountId = '';\n next.domain.cloudflare.apiToken = '';\n next.domain.cloudflare.tunnelId = '';\n next.domain.cloudflare.tunnelToken = '';\n next.domain.cloudflare.tunnelName = '';\n next.domain.cloudflare.zoneId = '';\n next.domain.cloudflare.zoneName = '';\n\n console.log(chalk.dim(` 접근: ${next.domain.name} (LAN 전용)`));\n console.log(chalk.dim(' 외부 접근: 비활성화'));\n console.log();\n console.log(chalk.green(' Network Access configured.'));\n console.log();\n\n return next;\n}\n\n// ---------------------------------------------------------------------------\n// Scenario 1: Quick Tunnel\n// ---------------------------------------------------------------------------\n\nasync function runQuickTunnelScenario(\n next: WizardState,\n _tunnelLogger: TunnelLogger,\n): Promise<WizardState> {\n console.log(chalk.bold(' Quick Tunnel'));\n console.log(chalk.dim(' Cloudflare 계정 없이 즉시 사용 가능한 임시 URL을 생성합니다.'));\n console.log();\n console.log(chalk.yellow(' ⚠️ 서버 재시작 시 URL이 변경됩니다. 영구 URL이 필요하면'));\n console.log(chalk.yellow(' 설치 완료 후 `brewnet domain connect`를 실행하세요.'));\n console.log();\n console.log(chalk.dim(' Quick Tunnel URL은 서비스 시작 후 자동으로 발급됩니다.'));\n console.log();\n\n // State 설정만 — 실제 컨테이너 시작은 Step 6 (docker compose up)에서\n next.domain.provider = 'quick-tunnel';\n next.domain.ssl = 'cloudflare';\n next.domain.cloudflare.enabled = true;\n next.domain.cloudflare.tunnelMode = 'quick';\n next.domain.cloudflare.quickTunnelUrl = '';\n next.domain.cloudflare.accountId = '';\n next.domain.cloudflare.apiToken = '';\n next.domain.cloudflare.tunnelId = '';\n next.domain.cloudflare.tunnelToken = '';\n next.domain.cloudflare.tunnelName = '';\n next.domain.cloudflare.zoneId = '';\n next.domain.cloudflare.zoneName = '';\n\n printNetworkSummary(next);\n return next;\n}\n\n// ---------------------------------------------------------------------------\n// Scenario 2: Named Tunnel with Existing Domain (core shared API flow)\n// ---------------------------------------------------------------------------\n\n/**\n * Shared Named Tunnel API flow used by Scenarios 2, 3, and 4.\n * Returns the updated state, or throws on unrecoverable error.\n *\n * @param includeDns - If false (Scenario 4), skips ingress config and DNS record creation.\n */\nasync function runNamedTunnelApiFlow(\n next: WizardState,\n tunnelLogger: TunnelLogger,\n includeDns: boolean,\n): Promise<WizardState> {\n // Step 1: Show token creation guide\n console.log(chalk.bold(' Cloudflare API Token — Setup Guide'));\n console.log();\n console.log(chalk.bold.white(' [1] Cloudflare 로그인'));\n console.log(chalk.dim(' https://dash.cloudflare.com → 로그인'));\n console.log();\n if (includeDns) {\n console.log(chalk.bold.white(' [2] 도메인을 Cloudflare에 추가 (처음 사용 시)'));\n console.log(chalk.dim(' 좌측 사이드바에서 \"Domains\" 클릭'));\n console.log(chalk.dim(' → \"Add a domain\" 버튼 → 도메인 입력 → Continue'));\n console.log(chalk.dim(' → Free 플랜 선택 → Continue → 네임서버 2개 확인'));\n console.log(chalk.dim(' → 도메인 등록업체에서 네임서버 교체 → 저장'));\n console.log(chalk.dim(' (네임서버 전파 최대 24시간 소요)'));\n console.log();\n }\n console.log(chalk.bold.white(' [3] API Token 생성'));\n console.log(chalk.dim(' 우측 상단 프로필 → My Profile → API Tokens → Create Token'));\n console.log(chalk.dim(' → \"Edit Cloudflare Tunnel\" 템플릿 → Use template'));\n console.log(chalk.dim(' → Zone Resources: 사용할 도메인 선택 → Continue → Create Token'));\n console.log();\n console.log(chalk.dim(' 필요 권한: Cloudflare Tunnel:Edit • DNS:Edit • Zone:Read'));\n console.log();\n\n const tokenUrl = buildTokenCreationUrl(next.projectName);\n console.log(chalk.dim(' 사전 설정된 토큰 생성 URL (브라우저에서 열림):'));\n console.log(` ${chalk.cyan(tokenUrl)}`);\n console.log();\n await tryOpenUrl(tokenUrl);\n\n // Step 2: Prompt for API token\n let apiToken = await input({\n message: 'Cloudflare API Token을 붙여넣으세요',\n default: '',\n validate: (v) => v.trim().length > 0 ? true : 'API Token이 필요합니다',\n });\n apiToken = apiToken.trim();\n console.log();\n\n // Step 3: Verify token (with retry)\n const verifySpinner = ora('API 토큰 검증 중...').start();\n let verifyResult: { valid: boolean; email?: string };\n try {\n verifyResult = await verifyToken(apiToken);\n } catch {\n verifyResult = { valid: false };\n }\n\n if (!verifyResult.valid) {\n verifySpinner.fail(chalk.red('유효하지 않은 API 토큰입니다. [BN004]'));\n console.log(chalk.dim(' Cloudflare 대시보드에서 토큰을 확인해주세요.'));\n console.log();\n throw new Error('API 토큰 검증 실패');\n }\n\n verifySpinner.succeed(\n chalk.green('토큰 검증 완료') +\n (verifyResult.email ? chalk.dim(` (계정: ${verifyResult.email})`) : ''),\n );\n console.log();\n\n // Step 4: Account auto-detection / selection\n const accountsSpinner = ora('Cloudflare 계정 조회 중...').start();\n let accounts: Array<{ id: string; name: string }> = [];\n try {\n accounts = await getAccounts(apiToken);\n } catch {\n accounts = [];\n }\n accountsSpinner.stop();\n\n let selectedAccountId: string;\n let selectedAccountName: string;\n\n if (accounts.length === 0) {\n console.log(chalk.yellow(' 계정을 찾을 수 없습니다. Account ID를 직접 입력해주세요.'));\n const manualAccountId = await input({\n message: 'Cloudflare Account ID',\n default: next.domain.cloudflare.accountId || '',\n validate: (v) => v.trim().length > 0 ? true : 'Account ID가 필요합니다',\n });\n selectedAccountId = manualAccountId.trim();\n selectedAccountName = selectedAccountId;\n } else if (accounts.length === 1) {\n selectedAccountId = accounts[0].id;\n selectedAccountName = accounts[0].name;\n console.log(chalk.dim(` 계정: ${selectedAccountName} (자동 선택)`));\n } else {\n selectedAccountId = await select<string>({\n message: 'Cloudflare 계정을 선택하세요',\n choices: accounts.map((a) => ({ name: a.name, value: a.id })),\n });\n selectedAccountName = accounts.find((a) => a.id === selectedAccountId)?.name ?? selectedAccountId;\n }\n\n next.domain.cloudflare.accountId = selectedAccountId;\n console.log();\n\n let selectedZoneId = '';\n let selectedZoneName = '';\n\n if (includeDns) {\n // Step 5: Zone (domain) selection\n const zonesSpinner = ora('DNS 존 조회 중...').start();\n let zones: Array<{ id: string; name: string; status: string }> = [];\n try {\n zones = await getZones(apiToken);\n } catch {\n zones = [];\n }\n zonesSpinner.stop();\n\n const activeZones = zones.filter((z) => z.status === 'active');\n\n if (activeZones.length === 0) {\n zonesSpinner.stop();\n console.log(chalk.yellow(' 활성 도메인이 없습니다. domains.cloudflare.com에서 도메인을 등록해주세요.'));\n console.log();\n throw new Error('Cloudflare 계정에 활성 도메인이 없습니다.');\n }\n\n if (activeZones.length === 1) {\n selectedZoneId = activeZones[0].id;\n selectedZoneName = activeZones[0].name;\n console.log(chalk.dim(` 도메인: ${selectedZoneName} (자동 선택)`));\n } else {\n selectedZoneId = await select<string>({\n message: '도메인(존)을 선택하세요',\n choices: activeZones.map((z) => ({ name: z.name, value: z.id })),\n });\n selectedZoneName = activeZones.find((z) => z.id === selectedZoneId)?.name ?? selectedZoneId;\n }\n\n next.domain.cloudflare.zoneId = selectedZoneId;\n next.domain.cloudflare.zoneName = selectedZoneName;\n next.domain.name = selectedZoneName;\n console.log();\n }\n\n // Step 6: Tunnel name\n const tunnelName = await input({\n message: '터널 이름',\n default: next.domain.cloudflare.tunnelName || next.projectName,\n });\n next.domain.cloudflare.tunnelName = tunnelName.trim();\n console.log();\n\n // Step 7: Create tunnel\n let createdTunnelId = '';\n const createSpinner = ora('Cloudflare 터널 생성 중...').start();\n try {\n const tunnelResult = await createTunnel(\n apiToken,\n selectedAccountId,\n next.domain.cloudflare.tunnelName,\n );\n createdTunnelId = tunnelResult.tunnelId;\n next.domain.cloudflare.tunnelId = tunnelResult.tunnelId;\n next.domain.cloudflare.tunnelToken = tunnelResult.tunnelToken;\n createSpinner.succeed(chalk.green(`터널 생성됨: ${next.domain.cloudflare.tunnelName}`));\n console.log(chalk.dim(` ID: ${tunnelResult.tunnelId}`));\n\n tunnelLogger.log({\n event: 'CREATE',\n tunnelMode: 'named',\n tunnelId: tunnelResult.tunnelId,\n tunnelName: next.domain.cloudflare.tunnelName,\n domain: selectedZoneName || undefined,\n detail: 'Named tunnel created successfully',\n });\n } catch (err) {\n createSpinner.fail(chalk.red('터널 생성에 실패했습니다. [BN009]'));\n console.log(chalk.yellow(` 오류: ${err instanceof Error ? err.message : String(err)}`));\n console.log();\n\n tunnelLogger.log({\n event: 'ROLLBACK',\n tunnelMode: 'named',\n detail: 'Rollback triggered: tunnel creation failed',\n error: err instanceof Error ? err.message : String(err),\n });\n\n throw new Error('터널 생성에 실패했습니다. 잠시 후 다시 시도해주세요. [BN009]');\n }\n console.log();\n\n if (includeDns) {\n // Step 8: Configure ingress rules\n const routes = getActiveServiceRoutes(next);\n if (routes.length > 0) {\n const ingressSpinner = ora('터널 인그레스 규칙 설정 중...').start();\n try {\n await configureTunnelIngress(\n apiToken,\n selectedAccountId,\n createdTunnelId,\n selectedZoneName,\n routes,\n );\n ingressSpinner.succeed(chalk.green(`인그레스 설정 완료 (${routes.length}개 서비스)`));\n } catch (err) {\n ingressSpinner.fail(chalk.red('인그레스 설정 실패'));\n console.log(chalk.yellow(` 오류: ${err instanceof Error ? err.message : String(err)}`));\n console.log();\n\n // Rollback: delete tunnel\n await rollbackTunnel(apiToken, selectedAccountId, createdTunnelId, tunnelLogger, 'ingress 설정 실패');\n throw new Error('설정 실패 — 터널 롤백 완료. 다시 시도하세요.');\n }\n console.log();\n\n // Step 9: Create DNS CNAME records\n const dnsSpinner = ora('DNS CNAME 레코드 생성 중...').start();\n const created: string[] = [];\n const failed: string[] = [];\n\n for (const route of routes) {\n try {\n await createDnsRecord(\n apiToken,\n selectedZoneId,\n createdTunnelId,\n route.subdomain,\n selectedZoneName,\n );\n created.push(`${route.subdomain}.${selectedZoneName}`);\n } catch (err) {\n failed.push(`${route.subdomain} (${err instanceof Error ? err.message : String(err)})`);\n }\n }\n\n // If ALL DNS records failed, rollback\n if (created.length === 0 && failed.length > 0) {\n dnsSpinner.fail(chalk.red('DNS 레코드 생성 실패'));\n for (const record of failed) {\n console.log(chalk.yellow(` 실패: ${record}`));\n }\n console.log();\n\n await rollbackTunnel(apiToken, selectedAccountId, createdTunnelId, tunnelLogger, 'DNS 레코드 생성 전체 실패');\n throw new Error('설정 실패 — 터널 롤백 완료. 다시 시도하세요.');\n }\n\n if (failed.length === 0) {\n dnsSpinner.succeed(chalk.green(`DNS 레코드 생성 완료 (${created.length}개)`));\n } else {\n dnsSpinner.warn(chalk.yellow(`DNS 레코드: ${created.length}개 생성, ${failed.length}개 실패`));\n }\n\n for (const record of created) {\n console.log(chalk.dim(` CNAME: ${record} → ${createdTunnelId}.cfargotunnel.com`));\n }\n for (const record of failed) {\n console.log(chalk.yellow(` 실패: ${record}`));\n }\n console.log();\n }\n\n // Step 10: Health verification — poll for 'healthy' status (30s timeout)\n const healthSpinner = ora('터널 연결 확인 중... (최대 30초)').start();\n try {\n await waitForTunnelHealthy(apiToken, selectedAccountId, createdTunnelId, 30_000);\n healthSpinner.succeed(chalk.green('터널이 정상 연결되었습니다 (healthy)'));\n } catch {\n healthSpinner.warn(chalk.yellow('터널 상태 확인 실패 (30초 초과)'));\n console.log(chalk.dim(' 터널이 백그라운드에서 계속 연결 시도 중일 수 있습니다.'));\n console.log(chalk.dim(' `brewnet domain tunnel status`로 상태를 확인하세요.'));\n }\n console.log();\n }\n\n // Step Final: Clear API token from state (security)\n next.domain.cloudflare.apiToken = '';\n\n return next;\n}\n\nasync function runNamedTunnelWithDomainScenario(\n next: WizardState,\n tunnelLogger: TunnelLogger,\n): Promise<WizardState> {\n next.domain.provider = 'tunnel';\n next.domain.ssl = 'cloudflare';\n next.domain.cloudflare.enabled = true;\n next.domain.cloudflare.tunnelMode = 'named';\n\n try {\n const updated = await runNamedTunnelApiFlow(next, tunnelLogger, true);\n printNetworkSummary(updated);\n return updated;\n } catch (err) {\n console.log(chalk.red(` 오류: ${err instanceof Error ? err.message : String(err)}`));\n console.log();\n console.log(chalk.yellow(' Local 모드로 전환합니다.'));\n return runLocalScenario(next);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Scenario 3: Guided Domain Purchase\n// ---------------------------------------------------------------------------\n\nasync function runGuidedDomainPurchaseScenario(\n next: WizardState,\n tunnelLogger: TunnelLogger,\n): Promise<WizardState> {\n console.log(chalk.bold(' 도메인 구입 안내'));\n console.log();\n console.log(chalk.bold.white(' Cloudflare에서 도메인을 등록하는 방법:'));\n console.log();\n console.log(chalk.dim(' 1. https://domains.cloudflare.com → 로그인 또는 계정 생성'));\n console.log(chalk.dim(' 2. 도메인 검색 → 등록 (연 $8~15 수준, .com 기준)'));\n console.log(chalk.dim(' 3. 도메인이 Cloudflare 네임서버로 자동 설정됩니다'));\n console.log(chalk.dim(' 4. 등록 완료까지 1~5분 소요'));\n console.log();\n\n // Offer Quick Tunnel bridge while waiting\n const useBridge = await confirm({\n message: 'Quick Tunnel로 임시 접근을 시작하겠습니까? (도메인 준비 중에도 서비스에 접근 가능)',\n default: true,\n });\n console.log();\n\n let qtManager: QuickTunnelManager | null = null;\n\n if (useBridge) {\n const spinner = ora('Quick Tunnel 시작 중...').start();\n try {\n qtManager = new QuickTunnelManager(tunnelLogger);\n const url = await qtManager.start();\n spinner.succeed(chalk.green(`임시 URL: ${url}`));\n console.log(chalk.dim(' 도메인 준비가 완료되면 아래에서 Enter를 눌러 Named Tunnel로 전환합니다.'));\n console.log();\n } catch (err) {\n spinner.fail(chalk.yellow(`Quick Tunnel 실패: ${err instanceof Error ? err.message : String(err)}`));\n console.log();\n qtManager = null;\n }\n }\n\n // Single-session wait: block until user confirms domain is ready\n await input({\n message: '도메인 설정 완료 후 Enter를 누르세요',\n default: '',\n });\n console.log();\n\n // Proceed with Named Tunnel setup\n next.domain.provider = 'tunnel';\n next.domain.ssl = 'cloudflare';\n next.domain.cloudflare.enabled = true;\n next.domain.cloudflare.tunnelMode = 'named';\n\n try {\n const updated = await runNamedTunnelApiFlow(next, tunnelLogger, true);\n\n // Stop Quick Tunnel bridge if it was running\n if (qtManager) {\n const stopSpinner = ora('임시 Quick Tunnel 중지 중...').start();\n try {\n await qtManager.stop();\n stopSpinner.succeed(chalk.green('Quick Tunnel 중지 완료'));\n } catch {\n stopSpinner.warn('Quick Tunnel 중지 실패 (수동으로 중지하세요)');\n }\n console.log();\n }\n\n printNetworkSummary(updated);\n return updated;\n } catch (err) {\n // Stop bridge on error too\n if (qtManager) {\n await qtManager.stop().catch(() => {/* best-effort */});\n }\n console.log(chalk.red(` 오류: ${err instanceof Error ? err.message : String(err)}`));\n console.log();\n console.log(chalk.yellow(' Local 모드로 전환합니다.'));\n return runLocalScenario(next);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Scenario 4: Named Tunnel Only (no DNS)\n// ---------------------------------------------------------------------------\n\nasync function runNamedTunnelOnlyScenario(\n next: WizardState,\n tunnelLogger: TunnelLogger,\n): Promise<WizardState> {\n console.log(chalk.bold(' Named Tunnel 생성 (도메인 연결은 나중에)'));\n console.log(chalk.dim(' 터널만 생성하고, 도메인 연결은 설치 완료 후'));\n console.log(chalk.dim(' `brewnet domain connect` 명령으로 진행할 수 있습니다.'));\n console.log();\n\n next.domain.provider = 'tunnel';\n next.domain.ssl = 'cloudflare';\n next.domain.cloudflare.enabled = true;\n next.domain.cloudflare.tunnelMode = 'named';\n next.domain.cloudflare.zoneId = '';\n next.domain.cloudflare.zoneName = '';\n\n try {\n // Run API flow without DNS (includeDns = false)\n const updated = await runNamedTunnelApiFlow(next, tunnelLogger, false);\n\n console.log(chalk.dim(' 도메인 연결: `brewnet domain connect`'));\n console.log();\n\n printNetworkSummary(updated);\n return updated;\n } catch (err) {\n console.log(chalk.red(` 오류: ${err instanceof Error ? err.message : String(err)}`));\n console.log();\n console.log(chalk.yellow(' Local 모드로 전환합니다.'));\n return runLocalScenario(next);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tunnel health polling helper\n// ---------------------------------------------------------------------------\n\nasync function waitForTunnelHealthy(\n apiToken: string,\n accountId: string,\n tunnelId: string,\n timeoutMs: number,\n): Promise<void> {\n const start = Date.now();\n const pollIntervalMs = 2_000;\n\n while (Date.now() - start < timeoutMs) {\n try {\n const health = await getTunnelHealth(apiToken, accountId, tunnelId);\n if (health.status === 'healthy' && health.connectorCount > 0) {\n return;\n }\n } catch {\n // Ignore transient errors during polling\n }\n await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));\n }\n\n throw new Error(`터널이 ${timeoutMs / 1000}초 내에 healthy 상태가 되지 않았습니다`);\n}\n\n// ---------------------------------------------------------------------------\n// Rollback helper\n// ---------------------------------------------------------------------------\n\nasync function rollbackTunnel(\n apiToken: string,\n accountId: string,\n tunnelId: string,\n tunnelLogger: TunnelLogger,\n reason: string,\n): Promise<void> {\n const rollbackSpinner = ora('터널 롤백 중...').start();\n try {\n await deleteTunnel(apiToken, accountId, tunnelId);\n rollbackSpinner.succeed(chalk.yellow('터널 롤백 완료'));\n\n tunnelLogger.log({\n event: 'ROLLBACK',\n tunnelMode: 'named',\n tunnelId,\n detail: `Rollback triggered: ${reason}`,\n });\n } catch (rollbackErr) {\n rollbackSpinner.fail(chalk.red('롤백 실패 (Cloudflare 대시보드에서 수동 삭제 필요)'));\n console.log(chalk.yellow(` 롤백 오류: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`));\n\n tunnelLogger.log({\n event: 'ROLLBACK',\n tunnelMode: 'named',\n tunnelId,\n detail: `Rollback failed: ${reason}`,\n error: rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr),\n });\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// Summary helper\n// ---------------------------------------------------------------------------\n\nfunction printNetworkSummary(state: WizardState): void {\n console.log(chalk.bold(' Network Summary'));\n if (state.domain.provider === 'local') {\n console.log(chalk.dim(' 접근: LAN 전용'));\n console.log(chalk.dim(` 호스트: ${state.domain.name}`));\n } else if (state.domain.provider === 'quick-tunnel') {\n console.log(chalk.dim(' 접근: Quick Tunnel (임시 URL)'));\n console.log(chalk.dim(` URL: ${state.domain.cloudflare.quickTunnelUrl}`));\n console.log(chalk.yellow(' ⚠️ 재시작 시 URL이 변경됩니다'));\n console.log(chalk.dim(' 영구 URL: `brewnet domain connect`'));\n } else {\n console.log(chalk.dim(' 접근: Named Tunnel (외부 접근 가능)'));\n console.log(chalk.dim(` 터널: ${state.domain.cloudflare.tunnelName}`));\n if (state.domain.cloudflare.tunnelId) {\n console.log(chalk.dim(` ID: ${state.domain.cloudflare.tunnelId}`));\n }\n if (state.domain.cloudflare.zoneName) {\n console.log(chalk.dim(` 도메인: ${state.domain.cloudflare.zoneName}`));\n } else {\n console.log(chalk.dim(' 도메인: 미설정 (`brewnet domain connect`로 연결)'));\n }\n console.log(chalk.dim(' SSL: Cloudflare 관리'));\n }\n console.log();\n console.log(chalk.green(' Network Access configured.'));\n console.log();\n}\n","/**\n * TunnelLogger — append-only NDJSON event log for Cloudflare Tunnel operations.\n *\n * Log location: ~/.brewnet/logs/tunnel.log\n * Format: One JSON object per line (NDJSON / JSON Lines).\n *\n * SECURITY: MUST NOT write apiToken or tunnelToken in any field.\n *\n * @module utils/tunnel-logger\n */\n\nimport * as fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { TunnelLogEvent } from '@brewnet/shared';\n\nconst LOG_DIR = path.join(os.homedir(), '.brewnet', 'logs');\nconst LOG_FILE = path.join(LOG_DIR, 'tunnel.log');\n\n// Fields that must never appear in log entries (security)\nconst FORBIDDEN_KEYS = new Set(['apiToken', 'tunnelToken']);\n\n/**\n * Sanitize a log event to ensure no credential fields are present.\n * Throws if a forbidden key is detected to surface bugs early.\n */\nfunction sanitize(event: TunnelLogEvent): TunnelLogEvent {\n const keys = Object.keys(event);\n for (const key of keys) {\n if (FORBIDDEN_KEYS.has(key)) {\n throw new Error(`TunnelLogger: forbidden field \"${key}\" in log event`);\n }\n }\n return event;\n}\n\n/**\n * Write a tunnel event to ~/.brewnet/logs/tunnel.log.\n *\n * Creates the log file and parent directories if they do not exist.\n * Each call appends one NDJSON line. Non-blocking (sync write for simplicity —\n * log events are low-frequency, so sync I/O is acceptable here).\n */\nexport function logTunnelEvent(event: TunnelLogEvent): void {\n const sanitized = sanitize(event);\n const line = JSON.stringify(sanitized) + '\\n';\n\n fs.mkdirSync(LOG_DIR, { recursive: true });\n fs.appendFileSync(LOG_FILE, line, 'utf8');\n}\n\n/**\n * TunnelLogger — class interface for consumers that prefer object-style usage.\n */\nexport class TunnelLogger {\n /**\n * Append a tunnel event to the persistent log file.\n * Automatically sets `timestamp` to the current ISO 8601 UTC string\n * if the event does not already have one.\n */\n log(event: Omit<TunnelLogEvent, 'timestamp'> & { timestamp?: string }): void {\n const withTimestamp: TunnelLogEvent = {\n ...event,\n timestamp: event.timestamp ?? new Date().toISOString(),\n } as TunnelLogEvent;\n logTunnelEvent(withTimestamp);\n }\n}\n","/**\n * QuickTunnelManager — manages Cloudflare Quick Tunnel via Docker.\n *\n * Quick Tunnel runs `cloudflared tunnel --url http://traefik:80` and\n * auto-assigns a *.trycloudflare.com URL. No Cloudflare account is required.\n *\n * The manager:\n * 1. Starts the cloudflared container (quick-tunnel variant)\n * 2. Streams container logs to extract the assigned *.trycloudflare.com URL\n * 3. Exposes the URL for use in the wizard and Step 7 display\n * 4. Provides a stop() method to shut down the container\n *\n * URL regex: /https?:\\/\\/([\\w-]+\\.trycloudflare\\.com)/i\n * Timeout: 30 seconds to capture URL\n *\n * @module services/quick-tunnel\n */\n\nimport Dockerode from 'dockerode';\nimport type { TunnelLogger } from '../utils/tunnel-logger.js';\n\nconst CONTAINER_NAME = 'brewnet-tunnel-quick';\nconst CLOUDFLARED_IMAGE = 'cloudflare/cloudflared:latest';\n// Real quick-tunnel URLs always contain at least one hyphen in the subdomain\n// (e.g. \"purple-meadow-abc123.trycloudflare.com\").\n// This prevents matching internal CF API endpoints like \"api.trycloudflare.com\"\n// that appear in cloudflared logs before the actual tunnel URL is assigned.\nconst URL_REGEX = /https?:\\/\\/([\\w]+-[\\w][\\w-]*\\.trycloudflare\\.com)/i;\nconst URL_TIMEOUT_MS = 30_000;\n\nexport class QuickTunnelManager {\n private docker: Dockerode;\n private logger: TunnelLogger;\n private capturedUrl = '';\n private containerId = '';\n\n constructor(logger: TunnelLogger, dockerSocket?: string) {\n this.docker = dockerSocket\n ? new Dockerode({ socketPath: dockerSocket })\n : new Dockerode();\n this.logger = logger;\n }\n\n /** Returns the last captured Quick Tunnel URL, or empty string if not yet started. */\n getUrl(): string {\n return this.capturedUrl;\n }\n\n /**\n * Start the Quick Tunnel container and wait for the *.trycloudflare.com URL.\n *\n * @returns The assigned tunnel URL (e.g. \"https://purple-meadow.trycloudflare.com\")\n * @throws If no URL is captured within 30 seconds\n */\n async start(): Promise<string> {\n // Remove any existing container with the same name\n await this.removeExistingContainer();\n\n // Pull image if not present (best-effort)\n await this.ensureImage();\n\n // Create and start container\n const container = await this.docker.createContainer({\n Image: CLOUDFLARED_IMAGE,\n name: CONTAINER_NAME,\n Cmd: ['tunnel', '--no-autoupdate', '--url', 'http://traefik:80'],\n HostConfig: {\n NetworkMode: 'brewnet',\n RestartPolicy: { Name: 'unless-stopped' },\n },\n });\n\n this.containerId = container.id;\n await container.start();\n\n // Stream logs and extract URL\n const url = await this.captureUrl(container);\n this.capturedUrl = url;\n\n this.logger.log({\n event: 'QUICK_START',\n tunnelMode: 'quick',\n detail: 'Quick Tunnel started',\n quickTunnelUrl: url,\n } as Parameters<TunnelLogger['log']>[0]);\n\n return url;\n }\n\n /**\n * Stop and remove the Quick Tunnel container.\n */\n async stop(): Promise<void> {\n const name = this.containerId || CONTAINER_NAME;\n try {\n const container = this.containerId\n ? this.docker.getContainer(this.containerId)\n : this.docker.getContainer(CONTAINER_NAME);\n\n await container.stop({ t: 5 }).catch(() => {/* already stopped */});\n await container.remove({ force: true }).catch(() => {/* already removed */});\n\n this.capturedUrl = '';\n this.containerId = '';\n\n this.logger.log({\n event: 'QUICK_STOP',\n tunnelMode: 'quick',\n detail: `Quick Tunnel container stopped (${name})`,\n });\n } catch {\n // Container may not exist — not an error\n }\n }\n\n // ── Private helpers ────────────────────────────────────────────────────────\n\n private async removeExistingContainer(): Promise<void> {\n try {\n const existing = this.docker.getContainer(CONTAINER_NAME);\n await existing.remove({ force: true });\n } catch {\n // Container does not exist — OK\n }\n }\n\n private async ensureImage(): Promise<void> {\n try {\n await this.docker.getImage(CLOUDFLARED_IMAGE).inspect();\n } catch {\n // Image not found — pull it\n await new Promise<void>((resolve, reject) => {\n this.docker.pull(CLOUDFLARED_IMAGE, (err: Error | null, stream: NodeJS.ReadableStream) => {\n if (err) return reject(err);\n this.docker.modem.followProgress(stream, (err2: Error | null) => {\n if (err2) return reject(err2);\n resolve();\n });\n });\n });\n }\n }\n\n private captureUrl(container: Dockerode.Container): Promise<string> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error('Quick Tunnel 시작 실패: 30초 내에 URL을 얻지 못했습니다.'));\n }, URL_TIMEOUT_MS);\n\n container.logs(\n { follow: true, stdout: true, stderr: true, tail: 0 },\n (err, stream) => {\n if (err || !stream) {\n clearTimeout(timer);\n return reject(err ?? new Error('컨테이너 로그 스트림을 열 수 없습니다.'));\n }\n\n const onData = (chunk: Buffer) => {\n // Docker log stream: 8-byte header + payload\n const text = chunk.toString('utf8');\n const match = URL_REGEX.exec(text);\n if (match) {\n clearTimeout(timer);\n stream.removeListener('data', onData);\n stream.destroy();\n // Use capture group 1 (domain only) to build a clean https URL.\n // Avoid using match[0] with replace(/\\/.*$/) which incorrectly\n // strips the // from the https:// protocol prefix.\n resolve(`https://${match[1]}`);\n }\n };\n\n stream.on('data', onData);\n stream.on('error', (streamErr) => {\n clearTimeout(timer);\n reject(streamErr);\n });\n },\n );\n });\n }\n}\n","/**\n * T070 — Step 5: Review & Confirm\n *\n * Pure data functions and interactive step for the Review screen:\n * - generateReviewSections(): renders wizard selections into organized sections\n * - exportConfig(): writes a sanitized brewnet.config.json (no secrets)\n * - importConfig(): reads config JSON and restores WizardState\n * - runReviewStep(): interactive review flow with Generate/Modify/Export\n *\n * @module wizard/steps/review\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { select, input } from '@inquirer/prompts';\nimport chalk from 'chalk';\nimport type { WizardState } from '@brewnet/shared';\nimport { validateBrewnetConfig } from '@brewnet/shared';\nimport type { BrewnetConfig } from '@brewnet/shared';\nimport { WizardStep, STEP_NAMES } from '../navigation.js';\nimport {\n estimateResources,\n getCredentialTargets,\n} from '../../utils/resources.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ReviewItem {\n label: string;\n value: string;\n}\n\nexport interface ReviewSection {\n id: string;\n title: string;\n step: WizardStep;\n items: ReviewItem[];\n}\n\nexport interface ReviewResult {\n action: 'generate' | 'modify' | 'export';\n modifyStep?: WizardStep;\n exportPath?: string;\n}\n\n// ---------------------------------------------------------------------------\n// generateReviewSections\n// ---------------------------------------------------------------------------\n\n/**\n * Generate organized review sections from the wizard state.\n * Each section corresponds to a wizard step and lists the relevant selections.\n */\nexport function generateReviewSections(state: WizardState): ReviewSection[] {\n const sections: ReviewSection[] = [];\n\n // --- Project section (Step 1) ---\n const projectItems: ReviewItem[] = [\n { label: 'Project Name', value: state.projectName },\n { label: 'Project Path', value: state.projectPath },\n { label: 'Setup Type', value: state.setupType === 'full' ? 'Full Install' : 'Partial Install' },\n ];\n sections.push({\n id: 'project',\n title: 'Project Setup',\n step: WizardStep.ProjectSetup,\n items: projectItems,\n });\n\n // --- Admin section (Step 2 — top half) ---\n const adminItems: ReviewItem[] = [\n { label: 'Admin Username', value: state.admin.username },\n { label: 'Admin Password', value: '••••••••' }, // masked for security\n { label: 'Credential Storage', value: state.admin.storage },\n ];\n sections.push({\n id: 'admin',\n title: 'Admin Account',\n step: WizardStep.ServerComponents,\n items: adminItems,\n });\n\n // --- Server Components section (Step 2 — bottom half) ---\n const serverItems: ReviewItem[] = [];\n\n // Web Server (always enabled)\n serverItems.push({ label: 'Web Server', value: state.servers.webServer.service });\n\n // Git Server (always enabled)\n serverItems.push({\n label: 'Git Server',\n value: `${state.servers.gitServer.service} (port ${state.servers.gitServer.port}, SSH ${state.servers.gitServer.sshPort})`,\n });\n\n // File Server\n if (state.servers.fileServer.enabled && state.servers.fileServer.service) {\n serverItems.push({ label: 'File Server', value: state.servers.fileServer.service });\n }\n\n // Database\n if (state.servers.dbServer.enabled && state.servers.dbServer.primary) {\n const dbLabel = `${state.servers.dbServer.primary} ${state.servers.dbServer.primaryVersion}`.trim();\n serverItems.push({ label: 'Database', value: dbLabel });\n if (state.servers.dbServer.cache) {\n serverItems.push({ label: 'Cache', value: state.servers.dbServer.cache });\n }\n if (state.servers.dbServer.adminUI) {\n serverItems.push({ label: 'DB Admin UI', value: 'Enabled' });\n }\n }\n\n // Media\n if (state.servers.media.enabled && state.servers.media.services.length > 0) {\n serverItems.push({ label: 'Media', value: state.servers.media.services.join(', ') });\n }\n\n // SSH\n if (state.servers.sshServer.enabled) {\n const sshDesc = `Port ${state.servers.sshServer.port}${state.servers.sshServer.sftp ? ' + SFTP' : ''}`;\n serverItems.push({ label: 'SSH Server', value: sshDesc });\n }\n\n // FileBrowser\n if (state.servers.fileBrowser.enabled && state.servers.fileBrowser.mode) {\n serverItems.push({ label: 'File Browser', value: state.servers.fileBrowser.mode });\n }\n\n sections.push({\n id: 'servers',\n title: 'Server Components',\n step: WizardStep.ServerComponents,\n items: serverItems,\n });\n\n // --- Dev Stack section (Step 3) ---\n const devItems: ReviewItem[] = [];\n if (state.devStack.languages.length > 0) {\n devItems.push({ label: 'Languages', value: state.devStack.languages.join(', ') });\n for (const [lang, fw] of Object.entries(state.devStack.frameworks)) {\n if (fw) {\n devItems.push({ label: `${lang} Framework`, value: fw });\n }\n }\n }\n if (state.devStack.frontend !== null) {\n devItems.push({ label: 'Frontend', value: state.devStack.frontend });\n }\n if (devItems.length === 0) {\n devItems.push({ label: 'Dev Stack', value: 'Skipped' });\n }\n sections.push({\n id: 'devStack',\n title: 'Dev Stack & Runtime',\n step: WizardStep.DevStack,\n items: devItems,\n });\n\n // --- Network Access section (Step 4) ---\n const domainItems: ReviewItem[] = [];\n if (state.domain.provider === 'local') {\n domainItems.push({ label: 'Access', value: 'LAN only' });\n domainItems.push({ label: 'Host', value: state.domain.name });\n } else {\n domainItems.push({ label: 'Access', value: 'Cloudflare Tunnel' });\n domainItems.push({ label: 'Tunnel name', value: state.domain.cloudflare.tunnelName || '(not set)' });\n if (state.domain.cloudflare.tunnelId) {\n domainItems.push({ label: 'Tunnel ID', value: state.domain.cloudflare.tunnelId });\n }\n domainItems.push({ label: 'Domain', value: state.domain.cloudflare.zoneName || '(pending)' });\n domainItems.push({ label: 'SSL', value: 'Cloudflare (managed)' });\n }\n sections.push({\n id: 'domain',\n title: 'Network Access',\n step: WizardStep.DomainNetwork,\n items: domainItems,\n });\n\n // --- Resource Estimate section ---\n const resources = estimateResources(state);\n const resourceItems: ReviewItem[] = [\n { label: 'Containers', value: String(resources.containers) },\n { label: 'Estimated RAM', value: resources.ramGB },\n { label: 'Estimated Disk', value: `${resources.diskGB} GB` },\n ];\n sections.push({\n id: 'resources',\n title: 'Resource Estimate',\n step: WizardStep.Review,\n items: resourceItems,\n });\n\n // --- Credential Propagation section ---\n const targets = getCredentialTargets(state);\n if (targets.length > 0) {\n sections.push({\n id: 'credentials',\n title: 'Credential Propagation',\n step: WizardStep.ServerComponents,\n items: targets.map((t) => ({ label: t, value: `← ${state.admin.username}` })),\n });\n }\n\n return sections;\n}\n\n// ---------------------------------------------------------------------------\n// exportConfig\n// ---------------------------------------------------------------------------\n\n/**\n * Export wizard state to a sanitized config file (no secrets).\n * Writes the file to `<projectPath>/brewnet.config.json`.\n * Returns the absolute path of the written file.\n */\nexport function exportConfig(state: WizardState, projectPath: string): string {\n // Strip sensitive fields\n const config: BrewnetConfig = {\n schemaVersion: state.schemaVersion,\n projectName: state.projectName,\n projectPath: state.projectPath,\n setupType: state.setupType,\n admin: {\n username: state.admin.username,\n storage: state.admin.storage,\n },\n servers: {\n webServer: { ...state.servers.webServer },\n fileServer: { ...state.servers.fileServer },\n gitServer: { ...state.servers.gitServer },\n dbServer: {\n enabled: state.servers.dbServer.enabled,\n primary: state.servers.dbServer.primary,\n primaryVersion: state.servers.dbServer.primaryVersion,\n dbName: state.servers.dbServer.dbName,\n dbUser: state.servers.dbServer.dbUser,\n adminUI: state.servers.dbServer.adminUI,\n pgadminEmail: state.servers.dbServer.pgadminEmail,\n cache: state.servers.dbServer.cache,\n // dbPassword excluded\n },\n media: { ...state.servers.media },\n sshServer: { ...state.servers.sshServer },\n appServer: { ...state.servers.appServer },\n fileBrowser: { ...state.servers.fileBrowser },\n },\n devStack: { ...state.devStack },\n boilerplate: { ...state.boilerplate },\n domain: {\n provider: state.domain.provider,\n name: state.domain.name,\n ssl: state.domain.ssl,\n cloudflare: {\n enabled: state.domain.cloudflare.enabled,\n tunnelName: state.domain.cloudflare.tunnelName,\n // tunnelToken excluded\n },\n },\n domainConnections: state.domainConnections ?? [],\n } as BrewnetConfig;\n\n mkdirSync(projectPath, { recursive: true });\n const filePath = join(projectPath, 'brewnet.config.json');\n writeFileSync(filePath, JSON.stringify(config, null, 2), 'utf-8');\n return filePath;\n}\n\n// ---------------------------------------------------------------------------\n// importConfig\n// ---------------------------------------------------------------------------\n\n/**\n * Import a config file and restore it to a WizardState.\n * Sensitive fields are populated with empty defaults (to be re-generated).\n */\nexport function importConfig(configPath: string): WizardState {\n const raw = readFileSync(configPath, 'utf-8');\n const parsed = JSON.parse(raw);\n\n // Validate against the config schema first\n const config = validateBrewnetConfig(parsed);\n\n // Reconstruct full WizardState by adding back secret fields with defaults\n const state: WizardState = {\n schemaVersion: config.schemaVersion,\n projectName: config.projectName,\n projectPath: config.projectPath,\n setupType: config.setupType,\n admin: {\n username: config.admin.username,\n password: '', // must be re-generated\n storage: config.admin.storage,\n },\n servers: {\n webServer: config.servers.webServer,\n fileServer: config.servers.fileServer,\n gitServer: config.servers.gitServer,\n dbServer: {\n ...config.servers.dbServer,\n dbPassword: '', // must be re-generated\n },\n media: config.servers.media,\n sshServer: config.servers.sshServer,\n appServer: config.servers.appServer,\n fileBrowser: config.servers.fileBrowser,\n },\n devStack: config.devStack,\n boilerplate: config.boilerplate,\n domain: {\n ...config.domain,\n cloudflare: {\n enabled: config.domain.cloudflare.enabled,\n tunnelName: config.domain.cloudflare.tunnelName,\n // Derive tunnelMode from domain.provider: quick-tunnel → 'quick', else 'none'.\n // BrewnetConfig omits tunnelMode (export-safe subset), so infer from provider.\n tunnelMode: config.domain.provider === 'quick-tunnel' ? 'quick' : 'none',\n quickTunnelUrl: '', // ephemeral, not preserved across sessions\n accountId: '',\n apiToken: '',\n tunnelId: '',\n tunnelToken: '', // must be re-supplied for named tunnels\n zoneId: '',\n zoneName: '',\n },\n },\n domainConnections: [],\n portRemapping: {}, // not persisted in config; reset fresh on each import\n };\n\n return state;\n}\n\n// ---------------------------------------------------------------------------\n// runReviewStep (interactive)\n// ---------------------------------------------------------------------------\n\n/**\n * Run Step 5: Review & Confirm.\n *\n * Displays all selections in organized sections, shows resource estimates\n * and credential propagation summary, then prompts user for action:\n * - Generate: proceed to Step 6\n * - Modify: jump back to a specific step\n * - Export: write config to file and return\n *\n * @param state - Current wizard state\n * @returns ReviewResult indicating the chosen action\n */\nexport async function runReviewStep(state: WizardState): Promise<ReviewResult> {\n // -------------------------------------------------------------------------\n // 1. Display header\n // -------------------------------------------------------------------------\n console.log();\n console.log(\n chalk.bold.cyan(' Step 6/8') + chalk.bold(' — Review & Confirm'),\n );\n console.log(chalk.dim(' Review your selections before generating'));\n console.log();\n\n // -------------------------------------------------------------------------\n // 2. Display all sections\n // -------------------------------------------------------------------------\n const sections = generateReviewSections(state);\n\n for (const section of sections) {\n console.log(chalk.bold(` ${section.title}`));\n for (const item of section.items) {\n console.log(` ${chalk.dim(item.label + ':')} ${item.value}`);\n }\n console.log();\n }\n\n // -------------------------------------------------------------------------\n // 3. Prompt: Generate / Modify / Export\n // -------------------------------------------------------------------------\n const action = await select<'generate' | 'modify' | 'export'>({\n message: 'What would you like to do?',\n choices: [\n { name: 'Generate — proceed to build and start', value: 'generate' },\n { name: 'Modify — go back and change a section', value: 'modify' },\n { name: 'Export — save configuration to file', value: 'export' },\n ],\n });\n\n // -------------------------------------------------------------------------\n // 4. Handle action\n // -------------------------------------------------------------------------\n if (action === 'export') {\n const exportDir = await input({\n message: 'Export directory',\n default: state.projectPath,\n });\n\n const exportPath = exportConfig(state, exportDir);\n console.log();\n console.log(chalk.green(` Configuration exported to: ${exportPath}`));\n console.log(chalk.dim(' Note: Secrets (passwords, tokens) are excluded from export.'));\n console.log();\n\n return { action: 'export', exportPath };\n }\n\n if (action === 'modify') {\n // Build choices from sections (deduplicated by step)\n const stepSet = new Map<WizardStep, string>();\n for (const section of sections) {\n if (!stepSet.has(section.step) && section.step !== WizardStep.Review) {\n stepSet.set(section.step, section.title);\n }\n }\n\n const modifyStep = await select<WizardStep>({\n message: 'Which section would you like to modify?',\n choices: [...stepSet.entries()].map(([step, title]) => ({\n name: `${STEP_NAMES[step]} — ${title}`,\n value: step,\n })),\n });\n\n return { action: 'modify', modifyStep };\n }\n\n // Generate\n return { action: 'generate' };\n}\n","/**\n * T023 — Wizard Navigation State Machine\n *\n * Manages step progression, history stack, forward/backward navigation,\n * step skipping, and cancellation for the 9-step wizard flow.\n *\n * @module wizard/navigation\n */\n\n// ---------------------------------------------------------------------------\n// Step Enum\n// ---------------------------------------------------------------------------\n\nexport enum WizardStep {\n AdminSetup = 0,\n SystemCheck = 1,\n ProjectSetup = 2,\n ServerComponents = 3,\n DevStack = 4,\n DomainNetwork = 5,\n Review = 6,\n Generate = 7,\n Complete = 8,\n}\n\nexport const TOTAL_STEPS = 9;\n\nexport const STEP_NAMES: Record<WizardStep, string> = {\n [WizardStep.AdminSetup]: 'Admin Account',\n [WizardStep.SystemCheck]: 'System Check',\n [WizardStep.ProjectSetup]: 'Project Setup',\n [WizardStep.ServerComponents]: 'Server Components',\n [WizardStep.DevStack]: 'Dev Stack & Runtime',\n [WizardStep.DomainNetwork]: 'Domain & Network',\n [WizardStep.Review]: 'Review & Confirm',\n [WizardStep.Generate]: 'Generate & Start',\n [WizardStep.Complete]: 'Complete',\n};\n\n// ---------------------------------------------------------------------------\n// Navigation State Machine\n// ---------------------------------------------------------------------------\n\nexport class WizardNavigation {\n private _currentStep: WizardStep;\n private _history: WizardStep[];\n private _skippedSteps: Set<WizardStep>;\n private _cancelled: boolean;\n\n constructor(startStep: WizardStep = WizardStep.AdminSetup) {\n this._currentStep = startStep;\n this._history = [];\n this._skippedSteps = new Set();\n this._cancelled = false;\n }\n\n /** Current wizard step. */\n get currentStep(): WizardStep {\n return this._currentStep;\n }\n\n /** Whether the wizard has been cancelled. */\n get isCancelled(): boolean {\n return this._cancelled;\n }\n\n /**\n * Move forward to the next step.\n * Automatically skips steps that are in the skipped set.\n * Returns the new current step.\n * No-op if already at the Complete step.\n */\n goForward(): WizardStep {\n if (this._currentStep >= WizardStep.Complete) {\n return this._currentStep;\n }\n\n this._history.push(this._currentStep);\n let next = this._currentStep + 1;\n\n // Skip over skipped steps\n while (\n next < WizardStep.Complete &&\n this._skippedSteps.has(next as WizardStep)\n ) {\n next++;\n }\n\n this._currentStep = next as WizardStep;\n return this._currentStep;\n }\n\n /**\n * Go back to the previous step from the history stack.\n * Returns the previous step, or null if at the start.\n */\n goBack(): WizardStep | null {\n if (this._history.length === 0) return null;\n\n this._currentStep = this._history.pop()!;\n return this._currentStep;\n }\n\n /** Whether there are previous steps in the history to go back to. */\n canGoBack(): boolean {\n return this._history.length > 0;\n }\n\n /**\n * Jump directly to a specific step (e.g., from Review \"Modify\" action).\n * Pushes the current step to history so the user can return.\n */\n goToStep(step: WizardStep): void {\n this._history.push(this._currentStep);\n this._currentStep = step;\n }\n\n /**\n * Mark a step as skipped. Skipped steps are bypassed during forward navigation.\n */\n skipStep(step: WizardStep): void {\n this._skippedSteps.add(step);\n }\n\n /**\n * Remove a step from the skipped set, making it visitable again.\n */\n unskipStep(step: WizardStep): void {\n this._skippedSteps.delete(step);\n }\n\n /** Whether a step is currently marked as skipped. */\n isStepSkipped(step: WizardStep): boolean {\n return this._skippedSteps.has(step);\n }\n\n /** Mark the wizard as cancelled. */\n cancel(): void {\n this._cancelled = true;\n }\n\n /**\n * Get progress information for display (e.g., \"Step 3/7\").\n */\n getProgress(): { current: number; total: number; percentage: number } {\n const total = TOTAL_STEPS - this._skippedSteps.size;\n const completedSteps = this._history.filter(\n (s) => !this._skippedSteps.has(s),\n ).length;\n return {\n current: completedSteps + 1,\n total,\n percentage: Math.round(((completedSteps + 1) / total) * 100),\n };\n }\n\n /** Get the human-readable name of the current or specified step. */\n getStepName(step?: WizardStep): string {\n return STEP_NAMES[step ?? this._currentStep] ?? 'Unknown';\n }\n\n /** Reset navigation to the initial state. */\n reset(): void {\n this._currentStep = WizardStep.AdminSetup;\n this._history = [];\n this._skippedSteps.clear();\n this._cancelled = false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Ctrl+C Handler\n// ---------------------------------------------------------------------------\n\n/**\n * Set up a Ctrl+C (SIGINT) handler for the wizard.\n *\n * When the user presses Ctrl+C, the provided `onCancel` callback is invoked.\n * The callback should handle confirmation prompting and state saving.\n *\n * If Ctrl+C is pressed a second time while the first handler is running,\n * the process exits immediately (force quit).\n *\n * Returns a cleanup function to remove the handler.\n *\n * NOTE: When using @inquirer/prompts, Ctrl+C during a prompt throws\n * ExitPromptError. The wizard step runner should catch this and call\n * the cancel flow directly rather than relying solely on SIGINT.\n */\nexport function setupCancelHandler(\n onCancel: () => void | Promise<void>,\n): () => void {\n let handling = false;\n\n const handler = () => {\n if (handling) {\n // Second Ctrl+C — force quit\n process.exit(1);\n }\n handling = true;\n\n const result = onCancel();\n if (result instanceof Promise) {\n result\n .catch(() => process.exit(1))\n .finally(() => {\n handling = false;\n });\n } else {\n handling = false;\n }\n };\n\n process.on('SIGINT', handler);\n\n return () => {\n process.removeListener('SIGINT', handler);\n };\n}\n","/**\n * T071 — Step 6: Generate & Start\n *\n * Orchestrates the full generation and startup flow:\n * 1. Generate docker-compose.yml, .env, infrastructure configs\n * 2. Write all files to the project directory\n * 3. Pull Docker images (with progress reporting)\n * 4. Start services (docker compose up -d)\n * 5. Health check all services\n * 6. Credential propagation verification\n * 7. Return result with recovery options on failure\n *\n * @module wizard/steps/generate\n */\n\nimport { writeFileSync, mkdirSync, readFileSync, existsSync, chmodSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { homedir } from 'node:os';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport Table from 'cli-table3';\nimport { confirm, select } from '@inquirer/prompts';\nimport type { WizardState, InstallManifest, InstallManifestStack } from '@brewnet/shared';\nimport {\n buildServiceUrlMap,\n buildServiceAccessGuide,\n verifyServiceAccess,\n} from '../../utils/service-verifier.js';\nimport { DOCKER_COMPOSE_FILENAME } from '@brewnet/shared';\nimport {\n generateComposeConfig,\n composeConfigToYaml,\n} from '../../services/compose-generator.js';\nimport {\n generateEnvFiles,\n writeEnvFile,\n writeSecretFiles,\n} from '../../services/env-generator.js';\nimport { generateInfraConfigs } from '../../services/config-generator.js';\nimport {\n buildPullCommand,\n buildUpCommand,\n buildDownCommand,\n sortByDependency,\n} from '../../services/health-checker.js';\nimport {\n collectAllServices,\n getCredentialTargets,\n} from '../../utils/resources.js';\n\n// ---------------------------------------------------------------------------\n// Helper: execute a shell command\n// ---------------------------------------------------------------------------\n\n/**\n * Execute a command via execa. Imported dynamically to handle ESM.\n */\nasync function execCommand(\n cmd: string,\n args: string[],\n): Promise<{ stdout: string; stderr: string; exitCode: number }> {\n try {\n const { execa } = await import('execa');\n const result = await execa(cmd, args);\n return {\n stdout: result.stdout,\n stderr: result.stderr,\n exitCode: result.exitCode ?? 0,\n };\n } catch (err: unknown) {\n if (err && typeof err === 'object' && 'stdout' in err) {\n const execaErr = err as { stdout: string; stderr: string; exitCode: number };\n return {\n stdout: execaErr.stdout ?? '',\n stderr: execaErr.stderr ?? '',\n exitCode: execaErr.exitCode ?? 1,\n };\n }\n throw err;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helper: capture Quick Tunnel URL from compose container logs\n// ---------------------------------------------------------------------------\n\nconst QUICK_TUNNEL_CONTAINER = 'brewnet-cloudflared';\nconst TUNNEL_URL_REGEX = /https?:\\/\\/([\\w]+-[\\w][\\w-]*\\.trycloudflare\\.com)/i;\nconst TUNNEL_URL_TIMEOUT_MS = 30_000;\n\n/**\n * Read logs from the compose-managed cloudflared container and extract\n * the *.trycloudflare.com URL. Uses `docker logs --follow` with a 30s timeout.\n */\nasync function captureQuickTunnelUrl(): Promise<string> {\n const { execa: execaFn } = await import('execa');\n\n return new Promise((resolve, reject) => {\n const proc = execaFn('docker', [\n 'logs', '--follow', '--tail', '50', QUICK_TUNNEL_CONTAINER,\n ]);\n\n // Suppress the expected rejection when we intentionally kill the process.\n // execa rejects with exit code 143 (SIGTERM) on proc.kill(), which would\n // otherwise become an unhandled promise rejection and crash Node.js.\n proc.catch(() => {});\n\n const timer = setTimeout(() => {\n proc.kill();\n reject(new Error('30초 내에 Quick Tunnel URL을 얻지 못했습니다.'));\n }, TUNNEL_URL_TIMEOUT_MS);\n\n let resolved = false;\n const onData = (data: Buffer | string) => {\n if (resolved) return;\n const text = String(data);\n const match = TUNNEL_URL_REGEX.exec(text);\n if (match) {\n resolved = true;\n clearTimeout(timer);\n proc.kill();\n resolve(`https://${match[1]}`);\n }\n };\n\n proc.stdout?.on('data', onData);\n proc.stderr?.on('data', onData);\n proc.on('error', (err) => {\n if (!resolved) {\n clearTimeout(timer);\n reject(err);\n }\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Result of the generate step.\n * - 'success': proceed to Complete step (includes user choosing \"continue\")\n * - 'error': pre-startup failure, go back to Review step\n * - 'restart': restart wizard from AdminSetup (Step 0)\n * - 'clean-restart': cleanup then restart from AdminSetup\n */\nexport type GenerateResult = 'success' | 'error' | 'restart' | 'clean-restart';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Display failure recovery options. Used by both docker compose up failure\n * and health check failure paths.\n */\nasync function promptFailureRecovery(context: string): Promise<GenerateResult> {\n console.log();\n console.log(chalk.dim(' Tip: view logs with docker compose logs -f'));\n console.log(chalk.dim(' Per-service logs: ls ~/.brewnet/*/logs/*.log'));\n console.log();\n\n const action = await select<'continue' | 'restart' | 'clean-restart'>({\n message: 'How would you like to proceed?',\n choices: [\n { value: 'continue' as const, name: `Continue — ${context}` },\n { value: 'restart' as const, name: 'Restart setup from the beginning' },\n { value: 'clean-restart' as const, name: 'Clean uninstall, then restart from scratch' },\n ],\n });\n\n return action === 'continue' ? 'success' : action;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: parse conflicting container names from docker compose stderr\n// ---------------------------------------------------------------------------\n\n/**\n * Parse \"container name already in use\" errors and extract container names.\n * Example stderr line:\n * ... The container name \"/brewnet-jellyfin\" is already in use ...\n */\nfunction parseConflictingNames(stderr: string): string[] {\n const re = /container name \"\\/([^\"]+)\" is already in use/g;\n const names: string[] = [];\n let m: RegExpExecArray | null;\n while ((m = re.exec(stderr)) !== null) {\n names.push(m[1]);\n }\n return names;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: write per-service Docker logs to disk\n// ---------------------------------------------------------------------------\n\n/**\n * Capture Docker Compose service logs and write each service's log to a\n * separate file under `<projectPath>/logs/`. This enables the user to\n * debug individual services with `tail -f ~/.brewnet/<project>/logs/<service>.log`.\n */\nasync function writeServiceLogs(\n projectPath: string,\n composePath: string,\n): Promise<void> {\n const logDir = join(projectPath, 'logs');\n mkdirSync(logDir, { recursive: true });\n\n try {\n // Get list of running service names from the compose file\n const psResult = await execCommand('docker', [\n 'compose', '-f', composePath, 'ps', '--format', '{{.Service}}',\n ]);\n if (psResult.exitCode !== 0 || !psResult.stdout.trim()) return;\n\n const serviceNames = psResult.stdout.trim().split('\\n').filter(Boolean);\n\n for (const svc of serviceNames) {\n try {\n const logsResult = await execCommand('docker', [\n 'compose', '-f', composePath, 'logs', '--no-color', '--tail', '200', svc,\n ]);\n const logFile = join(logDir, `${svc}.log`);\n const content = [\n `# Brewnet service log: ${svc}`,\n `# Captured: ${new Date().toISOString()}`,\n `# Live logs: docker compose -f \"${composePath}\" logs -f ${svc}`,\n '',\n logsResult.stdout || logsResult.stderr || '(no output)',\n '',\n ].join('\\n');\n writeFileSync(logFile, content, 'utf-8');\n } catch {\n // best-effort per service\n }\n }\n\n console.log(chalk.dim(` Logs saved to ${logDir}/`));\n console.log(chalk.dim(` Real-time: tail -f ${logDir}/*.log`));\n } catch {\n // best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// runGenerateStep\n// ---------------------------------------------------------------------------\n\n/**\n * Run Step 6: Generate & Start.\n *\n * Generates all configuration files, pulls Docker images, starts services,\n * runs health checks, and verifies credential propagation.\n *\n * @param state - Completed wizard state\n * @returns GenerateResult indicating success or chosen recovery action\n */\nexport async function runGenerateStep(state: WizardState): Promise<GenerateResult> {\n // -------------------------------------------------------------------------\n // 1. Display header\n // -------------------------------------------------------------------------\n console.log();\n console.log(\n chalk.bold.cyan(' Step 7/8') + chalk.bold(' — Generate & Start'),\n );\n console.log(chalk.dim(' Generating configuration and starting services'));\n console.log();\n\n const projectPath = state.projectPath.startsWith('~')\n ? join(homedir(), state.projectPath.slice(1))\n : state.projectPath;\n const composePath = join(projectPath, DOCKER_COMPOSE_FILENAME);\n\n // Manifest collectors — populated throughout generation, written at the end.\n const manifestFiles: string[] = [DOCKER_COMPOSE_FILENAME, '.env', '.env.example', '.gitignore'];\n const manifestDirs: string[] = ['secrets', 'logs'];\n const manifestStacks: InstallManifestStack[] = [];\n\n // -------------------------------------------------------------------------\n // 2. Generate docker-compose.yml\n // -------------------------------------------------------------------------\n const composeSpinner = ora(' Generating docker-compose.yml').start();\n try {\n const composeConfig = generateComposeConfig(state);\n const yamlContent = composeConfigToYaml(composeConfig);\n\n mkdirSync(projectPath, { recursive: true });\n writeFileSync(composePath, yamlContent, 'utf-8');\n composeSpinner.succeed(' docker-compose.yml generated');\n } catch (err) {\n composeSpinner.fail(' Failed to generate docker-compose.yml');\n if (err instanceof Error) {\n console.log(chalk.dim(` ${err.message}`));\n }\n return 'error';\n }\n\n // -------------------------------------------------------------------------\n // 3. Generate .env files\n // -------------------------------------------------------------------------\n const envSpinner = ora(' Generating .env files').start();\n try {\n const envResult = generateEnvFiles(state);\n writeEnvFile(projectPath, envResult.envContent);\n\n // Write .env.example (safe to share)\n const envExamplePath = join(projectPath, '.env.example');\n writeFileSync(envExamplePath, envResult.envExampleContent, 'utf-8');\n\n // Write secret files to secrets/ directory (chmod 700 dir, 600 files)\n writeSecretFiles(projectPath, envResult.secretFiles);\n\n // Record secret file paths for the install manifest\n for (const sf of envResult.secretFiles) {\n manifestFiles.push(sf.relativePath);\n }\n\n const secretCount = envResult.secretFiles.length;\n envSpinner.succeed(\n ` .env + ${secretCount} secret file(s) generated (chmod 600)`,\n );\n } catch (err) {\n envSpinner.fail(' Failed to generate .env / secret files');\n if (err instanceof Error) {\n console.log(chalk.dim(` ${err.message}`));\n }\n return 'error';\n }\n\n // -------------------------------------------------------------------------\n // 3b. Generate .gitignore\n // -------------------------------------------------------------------------\n try {\n const gitignorePath = join(projectPath, '.gitignore');\n const gitignoreContent = [\n '.env',\n '.env.*',\n '!.env.example',\n 'secrets/',\n 'data/',\n 'backups/',\n '*.db',\n '*.sqlite',\n '*.log',\n 'docker-compose.override.yml',\n '',\n ].join('\\n');\n writeFileSync(gitignorePath, gitignoreContent, 'utf-8');\n } catch {\n // Non-critical — best-effort\n }\n\n // -------------------------------------------------------------------------\n // 4. Generate infrastructure configs\n // -------------------------------------------------------------------------\n const infraSpinner = ora(' Generating infrastructure configs').start();\n try {\n const infraFiles = generateInfraConfigs(state);\n\n for (const file of infraFiles) {\n const filePath = join(projectPath, file.path);\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, file.content, 'utf-8');\n manifestFiles.push(file.path);\n // Track top-level generated config directories (e.g. 'configs', 'letsencrypt')\n const topDir = file.path.split('/')[0];\n if (topDir && topDir !== '.' && !manifestDirs.includes(topDir)) {\n manifestDirs.push(topDir);\n }\n }\n\n infraSpinner.succeed(` ${infraFiles.length} infrastructure config(s) generated`);\n } catch (err) {\n infraSpinner.fail(' Failed to generate infrastructure configs');\n if (err instanceof Error) {\n console.log(chalk.dim(` ${err.message}`));\n }\n return 'error';\n }\n\n // -------------------------------------------------------------------------\n // 5. Pull Docker images\n // -------------------------------------------------------------------------\n console.log();\n const pullSpinner = ora(' Pulling Docker images...').start();\n try {\n const pullCmd = buildPullCommand(composePath);\n const pullResult = await execCommand(pullCmd.cmd, pullCmd.args);\n\n if (pullResult.exitCode !== 0) {\n pullSpinner.fail(' Failed to pull Docker images');\n console.log(chalk.dim(` ${pullResult.stderr}`));\n\n // Offer to continue anyway\n const shouldContinue = await confirm({\n message: 'Continue without pulling images? (existing images will be used)',\n default: false,\n });\n\n if (!shouldContinue) {\n return 'error';\n }\n } else {\n pullSpinner.succeed(' Docker images pulled');\n }\n } catch (err) {\n pullSpinner.fail(' Failed to pull Docker images');\n if (err instanceof Error) {\n console.log(chalk.dim(` ${err.message}`));\n }\n\n const shouldContinue = await confirm({\n message: 'Continue without pulling images?',\n default: false,\n });\n\n if (!shouldContinue) {\n return 'error';\n }\n }\n\n // -------------------------------------------------------------------------\n // 6. Ensure Docker networks exist (external networks must be pre-created)\n // -------------------------------------------------------------------------\n // Only `brewnet` needs to be pre-created (it's marked `external: true` in compose).\n // `brewnet-internal` is managed by Docker Compose itself (internal: true, not external).\n try {\n await execCommand('docker', ['network', 'create', 'brewnet']);\n } catch {\n // Network already exists — that's fine\n }\n\n // -------------------------------------------------------------------------\n // 7. Start services\n // -------------------------------------------------------------------------\n const services = collectAllServices(state);\n const sorted = sortByDependency(services);\n\n console.log();\n console.log(chalk.dim(` Starting ${sorted.length} services in dependency order...`));\n\n // 7-pre. Pre-cleanup: remove ALL brewnet-* containers (best-effort).\n // `docker compose down` only removes containers from the CURRENT compose file,\n // so containers from a previous run with different services would remain and\n // cause \"container name already in use\" conflicts. Instead, we find and remove\n // every container whose name starts with \"brewnet-\".\n const downCmd = buildDownCommand(composePath);\n await execCommand(downCmd.cmd, downCmd.args).catch(() => {});\n try {\n const psResult = await execCommand('docker', [\n 'ps', '-a', '--filter', 'name=^brewnet-', '--format', '{{.Names}}',\n ]);\n if (psResult.exitCode === 0 && psResult.stdout.trim()) {\n const staleNames = psResult.stdout.trim().split('\\n').filter(Boolean);\n for (const name of staleNames) {\n await execCommand('docker', ['rm', '-f', name]).catch(() => {});\n }\n }\n } catch {\n // best-effort — Docker may not be available yet\n }\n\n // -------------------------------------------------------------------------\n // 7-pre-db. If Gitea + PostgreSQL: start PostgreSQL first, wait for ready,\n // then create gitea_db BEFORE starting all services.\n // (docker compose up starts Gitea immediately, which needs gitea_db to exist)\n // -------------------------------------------------------------------------\n const needsGiteaDb =\n state.servers.gitServer?.enabled &&\n state.servers.dbServer.enabled &&\n state.servers.dbServer.primary === 'postgresql';\n\n if (needsGiteaDb) {\n const pgPreSpinner = ora(' Starting PostgreSQL (pre-init for gitea_db)...').start();\n try {\n const { execa: execaFn } = await import('execa');\n\n // 1. Start only PostgreSQL\n await execaFn('docker', [\n 'compose', '-f', composePath, 'up', '-d', '--force-recreate', 'postgresql',\n ]);\n pgPreSpinner.text = ' Waiting for PostgreSQL to be ready...';\n\n // 2. Poll pg_isready — max 30 seconds (TCP listener only)\n const dbUser = state.servers.dbServer.dbUser || 'brewnet';\n let pgListening = false;\n for (let i = 0; i < 30; i++) {\n await new Promise((r) => setTimeout(r, 1000));\n try {\n const result = await execaFn('docker', [\n 'exec', 'brewnet-postgresql',\n 'pg_isready', '-U', dbUser,\n ]);\n if (result.exitCode === 0) { pgListening = true; break; }\n } catch { /* not ready yet */ }\n }\n\n if (!pgListening) {\n pgPreSpinner.warn(' PostgreSQL did not become ready in 30s — gitea_db may not be created');\n } else {\n // 2b. pg_isready returns 0 when PostgreSQL first accepts connections, but the\n // Docker init scripts (create POSTGRES_USER, POSTGRES_DB) may still be running.\n // Poll psql until the user actually exists and can connect — max 30s more.\n pgPreSpinner.text = ' Waiting for PostgreSQL user initialization...';\n let userReady = false;\n for (let i = 0; i < 30; i++) {\n await new Promise((r) => setTimeout(r, 1000));\n try {\n const res = await execaFn('docker', [\n 'exec', 'brewnet-postgresql',\n 'psql', '-U', dbUser, '-d', 'postgres', '-c', 'SELECT 1',\n ]);\n if (res.exitCode === 0) { userReady = true; break; }\n } catch { /* user not created yet by init scripts */ }\n }\n\n if (!userReady) {\n pgPreSpinner.warn(' PostgreSQL user not ready in 60s — gitea_db may not be created');\n } else {\n // 3. Check if gitea_db already exists\n // NOTE: CREATE DATABASE cannot run inside a transaction block (DO $$...$$),\n // so we use a two-step approach: check existence, then create outside a transaction.\n const checkResult = await execaFn('docker', [\n 'exec', 'brewnet-postgresql',\n 'psql', '-U', dbUser, '-d', 'postgres',\n '-tAc', `SELECT 1 FROM pg_database WHERE datname = 'gitea_db'`,\n ]);\n if (checkResult.stdout.trim() !== '1') {\n // 4. Create gitea_db — must run outside a transaction block\n await execaFn('docker', [\n 'exec', 'brewnet-postgresql',\n 'psql', '-U', dbUser, '-d', 'postgres',\n '-c', `CREATE DATABASE gitea_db OWNER ${dbUser}`,\n ]);\n }\n\n // 5. Sync db user password to match current secret file (handles re-run\n // scenarios where the volume has an old password but secrets changed).\n try {\n const dbPassPath = join(projectPath, 'secrets', 'db_password');\n const { readFileSync } = await import('node:fs');\n const dbPass = readFileSync(dbPassPath, 'utf-8').trim();\n if (dbPass) {\n await execaFn('docker', [\n 'exec', 'brewnet-postgresql',\n 'psql', '-U', dbUser, '-d', 'postgres',\n '-c', `ALTER USER ${dbUser} WITH PASSWORD '${dbPass}'`,\n ]);\n }\n } catch {\n // best-effort — if it fails, Gitea will crash-loop and surface the error\n }\n\n pgPreSpinner.succeed(' gitea_db ready');\n }\n }\n } catch (err) {\n pgPreSpinner.warn(' gitea_db pre-creation failed — Gitea may prompt for manual DB setup');\n if (err instanceof Error) {\n console.log(chalk.dim(` ${err.message}`));\n }\n }\n }\n\n const upSpinner = ora(' Starting services (docker compose up -d)').start();\n try {\n const upCmd = buildUpCommand(composePath);\n let upResult = await execCommand(upCmd.cmd, upCmd.args);\n\n // 7-retry. Conflict recovery: remove clashing containers and retry once\n if (upResult.exitCode !== 0 && upResult.stderr?.includes('is already in use')) {\n upSpinner.text = ' Resolving container name conflicts...';\n const names = parseConflictingNames(upResult.stderr);\n for (const name of names) {\n await execCommand('docker', ['rm', '-f', name]).catch(() => {});\n }\n upResult = await execCommand(upCmd.cmd, upCmd.args);\n }\n\n if (upResult.exitCode !== 0) {\n upSpinner.fail(' Failed to start services');\n console.log();\n\n // Write full output to log file for debugging\n const logDir = join(projectPath, 'logs');\n mkdirSync(logDir, { recursive: true });\n const logFile = join(logDir, 'docker-compose-up.log');\n const logContent = [\n `[${new Date().toISOString()}] docker compose up -d`,\n '--- stdout ---',\n upResult.stdout || '(empty)',\n '--- stderr ---',\n upResult.stderr || '(empty)',\n `--- exit code: ${upResult.exitCode} ---`,\n '',\n ].join('\\n');\n try { writeFileSync(logFile, logContent, 'utf-8'); } catch { /* best-effort */ }\n\n console.log(chalk.red(' Error details:'));\n if (upResult.stderr) {\n // Show LAST 30 lines (actual errors), not first lines (progress messages)\n const allLines = upResult.stderr.split('\\n').filter(Boolean);\n const errorLines = allLines.slice(-30);\n if (allLines.length > 30) {\n console.log(chalk.dim(' ... (earlier output omitted)'));\n }\n for (const line of errorLines) {\n console.log(chalk.dim(` ${line}`));\n }\n }\n console.log();\n console.log(chalk.dim(` Full log: ${logFile}`));\n\n return promptFailureRecovery('some services may have started');\n }\n\n upSpinner.succeed(' Services started');\n\n // Write per-service logs to disk for debugging with tail -f\n await writeServiceLogs(projectPath, composePath);\n } catch (err) {\n upSpinner.fail(' Failed to start services');\n if (err instanceof Error) {\n console.log();\n console.log(chalk.red(' Error details:'));\n console.log(chalk.dim(` ${err.message}`));\n }\n // Still try to capture whatever logs exist\n await writeServiceLogs(projectPath, composePath).catch(() => {});\n return promptFailureRecovery('Docker may need restarting');\n }\n\n // -------------------------------------------------------------------------\n // 7a. Quick Tunnel URL capture (after traefik + cloudflared are running)\n // -------------------------------------------------------------------------\n if (state.domain.cloudflare.tunnelMode === 'quick') {\n console.log();\n const tunnelSpinner = ora(' Quick Tunnel URL 캡처 중...').start();\n try {\n const tunnelUrl = await captureQuickTunnelUrl();\n state.domain.cloudflare.quickTunnelUrl = tunnelUrl;\n state.domain.name = new URL(tunnelUrl).hostname;\n tunnelSpinner.succeed(` Quick Tunnel: ${tunnelUrl}`);\n\n // Register Nextcloud trusted_domains/proxies after Nextcloud is ready.\n // IMPORTANT: env var NEXTCLOUD_TRUSTED_DOMAINS only applies on first boot.\n // After that, Nextcloud reads config.php — all changes require occ.\n // We set three things atomically once NC is confirmed ready:\n // index 0: localhost (local Traefik access)\n // index 4: regex (all *.trycloudflare.com URLs, survives tunnel restarts)\n // index 5: exact URL (current tunnel hostname for this session)\n // trusted_proxies: Traefik bridge range (for X-Forwarded-Proto)\n if (\n state.servers.fileServer.enabled &&\n state.servers.fileServer.service === 'nextcloud'\n ) {\n try {\n const { execa: execaFn } = await import('execa');\n const occQuick = (args: string[]) =>\n execaFn('docker', ['exec', '-u', 'www-data', 'brewnet-nextcloud', 'php', 'occ', ...args]);\n let registered = false;\n // Check NC health first (no initial wait); retry with 5s delay on failure.\n for (let attempt = 0; attempt < 18 && !registered; attempt++) {\n try {\n // Register exact tunnel URL (index 5) — if this succeeds, NC is ready.\n await occQuick(['config:system:set', 'trusted_domains', '5',\n `--value=${state.domain.name}`,\n ]);\n // NC is ready — set persistent config while we have it:\n await occQuick(['config:system:set', 'trusted_domains', '0', '--value=localhost']);\n // Regex pattern trusts ALL *.trycloudflare.com URLs permanently.\n // Quick Tunnel URL changes on every cloudflared restart; this prevents\n // the \"untrusted domain\" error without needing another occ run each time.\n await occQuick(['config:system:set', 'trusted_domains', '4',\n '--value=*.trycloudflare.com',\n ]);\n // Trust Traefik's bridge range so Nextcloud reads X-Forwarded-Proto.\n await occQuick(['config:system:set', 'trusted_proxies', '0', '--value=172.16.0.0/12']);\n registered = true;\n } catch {\n // Nextcloud still initializing — wait before retry (skip after last attempt)\n if (attempt < 17) await new Promise((r) => setTimeout(r, 5000));\n }\n }\n if (!registered) {\n console.log(chalk.yellow('\\n ⚠ Nextcloud: occ 설정 실패 (90s 대기 초과). 수동으로 실행하세요:'));\n const d = state.domain.name || '<tunnel-url>';\n console.log(chalk.dim(` docker exec -u www-data brewnet-nextcloud php occ config:system:set trusted_domains 0 --value=localhost`));\n console.log(chalk.dim(` docker exec -u www-data brewnet-nextcloud php occ config:system:set trusted_domains 4 --value='*.trycloudflare.com'`));\n console.log(chalk.dim(` docker exec -u www-data brewnet-nextcloud php occ config:system:set trusted_domains 5 --value=${d}`));\n console.log(chalk.dim(` docker exec -u www-data brewnet-nextcloud php occ config:system:set trusted_proxies 0 --value=172.16.0.0/12`));\n }\n } catch {\n // Non-critical — user can run occ manually\n }\n }\n } catch (err) {\n tunnelSpinner.fail(' Quick Tunnel URL 캡처 실패');\n if (err instanceof Error) {\n console.log(chalk.dim(` ${err.message}`));\n }\n console.log(chalk.dim(' 서비스는 정상 시작되었으나 외부 URL을 가져오지 못했습니다.'));\n console.log(chalk.dim(' `docker logs brewnet-cloudflared` 로 확인하세요.'));\n }\n\n // Post-install FileBrowser: force credentials to wizard admin values.\n // FB_USERNAME/FB_PASSWORD only apply on first boot (no DB).\n // When DB already exists, the running process holds an exclusive BoltDB lock —\n // docker exec will always timeout. Fix: stop container, run one-off command\n // against the DB volume, then restart.\n if (state.servers.fileBrowser.enabled) {\n const fbSpinner = ora({ text: ' Applying FileBrowser credentials...', indent: 2 }).start();\n try {\n const { execa: execaFn } = await import('execa');\n\n // Find the DB and config volume names from the running container\n const { stdout } = await execaFn('docker', [\n 'inspect', 'brewnet-filebrowser',\n '--format',\n '{{range .Mounts}}{{.Destination}}={{.Name}}\\n{{end}}',\n ]);\n const mountMap: Record<string, string> = {};\n for (const line of stdout.trim().split('\\n')) {\n const [dest, name] = line.split('=');\n if (dest && name) mountMap[dest.trim()] = name.trim();\n }\n const dbVolume = mountMap['/database'];\n const configVolume = mountMap['/config'];\n\n if (dbVolume) {\n // Stop to release DB lock, update via temp container, restart\n await execaFn('docker', ['stop', 'brewnet-filebrowser']);\n\n // In Quick Tunnel mode, write settings.json with baseURL=/files into the /config volume.\n // settings.json takes priority over DB values — the DB config set alone is not enough.\n // Without baseURL in settings.json, window.FileBrowser.BaseURL=\"\" → post-login redirect to / → 404.\n if (state.domain.cloudflare.tunnelMode === 'quick' && configVolume) {\n const settingsJson = JSON.stringify({\n port: 80,\n baseURL: '/files',\n address: '',\n log: 'stdout',\n database: '/database/filebrowser.db',\n root: '/srv',\n });\n await execaFn('docker', [\n 'run', '--rm',\n '-v', `${configVolume}:/config`,\n 'busybox',\n 'sh', '-c', `printf '%s' '${settingsJson}' > /config/settings.json`,\n ]);\n }\n\n // Relax minimum password length (default 12) to match brewnet admin password\n await execaFn('docker', [\n 'run', '--rm',\n '-v', `${dbVolume}:/database`,\n 'filebrowser/filebrowser:latest',\n '--database', '/database/filebrowser.db',\n 'config', 'set', '--minimumPasswordLength', '6',\n ]);\n await execaFn('docker', [\n 'run', '--rm',\n '-v', `${dbVolume}:/database`,\n 'filebrowser/filebrowser:latest',\n '--database', '/database/filebrowser.db',\n 'users', 'update', '1',\n '--username', state.admin.username || 'admin',\n '--password', state.admin.password || '',\n ]);\n await execaFn('docker', ['start', 'brewnet-filebrowser']);\n fbSpinner.succeed(' FileBrowser credentials applied');\n } else {\n fbSpinner.warn(' FileBrowser: DB volume not found — change password via UI');\n }\n } catch (err) {\n fbSpinner.warn(' FileBrowser credentials not applied — change password via UI');\n if (err instanceof Error) console.log(chalk.dim(` ${err.message}`));\n }\n }\n\n }\n\n // -------------------------------------------------------------------------\n // 7b. Boilerplate project: clone from GitHub, configure, build, verify\n // -------------------------------------------------------------------------\n if (state.boilerplate.generate && state.devStack.languages.length > 0) {\n const { resolveStackId } = await import('../../config/frameworks.js');\n const {\n cloneStack,\n generateEnv: boilerplateGenerateEnv,\n startContainers: boilerplateStartContainers,\n pollHealth: boilerplatePollHealth,\n verifyEndpoints: boilerplateVerifyEndpoints,\n findFreePort,\n } = await import('../../services/boilerplate-manager.js');\n\n // Map wizard DB selection to boilerplate DB_DRIVER value\n const dbPrimary = state.servers.dbServer.primary;\n const dbDriver = dbPrimary === 'postgresql' ? 'postgres'\n : dbPrimary === 'mysql' ? 'mysql'\n : 'sqlite3';\n\n // DB credentials from wizard settings (with safe fallbacks)\n const dbOpts = {\n dbUser: state.servers.dbServer.dbUser || 'brewnet',\n dbPassword: state.admin.password || undefined,\n dbName: state.servers.dbServer.dbName || 'brewnet_db',\n };\n\n type StackStatus = 'running' | 'failed' | 'timeout';\n const stackMetas: Array<{\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: StackStatus;\n }> = [];\n\n // Load previous boilerplate metadata to clean up stale containers before starting new ones.\n // This prevents \"port already in use\" errors when re-running the wizard.\n const boilerplateMetaPath = join(projectPath, '.brewnet-boilerplate.json');\n let prevStackMetas: Array<{ stackId: string; appDir: string }> = [];\n try {\n const prevRaw = JSON.parse(readFileSync(boilerplateMetaPath, 'utf-8'));\n prevStackMetas = Array.isArray(prevRaw) ? prevRaw : [prevRaw];\n } catch { /* no previous run — fine */ }\n\n // Process each selected language/framework combination\n for (const lang of state.devStack.languages) {\n const frameworkId = state.devStack.frameworks[lang] ?? '';\n const stackId = resolveStackId(lang, frameworkId);\n\n if (!stackId) {\n console.log(chalk.dim(` 보일러플레이트 건너뜀: ${lang}/${frameworkId}에 대한 스택 없음`));\n continue;\n }\n\n // Clone directly under projectPath (no 'apps/' prefix)\n const appDir = join(projectPath, stackId);\n const isUnified = stackId.startsWith('nodejs-nextjs');\n const defaultPort = isUnified ? 3000 : 8080;\n // Auto-select free ports so concurrent stacks or existing processes don't conflict.\n const backendPort = await findFreePort(defaultPort);\n if (backendPort !== defaultPort) {\n console.log(chalk.dim(` [${stackId}] 백엔드 포트 ${defaultPort} 사용 중 → ${backendPort} 자동 선택`));\n }\n // Non-unified stacks have a separate frontend container (default port 3000).\n const frontendPort = isUnified ? undefined : await findFreePort(3000);\n if (!isUnified && frontendPort !== 3000) {\n console.log(chalk.dim(` [${stackId}] 프론트 포트 3000 사용 중 → ${frontendPort} 자동 선택`));\n }\n const baseUrl = `http://127.0.0.1:${backendPort}`;\n const isRust = stackId.startsWith('rust-');\n const isJavaOrKotlin = stackId.startsWith('java-') || stackId.startsWith('kotlin-');\n const healthTimeoutMs = isRust ? 600_000 : (isJavaOrKotlin ? 300_000 : 120_000);\n\n console.log();\n const bpSpinner = ora(` [${stackId}] 클론 중...`).start();\n let stackStatus: StackStatus = 'failed';\n\n try {\n // Step 1: shallow clone from brewnet-boilerplate GitHub repo\n bpSpinner.text = ` [${stackId}] GitHub에서 소스 클론 중... (branch: stack/${stackId})`;\n await cloneStack(stackId, appDir);\n\n // Step 2: generate .env with wizard DB settings (hostPort/frontendPort ensure correct port bindings)\n bpSpinner.text = ` [${stackId}] 런타임 환경 구성 중... (DB: ${dbDriver}, user: ${dbOpts.dbUser})`;\n boilerplateGenerateEnv(appDir, stackId, dbDriver, { ...dbOpts, hostPort: backendPort, frontendPort });\n\n // Step 3: start containers (with build)\n if (isRust) {\n bpSpinner.warn(` [${stackId}] Rust 스택 첫 빌드는 3-10분 소요됩니다 — 잠시 기다려주세요`);\n bpSpinner.start(` [${stackId}] 빌드 중... (Rust: 최대 10분 소요)`);\n } else if (isJavaOrKotlin) {\n bpSpinner.text = ` [${stackId}] 빌드 중... (Java/Kotlin: 최대 5분 소요)`;\n } else {\n bpSpinner.text = ` [${stackId}] 빌드 및 컨테이너 시작 중...`;\n }\n // Best-effort: stop previous boilerplate containers for the SAME stackId to free resources.\n // Port conflicts are already handled by findFreePort above, but stopping stale containers\n // avoids wasting memory/CPU on orphaned stacks from previous wizard runs.\n {\n const { execa: execaFn } = await import('execa');\n const stale = prevStackMetas.filter(\n prev => prev.stackId === stackId && prev.appDir !== appDir && existsSync(prev.appDir),\n );\n for (const prev of stale) {\n bpSpinner.text = ` [${stackId}] 이전 컨테이너 정리 중...`;\n try { await execaFn('docker', ['compose', 'down'], { cwd: prev.appDir }); } catch { /* ignore */ }\n }\n }\n // Inject Traefik labels for Quick Tunnel external access at /apps/{stackId}\n // For Next.js stacks, this also patches next.config with basePath and\n // updates the compose healthcheck path to include the basePath prefix.\n let isNextjsBasePath = false;\n if (state.domain.cloudflare.tunnelMode === 'quick') {\n try {\n const { injectTraefikForQuickTunnel } = await import('../../services/boilerplate-manager.js');\n injectTraefikForQuickTunnel(appDir, stackId, backendPort);\n // Next.js with basePath: all routes shift under /apps/{stackId}/\n if (stackId.startsWith('nodejs-nextjs')) isNextjsBasePath = true;\n } catch { /* non-critical: external access simply won't work */ }\n }\n await boilerplateStartContainers(appDir);\n\n // Step 4: poll health endpoint until ready\n // Next.js basePath shifts /health → /apps/{stackId}/health on direct port access\n const healthBaseUrl = isNextjsBasePath ? `${baseUrl}/apps/${stackId}` : baseUrl;\n bpSpinner.text = ` [${stackId}] 헬스체크 대기 중... (timeout: ${Math.round(healthTimeoutMs / 1000)}s)`;\n const health = await boilerplatePollHealth(healthBaseUrl, healthTimeoutMs);\n\n if (!health.healthy) {\n bpSpinner.warn(` [${stackId}] 헬스체크 타임아웃 (${Math.round(healthTimeoutMs / 1000)}s 초과)`);\n console.log(chalk.dim(` 로그 확인: docker compose -f ${appDir}/docker-compose.yml logs`));\n stackStatus = 'timeout';\n } else {\n // Step 5: verify API endpoints (/api/hello, /api/echo)\n await boilerplateVerifyEndpoints(healthBaseUrl);\n bpSpinner.succeed(` [${stackId}] 완료 — 백엔드: ${chalk.cyan(baseUrl)}`);\n if (!isUnified && frontendPort !== undefined) {\n console.log(chalk.dim(` 프론트엔드: http://127.0.0.1:${frontendPort}`));\n }\n stackStatus = 'running';\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n const isPortConflict = errMsg.includes('address already in use') || errMsg.includes('ports are not available');\n if (isPortConflict) {\n bpSpinner.warn(` [${stackId}] 포트 ${backendPort} 충돌 — 다른 프로세스가 사용 중`);\n console.log(chalk.dim(` 충돌 프로세스 확인: lsof -i :${backendPort}`));\n console.log(chalk.dim(` Docker 컨테이너 확인: docker ps | grep ${backendPort}`));\n console.log(chalk.dim(` 해당 컨테이너 종료 후 재시도: cd ${appDir} && make up`));\n } else {\n bpSpinner.warn(` [${stackId}] 설치 실패 — 수동 실행: cd ${appDir} && make up`);\n console.log(chalk.dim(` ${errMsg}`));\n }\n stackStatus = 'failed';\n }\n\n stackMetas.push({\n stackId,\n appDir,\n backendUrl: baseUrl,\n frontendUrl: isUnified ? baseUrl : `http://127.0.0.1:${frontendPort ?? 3000}`,\n isUnified,\n lang,\n frameworkId,\n dbDriver,\n dbUser: dbOpts.dbUser,\n dbName: dbOpts.dbName,\n gitBranch: `stack/${stackId}`,\n status: stackStatus,\n });\n }\n\n // Write stack metadata array for admin panel and complete step\n if (stackMetas.length > 0) {\n try {\n writeFileSync(\n join(projectPath, '.brewnet-boilerplate.json'),\n JSON.stringify(stackMetas, null, 2),\n 'utf-8',\n );\n manifestFiles.push('.brewnet-boilerplate.json');\n } catch { /* best-effort */ }\n\n // Record each boilerplate stack for the install manifest\n for (const s of stackMetas) {\n const relDir = s.appDir.startsWith(projectPath)\n ? s.appDir.slice(projectPath.length).replace(/^[\\\\/]/, '')\n : s.stackId;\n manifestStacks.push({ stackId: s.stackId, directory: relDir });\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // 7c. Credential propagation summary\n // -------------------------------------------------------------------------\n console.log();\n const credTargets = getCredentialTargets(state);\n if (credTargets.length > 0) {\n console.log(chalk.bold(' Credential Propagation'));\n for (const target of credTargets) {\n console.log(\n chalk.dim(` ${target}: `) +\n chalk.green(`${state.admin.username} → .env`),\n );\n }\n console.log();\n }\n\n // -------------------------------------------------------------------------\n // 8. Service access verification\n // -------------------------------------------------------------------------\n const urlMap = buildServiceUrlMap(state);\n\n if (urlMap.length > 0) {\n console.log(chalk.bold(' Service Verification'));\n console.log(chalk.dim(' Checking each service is reachable (retries: 2)...'));\n console.log();\n\n const verifySpinner = ora({ text: ' Verifying services...', indent: 2 }).start();\n const verifyResults = await Promise.all(\n urlMap.map((entry) => verifyServiceAccess(entry, { timeout: 5000, retries: 2 })),\n );\n verifySpinner.stop();\n\n const hasExternal = verifyResults.some((r) => r.externalUrl);\n const verifyTable = hasExternal\n ? new Table({\n head: [chalk.bold('Service'), chalk.bold('Local'), chalk.bold('External'), chalk.bold('Status')],\n colWidths: [18, 30, 36, 10],\n style: { head: [], border: ['dim'] },\n wordWrap: true,\n })\n : new Table({\n head: [chalk.bold('Service'), chalk.bold('Local URL'), chalk.bold('Status')],\n colWidths: [18, 32, 10],\n style: { head: [], border: ['dim'] },\n wordWrap: true,\n });\n\n for (const result of verifyResults) {\n const statusText =\n result.status === 'ok'\n ? chalk.green('✓ ok')\n : result.status === 'warn'\n ? chalk.yellow('⚠ warn')\n : result.status === 'fail'\n ? chalk.red('✗ fail')\n : chalk.dim('— skip');\n\n if (hasExternal) {\n verifyTable.push([\n result.label,\n chalk.dim(result.localUrl),\n result.externalUrl ? chalk.cyan(result.externalUrl) : chalk.dim('—'),\n statusText,\n ]);\n } else {\n verifyTable.push([\n result.label,\n chalk.dim(result.localUrl),\n statusText,\n ]);\n }\n }\n\n console.log(verifyTable.toString());\n console.log();\n\n const failedServices = verifyResults.filter((r) => r.status === 'fail');\n if (failedServices.length > 0) {\n console.log(chalk.yellow(` ${failedServices.length} service(s) did not respond.`));\n console.log();\n console.log(chalk.red(' Failed services:'));\n for (const svc of failedServices) {\n const reason = svc.error ?? 'unknown error';\n console.log(chalk.dim(` • ${svc.label} — ${reason}`));\n }\n\n return promptFailureRecovery('services may still be starting up');\n }\n }\n\n // -------------------------------------------------------------------------\n // 9. Service access guide\n // -------------------------------------------------------------------------\n const accessGuide = buildServiceAccessGuide(state);\n if (accessGuide.length > 0) {\n console.log(chalk.bold(' How to access your services'));\n console.log(chalk.dim(' ─────────────────────────────────────────────────'));\n console.log();\n\n for (const info of accessGuide) {\n // Clickable URL via OSC 8 hyperlink\n const linkUrl = `\\x1b]8;;${info.url}\\x07${info.url}\\x1b]8;;\\x07`;\n const homepageLink = `\\x1b]8;;${info.homepage}\\x07${info.homepage}\\x1b]8;;\\x07`;\n console.log(\n ` ${chalk.bold.white(info.label.padEnd(26))} ${chalk.cyan(linkUrl)}`,\n );\n console.log(` ${chalk.dim('ℹ')} ${chalk.dim(info.loginNote)}`);\n console.log(` ${chalk.dim('⌂')} ${chalk.dim(`Homepage: ${homepageLink} — Refer to the official documentation for usage manual`)}`);\n console.log();\n }\n console.log(chalk.dim(' ─────────────────────────────────────────────────'));\n console.log();\n }\n\n // -------------------------------------------------------------------------\n // 10. Secrets & credentials location notice\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Auto-generated Secrets'));\n console.log(chalk.dim(' ─────────────────────────────────────────────────'));\n console.log(chalk.dim(` Location: ${projectPath}/secrets/`));\n console.log();\n console.log(\n chalk.dim(' ') + chalk.yellow('Admin password ') +\n chalk.dim(`${projectPath}/secrets/admin_password`) +\n chalk.dim(' → ') + chalk.white(state.admin.password || '(see file)'),\n );\n if (state.servers.dbServer.enabled && state.servers.dbServer.dbPassword) {\n console.log(\n chalk.dim(' ') + chalk.yellow('DB password ') +\n chalk.dim(`${projectPath}/secrets/db_password`) +\n chalk.dim(' → ') + chalk.white(state.servers.dbServer.dbPassword),\n );\n }\n console.log();\n if (state.servers.gitServer.enabled) {\n // Gitea runs in headless mode (INSTALL_LOCK=true) — no web installer.\n // Create the admin user via CLI after Gitea is healthy.\n const gitea = ora({ text: ' Gitea: admin 계정 생성 중...', indent: 2 }).start();\n try {\n const { execa: execaFn } = await import('execa');\n\n // Wait for Gitea to be ready (up to 60s)\n // Check health FIRST; only wait before retry on failure (not before first attempt).\n let giteaReady = false;\n for (let i = 0; i < 12 && !giteaReady; i++) {\n try {\n await execaFn('docker', [\n 'exec', 'brewnet-gitea',\n 'curl', '-fsSL', 'http://localhost:3000/api/healthz',\n ]);\n giteaReady = true;\n } catch {\n // Still starting — wait before next retry (skip wait after last attempt)\n if (i < 11) await new Promise((r) => setTimeout(r, 5000));\n }\n }\n\n if (!giteaReady) {\n gitea.warn(' Gitea: 60s 내 응답 없음 — admin 계정 미생성. 나중에 수동으로 생성하세요.');\n } else {\n const adminUser = state.admin.username || 'admin';\n const adminPass = state.admin.password || '';\n const adminEmail = `${adminUser}@${state.domain.name || 'brewnet.local'}`;\n\n const result = await execaFn('docker', [\n 'exec', '-u', 'git', 'brewnet-gitea',\n 'gitea', 'admin', 'user', 'create',\n '--username', adminUser,\n '--password', adminPass,\n '--email', adminEmail,\n '--admin',\n '--must-change-password', 'false',\n ]).catch((e: unknown) => {\n // \"user already exists\" is not an error — happens on re-run\n const stderr = (e as { stderr?: string }).stderr ?? '';\n if (stderr.includes('user already exists') || stderr.includes('already exists')) {\n return { exitCode: 0 };\n }\n return { exitCode: 1, stderr };\n });\n\n if ((result as { exitCode: number }).exitCode === 0) {\n // Always sync password + reset must_change_password.\n // --password syncs Gitea's stored password to match the current state on re-runs\n // (first-run: no-op since user was just created with the same password).\n // 'user edit' was removed in Gitea 1.22+; use 'change-password' to sync\n // credentials and clear mustChangePassword in one command.\n await execaFn('docker', [\n 'exec', '-u', 'git', 'brewnet-gitea',\n 'gitea', 'admin', 'user', 'change-password',\n '--username', adminUser,\n '--password', adminPass,\n '--must-change-password=false',\n ]).catch((e: unknown) => {\n const msg = (e as { stderr?: string }).stderr ?? String(e);\n gitea.warn(` Gitea: 계정 동기화 실패 — ${msg.slice(0, 120)}`);\n });\n gitea.succeed(` Gitea: admin 계정 생성 완료 (${adminUser})`);\n\n // Eager token creation — validates mustChangePassword=false immediately,\n // not lazily at create-app time. Saves token to ~/.brewnet/gitea-token.\n const tokenPath = join(homedir(), '.brewnet', 'gitea-token');\n if (!existsSync(tokenPath)) {\n const giteaDirectUrl = 'http://localhost:3000'; // direct port (health check passed)\n const basic = Buffer.from(`${adminUser}:${adminPass}`).toString('base64');\n try {\n const tr = await fetch(`${giteaDirectUrl}/api/v1/users/${adminUser}/tokens`, {\n method: 'POST',\n headers: { Authorization: `Basic ${basic}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n name: 'brewnet-init',\n scopes: ['write:repository', 'read:repository', 'write:user', 'read:user'],\n }),\n });\n if (tr.ok) {\n const td = (await tr.json()) as { sha1: string };\n mkdirSync(join(homedir(), '.brewnet'), { recursive: true });\n writeFileSync(tokenPath, td.sha1, 'utf-8');\n chmodSync(tokenPath, 0o600);\n gitea.succeed(' Gitea: API 토큰 생성 완료');\n } else {\n const errBody = await tr.text();\n gitea.warn(` Gitea: API 토큰 생성 실패 (${tr.status}) — create-app 시 자동 재시도됩니다`);\n if (errBody.includes('must change')) {\n gitea.warn(' Gitea: must-change-password 플래그가 여전히 설정되어 있습니다');\n gitea.warn(` Fix: docker exec -u git brewnet-gitea gitea admin user change-password --username ${adminUser} --password <password> --must-change-password=false`);\n }\n }\n } catch {\n gitea.warn(' Gitea: 토큰 사전 생성 건너뜀 — create-app 시 재시도됩니다');\n }\n }\n } else {\n gitea.warn(' Gitea: admin 계정 생성 실패 — 수동으로 생성하세요:');\n console.log(chalk.dim(` docker exec -u git brewnet-gitea gitea admin user create --username ${adminUser} --password <password> --email ${adminEmail} --admin --must-change-password false`));\n }\n }\n } catch (err) {\n gitea.warn(' Gitea: admin 계정 생성 건너뜀');\n if (err instanceof Error) console.log(chalk.dim(` ${err.message}`));\n }\n console.log();\n }\n console.log(chalk.dim(' ─────────────────────────────────────────────────'));\n console.log();\n\n // -------------------------------------------------------------------------\n // 11. Write install manifest (.brewnet-manifest.json)\n // -------------------------------------------------------------------------\n // Records every brewnet-generated file so `brewnet uninstall` can remove\n // only what brewnet created, preserving any files the user may have added.\n try {\n const manifest: InstallManifest = {\n schemaVersion: 1,\n projectName: state.projectName,\n projectPath: state.projectPath,\n createdAt: new Date().toISOString(),\n generatedFiles: [...manifestFiles, '.brewnet-manifest.json'],\n generatedDirs: manifestDirs,\n boilerplateStacks: manifestStacks,\n };\n writeFileSync(\n join(projectPath, '.brewnet-manifest.json'),\n JSON.stringify(manifest, null, 2),\n 'utf-8',\n );\n } catch { /* non-critical */ }\n\n // -------------------------------------------------------------------------\n // 12. Success\n // -------------------------------------------------------------------------\n console.log(chalk.green(' All files generated and services started successfully.'));\n console.log();\n\n return 'success';\n}\n","/**\n * @module service-verifier\n * @description Service installation verification utilities.\n * After services start, verifies each service is accessible via HTTP health check.\n * Shows local URLs always; external URLs only when Cloudflare Tunnel is active.\n */\n\nimport type { WizardState } from '@brewnet/shared';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ServiceUrlEntry {\n serviceId: string;\n label: string;\n localUrl: string;\n externalUrl?: string;\n healthEndpoint?: string;\n /** Full URL for health check, overrides localUrl + healthEndpoint when set */\n healthUrl?: string;\n /** Milliseconds to wait before the first health check attempt (for slow-starting services) */\n startupDelay?: number;\n}\n\nexport interface VerifyResult {\n serviceId: string;\n label: string;\n localUrl: string;\n externalUrl?: string;\n status: 'ok' | 'warn' | 'fail' | 'skip';\n statusCode?: number;\n error?: string;\n}\n\n// ---------------------------------------------------------------------------\n// URL building\n// ---------------------------------------------------------------------------\n\n/**\n * Build the service URL map from wizard state.\n * localUrl is always present; externalUrl is only set when a tunnel is configured.\n *\n * Quick Tunnel: path-based external URLs (single hostname, /path per service)\n * Named Tunnel: subdomain-based external URLs (subdomain.domain per service)\n */\nexport function buildServiceUrlMap(state: WizardState): ServiceUrlEntry[] {\n const entries: ServiceUrlEntry[] = [];\n const { servers, domain, portRemapping = {} } = state;\n\n const hasTunnel =\n (domain.cloudflare.enabled || domain.provider === 'quick-tunnel') &&\n (domain.cloudflare.tunnelMode === 'quick' || domain.cloudflare.tunnelMode === 'named');\n const isQuickTunnel = domain.cloudflare.tunnelMode === 'quick';\n\n // Quick Tunnel base: already a full URL like \"https://xxx.trycloudflare.com\"\n const quickBase = (domain.cloudflare.quickTunnelUrl ?? '').replace(/\\/$/, '');\n\n // Named Tunnel domain: plain hostname like \"example.com\" (strip protocol if present)\n const namedDomain = (domain.name ?? '').replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\n function effectivePort(defaultPort: number): number {\n return portRemapping[defaultPort] ?? defaultPort;\n }\n\n // Web server HTTP port\n const httpPort = effectivePort(80);\n\n /**\n * Build external URL for a service.\n * Quick Tunnel: path-based routing (all services supported).\n * Named Tunnel: subdomain-based routing.\n */\n function extUrl(quickPath: string, namedSubdomain: string): string | undefined {\n if (!hasTunnel) return undefined;\n if (isQuickTunnel) {\n return quickBase ? `${quickBase}${quickPath}` : undefined;\n }\n return namedDomain ? `https://${namedSubdomain}.${namedDomain}` : undefined;\n }\n\n // Web server entry (port 80 → HTTP landing page)\n const webService = state.servers.webServer.service;\n entries.push({\n serviceId: webService,\n label: webService === 'traefik'\n ? 'Web Server (Traefik)'\n : webService === 'nginx'\n ? 'Web Server (Nginx)'\n : 'Web Server (Caddy)',\n localUrl: `http://localhost:${httpPort}`,\n externalUrl: hasTunnel\n ? isQuickTunnel\n ? (quickBase || undefined)\n : (namedDomain ? `https://${namedDomain}` : undefined)\n : undefined,\n healthEndpoint: '/',\n });\n\n // Traefik Dashboard — routed through Traefik labels on the HTTP port (80).\n // Port 8080 is NOT host-exposed; dashboard is behind BasicAuth on /dashboard/.\n // Health check hits /api/overview which returns 401 (BasicAuth), treated as ok (< 500).\n if (webService === 'traefik') {\n entries.push({\n serviceId: 'traefik-dashboard',\n label: 'Traefik Dashboard',\n localUrl: `http://localhost:${httpPort}/dashboard/`,\n healthUrl: `http://localhost:${httpPort}/api/overview`,\n });\n }\n\n // Gitea — Quick Tunnel: port 3000 is not host-exposed; access via Traefik path /git\n // Named Tunnel / no tunnel: direct host port (default 3000)\n // Ref: Gitea app.ini [server] HTTP_PORT = 3000\n const giteaPort = effectivePort(servers.gitServer.port);\n const giteaLocalUrl = isQuickTunnel\n ? `http://localhost:${httpPort}/git`\n : `http://localhost:${giteaPort}`;\n entries.push({\n serviceId: 'gitea',\n label: 'Gitea (Git)',\n localUrl: giteaLocalUrl,\n externalUrl: extUrl('/git', 'git'),\n healthEndpoint: '/',\n });\n\n // File Server — direct host port\n if (servers.fileServer.enabled) {\n if (servers.fileServer.service === 'nextcloud') {\n // Nextcloud: container port 80 → host 8443\n // Quick Tunnel: OVERWRITEWEBROOT=/cloud is set, so Nextcloud generates all URLs\n // with /cloud prefix. Direct port access (localhost:8443/) causes redirect to\n // /cloud/... which Apache cannot serve (no cloud/ subdirectory at root).\n // Must go through Traefik (port 80) with path /cloud so Traefik strips the\n // prefix before forwarding to the container — same pattern as Jellyfin.\n // Named Tunnel / no tunnel: OVERWRITEWEBROOT not set; direct port access works.\n // Ref: https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/reverse_proxy_configuration.html\n entries.push({\n serviceId: 'nextcloud',\n label: 'Nextcloud',\n localUrl: isQuickTunnel\n ? `http://localhost:${httpPort}/cloud`\n : `http://localhost:${effectivePort(8443)}`,\n externalUrl: extUrl('/cloud', 'cloud'),\n healthEndpoint: '/status.php',\n startupDelay: 30000,\n });\n } else if (servers.fileServer.service === 'minio') {\n // MinIO Console: port 9001, API: port 9000\n // Health /minio/health/live is on API port 9000, not console 9001\n // Ref: https://github.com/minio/docs\n entries.push({\n serviceId: 'minio',\n label: 'MinIO Console',\n localUrl: `http://localhost:${effectivePort(9001)}`,\n externalUrl: extUrl('/minio', 'minio'),\n healthEndpoint: '/',\n });\n }\n }\n\n // Media — Jellyfin: port 8096\n // Quick Tunnel: BaseUrl=/jellyfin → access via Traefik (port 80) so the web app\n // auto-detects the server URL as http://localhost/jellyfin (matching BaseUrl).\n // Direct port access (localhost:8096) causes the web app to detect http://localhost:8096\n // as the server URL, which mismatches BaseUrl=/jellyfin → first-screen fails to load.\n // Health check still uses the direct port (more reliable, bypasses Traefik startup timing).\n // Named Tunnel / no tunnel: BaseUrl not set; direct port access is correct.\n // Ref: https://jellyfin.org/docs/general/post-install/networking\n if (servers.media.enabled) {\n const jellyfinLocalUrl = isQuickTunnel\n ? `http://localhost:${httpPort}/jellyfin/web/#/wizard/start`\n : `http://localhost:${effectivePort(8096)}/web/#/wizard/start`;\n entries.push({\n serviceId: 'jellyfin',\n label: 'Jellyfin',\n localUrl: jellyfinLocalUrl,\n externalUrl: extUrl('/jellyfin', 'media'),\n healthUrl: isQuickTunnel\n ? `http://localhost:${effectivePort(8096)}/jellyfin/health`\n : `http://localhost:${effectivePort(8096)}/health`,\n });\n }\n\n // DB Admin UI — pgAdmin: container port 80 → host 5050\n // SCRIPT_NAME=/pgadmin requires all paths to include /pgadmin prefix.\n // Ref: https://www.pgadmin.org/docs/pgadmin4/latest/container_deployment\n if (servers.dbServer.enabled && servers.dbServer.adminUI && servers.dbServer.primary === 'postgresql') {\n entries.push({\n serviceId: 'pgadmin',\n label: 'pgAdmin',\n localUrl: `http://localhost:${effectivePort(5050)}/pgadmin`,\n externalUrl: extUrl('/pgadmin', 'db'),\n healthEndpoint: '/misc/ping',\n startupDelay: 45000, // pgAdmin4 takes 30-60s on first boot (Python/Flask + DB init)\n });\n }\n\n // FileBrowser (standalone) — container port 80 → host 8085\n // Ref: https://github.com/gtsteffaniak/filebrowser\n if (servers.fileBrowser.enabled && servers.fileBrowser.mode === 'standalone') {\n entries.push({\n serviceId: 'filebrowser',\n label: 'FileBrowser',\n localUrl: `http://localhost:${effectivePort(8085)}`,\n externalUrl: extUrl('/files', 'fb'),\n healthEndpoint: '/health',\n });\n }\n\n return entries;\n}\n\n// ---------------------------------------------------------------------------\n// Health check\n// ---------------------------------------------------------------------------\n\n/**\n * Verify that a service is accessible at the given URL.\n * Uses Node.js built-in fetch (Node 18+).\n * Returns status 'skip' when the URL is empty.\n */\nexport async function verifyServiceAccess(\n entry: ServiceUrlEntry,\n options: { timeout?: number; retries?: number } = {},\n): Promise<VerifyResult> {\n const { timeout = 5000, retries = 2 } = options;\n const url = entry.healthUrl ?? (entry.localUrl + (entry.healthEndpoint ?? '/'));\n\n if (entry.startupDelay) {\n await new Promise((r) => setTimeout(r, entry.startupDelay));\n }\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n const res = await fetch(url, { signal: controller.signal });\n clearTimeout(timer);\n // Accept any non-5xx response as \"reachable\"\n const status = res.status < 500 ? 'ok' : 'warn';\n return {\n serviceId: entry.serviceId,\n label: entry.label,\n localUrl: entry.localUrl,\n externalUrl: entry.externalUrl,\n status,\n statusCode: res.status,\n };\n } finally {\n clearTimeout(timer);\n }\n } catch (err) {\n if (attempt === retries) {\n return {\n serviceId: entry.serviceId,\n label: entry.label,\n localUrl: entry.localUrl,\n externalUrl: entry.externalUrl,\n status: 'fail',\n error: err instanceof Error ? err.message : String(err),\n };\n }\n // Wait 2s before retry\n await new Promise((r) => setTimeout(r, 2000));\n }\n }\n\n // Should not be reached, but TypeScript requires a return\n return {\n serviceId: entry.serviceId,\n label: entry.label,\n localUrl: entry.localUrl,\n externalUrl: entry.externalUrl,\n status: 'fail',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Service access guide\n// ---------------------------------------------------------------------------\n\nexport interface ServiceAccessInfo {\n serviceId: string;\n label: string;\n url: string;\n loginUser?: string;\n loginNote: string;\n homepage: string;\n}\n\n/**\n * Build a human-readable access guide for each installed service.\n * Shows the localhost URL and how to log in (or first-run instructions).\n * All URLs and notes are based on official documentation for each service.\n */\nexport function buildServiceAccessGuide(state: WizardState): ServiceAccessInfo[] {\n const portMap = state.portRemapping ?? {};\n const remap = (p: number): number => portMap[p] ?? p;\n const user = state.admin.username || 'admin';\n const entries: ServiceAccessInfo[] = [];\n const httpPort = remap(80);\n\n // Web server welcome page (port 80)\n const webService = state.servers.webServer.service;\n entries.push({\n serviceId: webService,\n label:\n webService === 'traefik'\n ? 'Web Server (Traefik)'\n : webService === 'nginx'\n ? 'Web Server (Nginx)'\n : 'Web Server (Caddy)',\n url: `http://localhost:${httpPort}`,\n loginNote: 'No login required — shows server info page',\n homepage: webService === 'traefik' ? 'https://traefik.io/traefik/' : webService === 'nginx' ? 'https://nginx.org/' : 'https://caddyserver.com/',\n });\n\n // Traefik Dashboard — routed through Traefik labels on port 80 with BasicAuth.\n if (webService === 'traefik') {\n entries.push({\n serviceId: 'traefik-dashboard',\n label: 'Traefik Dashboard',\n url: `http://localhost:${remap(80)}/dashboard/`,\n loginUser: user,\n loginNote: `Login: ${user} / <your password> (BasicAuth)`,\n homepage: 'https://doc.traefik.io/traefik/operations/dashboard/',\n });\n }\n\n // Gitea — Quick Tunnel: port 3000 not host-exposed, access via Traefik /git\n // Named Tunnel / no tunnel: direct host port (default 3000)\n // First visit: installation wizard (DB, admin account, site settings)\n // Ref: Gitea docs [server] HTTP_PORT = 3000\n const isQuickTunnelGuide = state.domain.cloudflare.tunnelMode === 'quick';\n const giteaPort = remap(state.servers.gitServer.port);\n const giteaUrl = isQuickTunnelGuide\n ? `http://localhost:${httpPort}/git/`\n : `http://localhost:${giteaPort}`;\n entries.push({\n serviceId: 'gitea',\n label: 'Gitea (Git)',\n url: giteaUrl,\n loginUser: user,\n loginNote: `First visit: installation wizard → then login as ${user}`,\n homepage: 'https://about.gitea.com/',\n });\n\n // Nextcloud — container port 80 → host 8443\n // Admin account auto-configured via NEXTCLOUD_ADMIN_USER/PASSWORD env vars\n // Ref: https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/reverse_proxy_configuration.html\n if (state.servers.fileServer.enabled) {\n if (state.servers.fileServer.service === 'nextcloud') {\n entries.push({\n serviceId: 'nextcloud',\n label: 'Nextcloud',\n url: isQuickTunnelGuide\n ? `http://localhost:${httpPort}/cloud`\n : `http://localhost:${remap(8443)}`,\n loginUser: user,\n loginNote: `Login: ${user} / <your password>`,\n homepage: 'https://nextcloud.com/',\n });\n } else if (state.servers.fileServer.service === 'minio') {\n // MinIO Console: port 9001 (--console-address :9001)\n // MinIO API: port 9000 (S3-compatible)\n // Ref: https://github.com/minio/docs\n entries.push({\n serviceId: 'minio',\n label: 'MinIO Console',\n url: `http://localhost:${remap(9001)}`,\n loginUser: user,\n loginNote: `Login: ${user} / <your password> (API: localhost:${remap(9000)})`,\n homepage: 'https://min.io/',\n });\n }\n }\n\n // Jellyfin — port 8096\n // Quick Tunnel: BaseUrl=/jellyfin → web UI at /jellyfin/web/#/wizard/start (first visit)\n // /jellyfin/web/#/home causes \"Server Mismatch\" when localStorage has a different server URL.\n // Use /wizard/start to avoid mismatch and force fresh setup flow.\n // Ref: https://jellyfin.org/docs/general/post-install/networking\n if (state.servers.media.enabled) {\n const isQuickTunnelAccess = state.domain.cloudflare.tunnelMode === 'quick';\n const jellyfinLocalUrl = isQuickTunnelAccess\n ? `http://localhost:${remap(8096)}/jellyfin/web/#/wizard/start`\n : `http://localhost:${remap(8096)}/web/#/wizard/start`;\n entries.push({\n serviceId: 'jellyfin',\n label: 'Jellyfin (Media)',\n url: jellyfinLocalUrl,\n loginNote: 'First visit: setup wizard — choose language, create admin account',\n homepage: 'https://jellyfin.org/',\n });\n }\n\n // pgAdmin — container port 80 → host 5050\n // Login: email (PGADMIN_DEFAULT_EMAIL) + password\n // Ref: https://www.pgadmin.org/docs/pgadmin4/latest/container_deployment\n if (\n state.servers.dbServer.enabled &&\n state.servers.dbServer.adminUI &&\n state.servers.dbServer.primary === 'postgresql'\n ) {\n entries.push({\n serviceId: 'pgadmin',\n label: 'pgAdmin (DB)',\n url: `http://localhost:${remap(5050)}/pgadmin`,\n loginUser: `${user}@brewnet.dev`,\n loginNote: `Login: ${user}@brewnet.dev / <your password>`,\n homepage: 'https://www.pgadmin.org/',\n });\n }\n\n // FileBrowser — container port 80 → host 8085\n // Login: configured admin username/password\n // Ref: https://github.com/gtsteffaniak/filebrowser\n if (state.servers.fileBrowser.enabled && state.servers.fileBrowser.mode === 'standalone') {\n entries.push({\n serviceId: 'filebrowser',\n label: 'FileBrowser',\n url: `http://localhost:${remap(8085)}`,\n loginUser: user,\n loginNote: `Login: ${user} / <your password>`,\n homepage: 'https://filebrowser.org/',\n });\n }\n\n return entries;\n}\n","/**\n * T062 — .env / .env.example File Generator\n *\n * Generates environment files from the wizard state:\n * - `.env` — contains all secret keys and configuration values\n * - `.env.example` — same keys with masked/placeholder values (safe to share)\n *\n * Uses credential-manager for admin credential propagation and\n * password generator for any missing secrets.\n *\n * @module services/env-generator\n */\n\nimport { writeFileSync, chmodSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { WizardState } from '@brewnet/shared';\nimport { generateCredentialEnvEntries } from './credential-manager.js';\nimport { generatePassword } from '../utils/password.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SecretFile {\n /** Relative path from project root, e.g. 'secrets/admin_password' */\n relativePath: string;\n /** File content — NO trailing newline (some Docker images include whitespace) */\n content: string;\n}\n\nexport interface EnvGeneratorResult {\n envContent: string;\n envExampleContent: string;\n secretFiles: SecretFile[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nimport { execSync } from 'node:child_process';\nimport { createHash } from 'node:crypto';\n\n/**\n * Generate an htpasswd-compatible apr1 hash string for BasicAuth.\n *\n * Tries `htpasswd -nb` first (most reliable), falls back to `openssl passwd -apr1`,\n * and finally falls back to a Node.js MD5-crypt implementation.\n *\n * The output is written to a secret file (secrets/traefik_dashboard_auth) and\n * read by Traefik via basicauth.usersfile — no `$$` escaping needed.\n *\n * @param username - The BasicAuth username\n * @param password - The BasicAuth password\n * @returns htpasswd string (e.g. `admin:$apr1$...`)\n */\nfunction generateHtpasswd(username: string, password: string): string {\n if (!password) return `${username}:<generate-password>`;\n\n let hash = '';\n\n // Try htpasswd\n try {\n const result = execSync(\n `htpasswd -nb ${shellEscape(username)} ${shellEscape(password)}`,\n { encoding: 'utf-8', timeout: 5000 },\n ).trim();\n if (result.includes(':$')) hash = result;\n } catch { /* fallback */ }\n\n // Try openssl\n if (!hash) {\n try {\n const raw = execSync(\n `openssl passwd -apr1 ${shellEscape(password)}`,\n { encoding: 'utf-8', timeout: 5000 },\n ).trim();\n if (raw.startsWith('$apr1$')) hash = `${username}:${raw}`;\n } catch { /* fallback */ }\n }\n\n // Node.js fallback: simple MD5 hash (not apr1, but works with Traefik)\n if (!hash) {\n const md5 = createHash('md5').update(password).digest('hex');\n hash = `${username}:{MD5}${Buffer.from(md5).toString('base64')}`;\n }\n\n // File-based secret: no $$ escaping needed (Traefik reads usersfile directly)\n return hash;\n}\n\n/** Escape a string for safe use in a shell command. */\nfunction shellEscape(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/**\n * Determine whether a key holds a secret value that should be masked\n * in .env.example.\n */\nfunction isSecretKey(key: string): boolean {\n return (\n key.includes('PASSWORD') ||\n key.includes('SECRET') ||\n key.includes('TOKEN') ||\n key === 'TRAEFIK_DASHBOARD_AUTH'\n );\n}\n\n/**\n * Return a masked placeholder for a given key.\n */\nfunction maskValue(key: string): string {\n if (key === 'TRAEFIK_DASHBOARD_AUTH') return '<htpasswd-hash>';\n if (key.includes('PASSWORD')) return '<your-password>';\n if (key.includes('SECRET')) return '<your-secret>';\n if (key.includes('TOKEN')) return '<your-token>';\n return '<your-value>';\n}\n\n/**\n * Map env var key → secret file relative path.\n * Keys not listed here stay in .env. MINIO_ROOT_PASSWORD and\n * CLOUDFLARE_TUNNEL_TOKEN stay in .env because their images don't\n * support _FILE convention.\n */\nconst ENV_TO_SECRET_FILE: Record<string, string> = {\n BREWNET_ADMIN_PASSWORD: 'secrets/admin_password',\n POSTGRES_PASSWORD: 'secrets/db_password',\n MYSQL_ROOT_PASSWORD: 'secrets/db_password',\n MYSQL_PASSWORD: 'secrets/db_password',\n 'GITEA__security__SECRET_KEY': 'secrets/gitea_secret_key',\n GITEA_ADMIN_PASSWORD: 'secrets/admin_password',\n NEXTCLOUD_ADMIN_PASSWORD: 'secrets/admin_password',\n PGADMIN_DEFAULT_PASSWORD: 'secrets/admin_password',\n TRAEFIK_DASHBOARD_AUTH: 'secrets/traefik_dashboard_auth',\n // CLOUDFLARE_TUNNEL_TOKEN stays in .env — cloudflared image does not support _FILE convention\n // MINIO_ROOT_PASSWORD stays in .env (_FILE not supported)\n};\n\n/**\n * Serialize a key-value record into .env file format.\n * Entries are grouped by section with comment headers.\n */\nfunction serializeEnv(\n entries: Record<string, string>,\n mask: boolean,\n): string {\n const lines: string[] = [];\n\n for (const [key, value] of Object.entries(entries)) {\n if (mask && isSecretKey(key)) {\n lines.push(`${key}=${maskValue(key)}`);\n } else {\n lines.push(`${key}=${value}`);\n }\n }\n\n return lines.join('\\n') + '\\n';\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Generate .env and .env.example file contents from the wizard state.\n *\n * Collects entries from:\n * 1. Admin credentials + credential propagation (credential-manager)\n * 2. Cloudflare Tunnel token\n * 3. Domain configuration\n * 5. Generated secret keys (Gitea secret, etc.)\n *\n * Any PASSWORD/SECRET/TOKEN entry that is empty will be filled with a\n * generated random password.\n */\nexport function generateEnvFiles(state: WizardState): EnvGeneratorResult {\n const entries: Record<string, string> = {};\n\n // ── 1. Admin credentials + credential propagation ──────────────────\n const credentialEntries = generateCredentialEnvEntries(state);\n Object.assign(entries, credentialEntries);\n\n // ── 2. Cloudflare Tunnel ───────────────────────────────────────────\n if (state.domain.cloudflare.enabled && state.domain.cloudflare.tunnelToken) {\n entries['CLOUDFLARE_TUNNEL_TOKEN'] = state.domain.cloudflare.tunnelToken;\n }\n\n // ── 4. Traefik Dashboard BasicAuth ─────────────────────────────────\n // htpasswd format with $$ escaping for docker-compose interpolation.\n // Generated at install time via openssl; falls back to a placeholder.\n entries['TRAEFIK_DASHBOARD_AUTH'] = generateHtpasswd(\n state.admin.username || 'admin',\n state.admin.password || '',\n );\n\n // ── 6. Domain ──────────────────────────────────────────────────────\n entries['BREWNET_DOMAIN'] = state.domain.name || 'brewnet.local';\n\n // ── 7. Ensure all secret entries have non-empty values ─────────────\n for (const key of Object.keys(entries)) {\n if (isSecretKey(key) && !entries[key]) {\n entries[key] = generatePassword(16);\n }\n }\n\n // ── Split entries into .env (non-secret) and secret files ──────────\n const envEntries: Record<string, string> = {};\n const secretFileMap = new Map<string, string>(); // relativePath → content\n\n for (const [key, value] of Object.entries(entries)) {\n const secretPath = ENV_TO_SECRET_FILE[key];\n if (secretPath) {\n // Deduplicate: multiple env vars may map to the same secret file\n // (e.g. GITEA_ADMIN_PASSWORD, NEXTCLOUD_ADMIN_PASSWORD → admin_password)\n if (!secretFileMap.has(secretPath)) {\n secretFileMap.set(secretPath, value);\n }\n } else {\n envEntries[key] = value;\n }\n }\n\n const secretFiles: SecretFile[] = Array.from(secretFileMap.entries()).map(\n ([relativePath, content]) => ({ relativePath, content }),\n );\n\n // ── Build output ───────────────────────────────────────────────────\n const envContent = serializeEnv(envEntries, false);\n const envExampleContent = serializeEnv(entries, true);\n\n return { envContent, envExampleContent, secretFiles };\n}\n\n/**\n * Write the .env file to disk with restrictive permissions (chmod 600).\n *\n * Creates the parent directory if it does not exist.\n *\n * @param projectPath - Absolute path to the project directory\n * @param content - The .env file content to write\n */\nexport function writeEnvFile(projectPath: string, content: string): void {\n const filePath = join(projectPath, '.env');\n mkdirSync(projectPath, { recursive: true });\n writeFileSync(filePath, content, { encoding: 'utf-8', mode: 0o600 });\n chmodSync(filePath, 0o600);\n}\n\n/**\n * Write the .env.example file to disk.\n *\n * This file contains masked/placeholder values and is safe to commit\n * to version control.\n *\n * @param projectPath - Absolute path to the project directory\n * @param content - The .env.example file content to write\n */\nexport function writeEnvExampleFile(projectPath: string, content: string): void {\n const filePath = join(projectPath, '.env.example');\n mkdirSync(projectPath, { recursive: true });\n writeFileSync(filePath, content, { encoding: 'utf-8' });\n}\n\n/**\n * Write secret files to disk with restrictive permissions.\n *\n * Creates the `secrets/` directory (chmod 700) and writes each secret\n * file (chmod 600). No trailing newline in file content — some Docker\n * images include whitespace when reading secrets.\n *\n * @param projectPath - Absolute path to the project directory\n * @param secretFiles - Array of SecretFile objects to write\n */\nexport function writeSecretFiles(projectPath: string, secretFiles: SecretFile[]): void {\n if (secretFiles.length === 0) return;\n\n const secretsDir = join(projectPath, 'secrets');\n mkdirSync(secretsDir, { recursive: true });\n chmodSync(secretsDir, 0o700);\n\n for (const sf of secretFiles) {\n const filePath = join(projectPath, sf.relativePath);\n writeFileSync(filePath, sf.content, { encoding: 'utf-8', mode: 0o600 });\n chmodSync(filePath, 0o600);\n }\n}\n","/**\n * T059 — Credential Manager\n *\n * Determines which services receive the admin credentials during setup,\n * and generates .env entries for credential propagation.\n *\n * Admin credentials (username + password) from Step 2 are propagated\n * to all enabled services that require authentication.\n *\n * @module services/credential-manager\n */\n\nimport type { WizardState } from '@brewnet/shared';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface CredentialTarget {\n /** Service identifier (e.g. 'gitea', 'postgresql') */\n service: string;\n /** Environment variable name (e.g. 'GITEA_ADMIN_PASSWORD') */\n envVar: string;\n /** Human-readable description (e.g. 'Gitea admin password') */\n description: string;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Get all credential propagation targets based on enabled services.\n *\n * Returns a list of services that will receive the admin credentials\n * during setup. Each target includes the service name, the environment\n * variable that will be set, and a human-readable description.\n *\n * @param state - Current wizard state\n * @returns Array of credential targets for enabled services\n */\nexport function getCredentialPropagationTargets(\n state: WizardState,\n): CredentialTarget[] {\n const targets: CredentialTarget[] = [];\n const s = state.servers;\n\n // Gitea always receives admin credentials (Git Server is required)\n targets.push({\n service: 'gitea',\n envVar: 'GITEA__security__SECRET_KEY',\n description: 'Gitea secret key',\n });\n targets.push({\n service: 'gitea',\n envVar: 'GITEA_ADMIN_PASSWORD',\n description: 'Gitea admin password',\n });\n\n // PostgreSQL\n if (s.dbServer.enabled && s.dbServer.primary === 'postgresql') {\n targets.push({\n service: 'postgresql',\n envVar: 'POSTGRES_PASSWORD',\n description: 'PostgreSQL root password',\n });\n }\n\n // MySQL\n if (s.dbServer.enabled && s.dbServer.primary === 'mysql') {\n targets.push({\n service: 'mysql',\n envVar: 'MYSQL_ROOT_PASSWORD',\n description: 'MySQL root password',\n });\n }\n\n // pgAdmin (DB admin UI for PostgreSQL)\n if (\n s.dbServer.enabled &&\n s.dbServer.adminUI &&\n s.dbServer.primary === 'postgresql'\n ) {\n targets.push({\n service: 'pgadmin',\n envVar: 'PGADMIN_DEFAULT_PASSWORD',\n description: 'pgAdmin default password',\n });\n }\n\n // Nextcloud\n if (s.fileServer.enabled && s.fileServer.service === 'nextcloud') {\n targets.push({\n service: 'nextcloud',\n envVar: 'NEXTCLOUD_ADMIN_PASSWORD',\n description: 'Nextcloud admin password',\n });\n }\n\n // MinIO\n if (s.fileServer.enabled && s.fileServer.service === 'minio') {\n targets.push({\n service: 'minio',\n envVar: 'MINIO_ROOT_PASSWORD',\n description: 'MinIO root password',\n });\n }\n\n return targets;\n}\n\n/**\n * Generate .env entries for credential propagation.\n *\n * Returns a key-value map of environment variable names to their\n * values, based on the admin credentials and enabled services.\n * These entries should be written to the project's .env file.\n *\n * @param state - Current wizard state\n * @returns Record of environment variable entries\n */\nexport function generateCredentialEnvEntries(\n state: WizardState,\n): Record<string, string> {\n const entries: Record<string, string> = {};\n const { username, password } = state.admin;\n\n // Always include admin credentials\n entries['BREWNET_ADMIN_USERNAME'] = username;\n entries['BREWNET_ADMIN_PASSWORD'] = password;\n\n // Get all targets and set their env vars\n const targets = getCredentialPropagationTargets(state);\n\n for (const target of targets) {\n // For password env vars, use the admin password\n // For other vars like secret keys, also use the admin password as the base\n entries[target.envVar] = password;\n }\n\n // Additional service-specific entries that use the admin username\n\n // Gitea admin username\n entries['GITEA_ADMIN_USER'] = username;\n\n // pgAdmin uses email format for the default admin\n const s = state.servers;\n if (\n s.dbServer.enabled &&\n s.dbServer.adminUI &&\n s.dbServer.primary === 'postgresql'\n ) {\n entries['PGADMIN_DEFAULT_EMAIL'] = `${username}@brewnet.dev`;\n }\n\n // Nextcloud admin username\n if (s.fileServer.enabled && s.fileServer.service === 'nextcloud') {\n entries['NEXTCLOUD_ADMIN_USER'] = username;\n }\n\n // MinIO root user\n if (s.fileServer.enabled && s.fileServer.service === 'minio') {\n entries['MINIO_ROOT_USER'] = username;\n }\n\n // Database credentials (these use the DB-specific password, not admin password)\n if (s.dbServer.enabled && s.dbServer.primary === 'postgresql') {\n entries['POSTGRES_USER'] = s.dbServer.dbUser;\n entries['POSTGRES_DB'] = s.dbServer.dbName;\n entries['POSTGRES_PASSWORD'] = s.dbServer.dbPassword || password;\n }\n\n if (s.dbServer.enabled && s.dbServer.primary === 'mysql') {\n entries['MYSQL_DATABASE'] = s.dbServer.dbName;\n entries['MYSQL_USER'] = s.dbServer.dbUser;\n entries['MYSQL_PASSWORD'] = s.dbServer.dbPassword || password;\n entries['MYSQL_ROOT_PASSWORD'] = password;\n }\n\n return entries;\n}\n","/**\n * T068 — Infrastructure Config Generator\n *\n * Generates infrastructure configuration files from WizardState.\n * Produces sshd_config, Gitea app.ini, FileBrowser config, Traefik config,\n * and supports template variable substitution.\n *\n * Design principles:\n * - Secure by Default: SSH key-only auth, no root login\n * - Transparent: All generated configs are human-readable\n *\n * @module services/config-generator\n */\n\nimport type { WizardState, GeneratedFile } from '@brewnet/shared';\n\n// ---------------------------------------------------------------------------\n// Template Variable Substitution\n// ---------------------------------------------------------------------------\n\n/**\n * Replace template variables in a string with values from WizardState.\n *\n * Supported variables:\n * - `${PROJECT_NAME}` -> state.projectName\n * - `${DOMAIN}` -> state.domain.name\n * - `${ADMIN_USER}` -> state.admin.username\n *\n * Unknown variables are left unchanged.\n *\n * @param template - Template string containing `${VAR}` placeholders\n * @param state - Current wizard state\n * @returns The template with known variables replaced\n */\nexport function substituteTemplateVars(\n template: string,\n state: WizardState,\n): string {\n const vars: Record<string, string> = {\n PROJECT_NAME: state.projectName,\n DOMAIN: state.domain.name,\n ADMIN_USER: state.admin.username,\n };\n\n return template.replace(/\\$\\{([A-Z_]+)\\}/g, (match, varName: string) => {\n return varName in vars ? vars[varName] : match;\n });\n}\n\n// ---------------------------------------------------------------------------\n// SSH — sshd_config\n// ---------------------------------------------------------------------------\n\n/**\n * Generate an OpenSSH sshd_config based on wizard state.\n *\n * Security defaults (per Brewnet constitution):\n * - PubkeyAuthentication yes (always)\n * - PermitRootLogin no (always)\n * - PasswordAuthentication no (unless explicitly enabled)\n *\n * @param state - Current wizard state\n * @returns GeneratedFile with path and sshd_config content\n */\nexport function generateSshdConfig(state: WizardState): GeneratedFile {\n const ssh = state.servers.sshServer;\n const passwordAuth = ssh.passwordAuth ? 'yes' : 'no';\n\n const lines: string[] = [\n '# Brewnet SSH Server Configuration',\n '# Generated by brewnet config-generator',\n '',\n `Port ${ssh.port}`,\n 'AddressFamily any',\n 'ListenAddress 0.0.0.0',\n 'ListenAddress ::',\n '',\n '# Authentication',\n 'PermitRootLogin no',\n 'PubkeyAuthentication yes',\n `PasswordAuthentication ${passwordAuth}`,\n 'ChallengeResponseAuthentication no',\n 'UsePAM yes',\n '',\n '# Security',\n 'X11Forwarding no',\n 'PrintMotd no',\n 'AcceptEnv LANG LC_*',\n 'MaxAuthTries 3',\n 'MaxSessions 10',\n '',\n ];\n\n // SFTP subsystem\n if (ssh.sftp) {\n lines.push('# SFTP Subsystem');\n lines.push('Subsystem sftp /usr/lib/openssh/sftp-server');\n lines.push('');\n }\n\n return {\n path: 'infrastructure/ssh/sshd_config',\n content: lines.join('\\n'),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Gitea — app.ini\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a Gitea app.ini configuration file.\n *\n * Includes [server], [database], [security] sections with admin\n * username and domain from wizard state.\n *\n * @param state - Current wizard state\n * @returns GeneratedFile with path and app.ini content\n */\nexport function generateGiteaConfig(state: WizardState): GeneratedFile {\n const git = state.servers.gitServer;\n const domain = state.domain.name;\n const admin = state.admin;\n\n const lines: string[] = [\n '; Brewnet Gitea Configuration',\n '; Generated by brewnet config-generator',\n '',\n '[server]',\n `DOMAIN = ${domain}`,\n `HTTP_PORT = ${git.port}`,\n `ROOT_URL = http://${domain}:${git.port}/`,\n `SSH_PORT = ${git.sshPort}`,\n `SSH_DOMAIN = ${domain}`,\n 'DISABLE_SSH = false',\n 'START_SSH_SERVER = true',\n 'LFS_START_SERVER = true',\n '',\n '[database]',\n 'DB_TYPE = sqlite3',\n 'PATH = /data/gitea/gitea.db',\n '',\n '[security]',\n 'INSTALL_LOCK = true',\n `INTERNAL_TOKEN = auto`,\n `SECRET_KEY = auto`,\n '',\n '[service]',\n 'DISABLE_REGISTRATION = true',\n 'REQUIRE_SIGNIN_VIEW = true',\n 'DEFAULT_KEEP_EMAIL_PRIVATE = true',\n '',\n '[admin]',\n `ADMIN_NAME = ${admin.username}`,\n '',\n '[log]',\n 'MODE = console',\n 'LEVEL = info',\n '',\n ];\n\n return {\n path: 'infrastructure/gitea/app.ini',\n content: lines.join('\\n'),\n };\n}\n\n// ---------------------------------------------------------------------------\n// FileBrowser — filebrowser.json\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a FileBrowser configuration file.\n *\n * Returns null if FileBrowser is disabled in the wizard state.\n *\n * @param state - Current wizard state\n * @returns GeneratedFile with filebrowser.json content, or null if disabled\n */\nexport function generateFileBrowserConfig(\n state: WizardState,\n): GeneratedFile | null {\n const fb = state.servers.fileBrowser;\n\n if (!fb.enabled) {\n return null;\n }\n\n const config: Record<string, unknown> = {\n port: 80,\n baseURL: '',\n address: '0.0.0.0',\n log: 'stdout',\n database: '/database.db',\n root: fb.mode === 'directory' ? '/srv/data' : '/srv',\n auth: {\n method: 'json',\n header: '',\n recaptcha: {\n host: '',\n key: '',\n secret: '',\n },\n },\n };\n\n return {\n path: 'infrastructure/filebrowser/filebrowser.json',\n content: JSON.stringify(config, null, 2),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Traefik — traefik.yml\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a Traefik static configuration file.\n *\n * Includes entryPoints for HTTP/HTTPS and Docker provider settings.\n *\n * @param state - Current wizard state\n * @returns GeneratedFile with traefik.yml content\n */\nexport function generateTraefikConfig(state: WizardState): GeneratedFile {\n const isQuickTunnel = state.domain.cloudflare.tunnelMode === 'quick';\n\n const lines: string[] = [\n '# Brewnet Traefik Configuration',\n '# Generated by brewnet config-generator',\n '',\n '# Entry Points',\n 'entryPoints:',\n ' web:',\n ' address: \":80\"',\n ' websecure:',\n ' address: \":443\"',\n '',\n '# Docker Provider',\n 'providers:',\n ' docker:',\n ' endpoint: \"unix:///var/run/docker.sock\"',\n ' exposedByDefault: false',\n ' network: brewnet',\n ' file:',\n ' directory: /etc/traefik/dynamic',\n ' watch: true',\n '',\n '# API / Dashboard',\n 'api:',\n ' dashboard: true',\n // insecure mode only in Quick Tunnel (local-only). Named Tunnel / SSL exposes host externally.\n ...(isQuickTunnel ? [' insecure: true'] : []),\n '',\n '# Logging',\n 'log:',\n ' level: INFO',\n '',\n ];\n\n if (state.domain.ssl === 'letsencrypt') {\n const email = `admin@${state.domain.name ?? 'example.com'}`;\n lines.push(\n '# ACME Certificate Resolver (Let\\'s Encrypt)',\n 'certificatesResolvers:',\n ' letsencrypt:',\n ' acme:',\n ` email: ${email}`,\n ' storage: /letsencrypt/acme.json',\n ' httpChallenge:',\n ' entryPoint: web',\n '',\n );\n }\n\n return {\n path: 'infrastructure/traefik/traefik.yml',\n content: lines.join('\\n'),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Landing Page — Dockerfile + nginx.conf + index.html\n// ---------------------------------------------------------------------------\n\n/**\n * Generate the Brewnet landing page files used as Traefik catch-all.\n *\n * Replaces traefik/whoami to prevent internal infrastructure info leakage.\n * Produces three files under `landing/` in the project directory:\n * - Dockerfile (nginx:alpine based)\n * - nginx.conf (security headers, server_tokens off)\n * - index.html (branded landing page)\n *\n * @returns Array of GeneratedFile for the landing page build context\n */\nexport function generateLandingPage(): GeneratedFile[] {\n const dockerfile = `FROM nginx:1.27-alpine\n\n# Remove default config, apply custom\nRUN rm /etc/nginx/conf.d/default.conf\n\nCOPY nginx.conf /etc/nginx/conf.d/default.conf\nCOPY index.html /usr/share/nginx/html/index.html\n\n# Non-root permissions\nRUN chown -R nginx:nginx /usr/share/nginx/html && \\\\\n chmod -R 755 /usr/share/nginx/html\n\nEXPOSE 80\n\nHEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\\\\n CMD wget -qO- http://127.0.0.1/health || exit 1\n`;\n\n const nginxConf = `server {\n listen 80;\n server_name _;\n\n root /usr/share/nginx/html;\n index index.html;\n\n # Hide server version\n server_tokens off;\n\n # Security + cache headers\n # no-store: prevent browser heuristic caching when transiently served for sub-service paths\n # (e.g. /git/user/sign_up during Gitea mode switch — without this, browsers cache the landing\n # page response via ETag heuristic for ~3 hours, persisting even after manual cache-clear)\n add_header Cache-Control \"no-store\" always;\n add_header X-Content-Type-Options \"nosniff\" always;\n add_header X-Frame-Options \"DENY\" always;\n add_header X-XSS-Protection \"1; mode=block\" always;\n add_header Referrer-Policy \"no-referrer\" always;\n add_header Permissions-Policy \"camera=(), microphone=(), geolocation=()\" always;\n add_header Content-Security-Policy \"default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com\" always;\n\n # All paths -> landing page\n location / {\n try_files $uri /index.html;\n }\n\n # Traefik health check\n location = /health {\n access_log off;\n default_type application/json;\n return 200 '{\"status\":\"ok\",\"service\":\"brewnet-landing\"}';\n }\n\n # Block hidden files\n location ~ /\\\\\\\\. {\n deny all;\n return 404;\n }\n\n # Block unnecessary methods\n if ($request_method !~ ^(GET|HEAD)$) {\n return 405;\n }\n}\n`;\n\n const indexHtml = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Brewnet — Your server on tap</title>\n <meta name=\"description\" content=\"Brewnet home server management platform\">\n <meta name=\"robots\" content=\"noindex, nofollow\">\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=DM+Sans:wght@400;500&display=swap\" rel=\"stylesheet\">\n <style>\n *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }\n :root {\n --amber: #f59e0b; --amber-dim: #b45309; --surface: #0a0a0a;\n --surface-raised: #141414; --border: #1f1f1f; --text: #e5e5e5;\n --text-muted: #525252; --green: #22c55e;\n --mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;\n --sans: 'DM Sans', system-ui, sans-serif;\n }\n body { min-height:100vh; display:flex; flex-direction:column; align-items:center;\n justify-content:center; font-family:var(--sans); background:var(--surface);\n color:var(--text); overflow:hidden; }\n body::after { content:''; position:fixed; inset:0;\n background-image:url(\"data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.025'/%3E%3C/svg%3E\");\n pointer-events:none; z-index:0; }\n .glow { position:fixed; width:600px; height:600px; border-radius:50%;\n background:radial-gradient(circle,rgba(245,158,11,0.04) 0%,transparent 70%);\n top:50%; left:50%; transform:translate(-50%,-50%); pointer-events:none; z-index:0; }\n .container { position:relative; z-index:1; text-align:center; padding:2rem;\n animation:fadeIn .8s ease-out; }\n @keyframes fadeIn { from{opacity:0;transform:translateY(12px)} to{opacity:1;transform:translateY(0)} }\n .icon { margin-bottom:1.5rem; filter:drop-shadow(0 0 20px rgba(245,158,11,0.2)); }\n .icon svg { width:64px; height:64px; color:var(--amber); }\n h1 { font-family:var(--mono); font-size:clamp(2.2rem,6vw,3.8rem); font-weight:700;\n letter-spacing:-0.03em; line-height:1; margin-bottom:.4rem; }\n h1 .brew { color:var(--amber); }\n h1 .net { color:var(--text); }\n .tagline { font-family:var(--mono); font-size:.82rem; color:var(--text-muted);\n letter-spacing:.15em; text-transform:uppercase; margin-bottom:2.5rem; }\n .status { display:inline-flex; align-items:center; gap:.6rem; padding:.55rem 1.3rem;\n border:1px solid var(--border); border-radius:100px; font-family:var(--mono);\n font-size:.78rem; color:#737373; background:var(--surface-raised); transition:border-color .3s; }\n .status:hover { border-color:#2a2a2a; }\n .status .dot { width:7px; height:7px; border-radius:50%; background:var(--green);\n box-shadow:0 0 6px rgba(34,197,94,0.4); animation:pulse 2.5s ease-in-out infinite; }\n @keyframes pulse { 0%,100%{opacity:1;box-shadow:0 0 6px rgba(34,197,94,0.4)}\n 50%{opacity:.5;box-shadow:0 0 2px rgba(34,197,94,0.2)} }\n .footer { position:fixed; bottom:1.2rem; left:0; right:0; text-align:center;\n font-family:var(--mono); font-size:.65rem; color:#2a2a2a; letter-spacing:.08em; z-index:1; }\n .footer span { color:#333; }\n @media(max-width:480px) { .icon{font-size:2.4rem} .status{font-size:.72rem;padding:.45rem 1rem} }\n </style>\n</head>\n<body>\n <div class=\"glow\"></div>\n <div class=\"container\">\n <div class=\"icon\">\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M8 26H32V34C32 36.8 29.8 39 27 39H13C10.2 39 8 36.8 8 34V26Z\" stroke-width=\"3.2\" fill=\"none\"/>\n <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.2\" fill=\"none\"/>\n <circle cx=\"20\" cy=\"30\" r=\"1.8\" fill=\"currentColor\" stroke=\"none\"/>\n <path d=\"M16.5 20a5 5 0 0 1 7 0\" stroke-width=\"3\" fill=\"none\"/>\n <path d=\"M13.5 15.5a10 10 0 0 1 13 0\" stroke-width=\"3\" fill=\"none\"/>\n <path d=\"M10.5 11a15 15 0 0 1 19 0\" stroke-width=\"3\" fill=\"none\"/>\n </svg>\n </div>\n <h1><span class=\"brew\">Brew</span><span class=\"net\">net</span></h1>\n <p class=\"tagline\">Your server on tap &middot; Just brew it</p>\n <div class=\"status\">\n <span class=\"dot\"></span>\n systems operational\n </div>\n </div>\n <div class=\"footer\">\n brewnet.dev <span>&middot;</span> MIT\n </div>\n</body>\n</html>\n`;\n\n return [\n { path: 'landing/Dockerfile', content: dockerfile },\n { path: 'landing/nginx.conf', content: nginxConf },\n { path: 'landing/index.html', content: indexHtml },\n ];\n}\n\n// ---------------------------------------------------------------------------\n// Orchestrator — generateInfraConfigs\n// ---------------------------------------------------------------------------\n\n/**\n * Generate all infrastructure configuration files based on wizard state.\n *\n * Orchestrates all individual generators and returns a flat array of\n * GeneratedFile objects. Only includes configs for enabled services.\n *\n * Always included:\n * - Traefik config (web server is always enabled)\n * - Gitea app.ini (git server is always enabled)\n *\n * Conditionally included:\n * - sshd_config (when SSH server is enabled)\n * - filebrowser.json (when FileBrowser is enabled)\n *\n * @param state - Current wizard state\n * @returns Array of GeneratedFile objects for all enabled infrastructure\n */\nexport function generateInfraConfigs(state: WizardState): GeneratedFile[] {\n const files: GeneratedFile[] = [];\n\n // Traefik (always included — web server is required)\n files.push(generateTraefikConfig(state));\n\n // Landing page (always included — catch-all for Traefik, replaces whoami)\n files.push(...generateLandingPage());\n\n // Gitea (always included — git server is required)\n files.push(generateGiteaConfig(state));\n\n // SSH (conditional)\n if (state.servers.sshServer.enabled) {\n files.push(generateSshdConfig(state));\n }\n\n // FileBrowser (conditional)\n const fbConfig = generateFileBrowserConfig(state);\n if (fbConfig) {\n files.push(fbConfig);\n }\n\n return files;\n}\n","/**\n * T069 — Health Checker Service\n *\n * Provides service categorization, dependency-based sorting, Docker Compose\n * command builders, health check polling, endpoint generation, and external\n * access verification utilities for the Step 6 (Generate & Start) flow.\n *\n * Service startup order:\n * infrastructure → database → application → utility\n *\n * @module services/health-checker\n */\n\nimport type Dockerode from 'dockerode';\nimport type { WizardState } from '@brewnet/shared';\nimport {\n HEALTH_CHECK_TIMEOUT_MS,\n HEALTH_CHECK_INTERVAL_MS,\n DOCKER_COMPOSE_FILENAME,\n} from '@brewnet/shared';\nimport { getCredentialTargets } from '../utils/resources.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nexport { DOCKER_COMPOSE_FILENAME };\nexport const HEALTH_CHECK_TIMEOUT = HEALTH_CHECK_TIMEOUT_MS;\nexport const HEALTH_CHECK_INTERVAL = HEALTH_CHECK_INTERVAL_MS;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ServiceCategory =\n | 'infrastructure'\n | 'database'\n | 'application'\n | 'utility';\n\nexport interface HealthCheckResult {\n service: string;\n healthy: boolean;\n elapsed: number;\n error?: string;\n}\n\nexport interface EndpointInfo {\n service: string;\n url: string;\n credentials?: { username: string; password: string };\n}\n\n// ---------------------------------------------------------------------------\n// Service Categorization\n// ---------------------------------------------------------------------------\n\nconst INFRA_SERVICES = ['traefik', 'nginx', 'caddy', 'cloudflared'];\nconst DB_SERVICES = ['postgresql', 'mysql'];\nconst UTILITY_SERVICES = [\n 'pgadmin',\n 'filebrowser',\n 'openssh-server',\n 'docker-mailserver',\n];\n\n/**\n * Categorize a service ID for dependency ordering.\n *\n * Infrastructure (web server, tunnel) → Database → Application → Utility\n */\nexport function categorizeService(serviceId: string): ServiceCategory {\n if (INFRA_SERVICES.includes(serviceId)) return 'infrastructure';\n if (DB_SERVICES.includes(serviceId)) return 'database';\n if (UTILITY_SERVICES.includes(serviceId)) return 'utility';\n return 'application';\n}\n\n// ---------------------------------------------------------------------------\n// Dependency Sorting\n// ---------------------------------------------------------------------------\n\nconst CATEGORY_ORDER: Record<ServiceCategory, number> = {\n infrastructure: 0,\n database: 1,\n application: 2,\n utility: 3,\n};\n\n/**\n * Sort services into dependency order for startup.\n * Order: infrastructure → database → application → utility.\n *\n * Stable sort: services within the same category preserve their original order.\n */\nexport function sortByDependency(serviceIds: string[]): string[] {\n return [...serviceIds].sort((a, b) => {\n return CATEGORY_ORDER[categorizeService(a)] - CATEGORY_ORDER[categorizeService(b)];\n });\n}\n\n// ---------------------------------------------------------------------------\n// Docker Compose Command Builders\n// ---------------------------------------------------------------------------\n\n/**\n * Build `docker compose pull` command arguments.\n */\nexport function buildPullCommand(\n composePath: string,\n): { cmd: string; args: string[] } {\n return {\n cmd: 'docker',\n args: ['compose', '-f', composePath, 'pull'],\n };\n}\n\n/**\n * Build `docker compose up -d --force-recreate --remove-orphans` command arguments.\n *\n * --force-recreate : recreate containers even if config has not changed,\n * resolving \"container name already in use\" conflicts on\n * re-runs of `brewnet init`.\n * --remove-orphans : remove containers for services not defined in the\n * current compose file (handles partial uninstall remnants).\n */\nexport function buildUpCommand(\n composePath: string,\n): { cmd: string; args: string[] } {\n return {\n cmd: 'docker',\n args: ['compose', '-f', composePath, 'up', '-d', '--force-recreate', '--remove-orphans'],\n };\n}\n\n/**\n * Build `docker compose down --remove-orphans` command for rollback.\n */\nexport function buildDownCommand(\n composePath: string,\n): { cmd: string; args: string[] } {\n return {\n cmd: 'docker',\n args: ['compose', '-f', composePath, 'down', '--remove-orphans'],\n };\n}\n\n/**\n * Build `docker compose logs <service>` command for debugging failures.\n */\nexport function buildLogsCommand(\n composePath: string,\n service: string,\n): { cmd: string; args: string[] } {\n return {\n cmd: 'docker',\n args: ['compose', '-f', composePath, 'logs', '--tail', '50', service],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Health Check Polling\n// ---------------------------------------------------------------------------\n\n/**\n * Poll a Docker container's health status until it becomes healthy or\n * the timeout is reached.\n *\n * @param service - Service identifier for reporting\n * @param docker - Dockerode instance\n * @param timeoutMs - Maximum time to wait (default: HEALTH_CHECK_TIMEOUT)\n * @param intervalMs - Poll interval (default: HEALTH_CHECK_INTERVAL)\n * @returns Promise resolving to the final HealthCheckResult\n */\nexport async function pollHealthCheck(\n service: string,\n docker: Dockerode,\n timeoutMs: number = HEALTH_CHECK_TIMEOUT,\n intervalMs: number = HEALTH_CHECK_INTERVAL,\n): Promise<HealthCheckResult> {\n const start = Date.now();\n\n while (true) {\n const elapsed = Date.now() - start;\n\n if (elapsed >= timeoutMs) {\n return {\n service,\n healthy: false,\n elapsed,\n error: `Health check timeout after ${Math.round(elapsed / 1000)}s`,\n };\n }\n\n try {\n const containers = await docker.listContainers({\n filters: { name: [service] },\n });\n\n if (containers.length > 0) {\n const container = containers[0];\n const state = container.State;\n const status = container.Status || '';\n\n if (state === 'running' && status.includes('(healthy)')) {\n return {\n service,\n healthy: true,\n elapsed,\n };\n }\n\n // If the container has no healthcheck, consider \"running\" as healthy\n if (state === 'running' && !status.includes('(health:')) {\n return {\n service,\n healthy: true,\n elapsed,\n };\n }\n }\n } catch {\n // Docker API error — continue polling\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Endpoint Generation\n// ---------------------------------------------------------------------------\n\n/**\n * Mapping of service IDs to their subdomains and display names.\n */\nconst SUBDOMAIN_MAP: Record<string, { subdomain: string; name: string }> = {\n traefik: { subdomain: 'traefik', name: 'Traefik Dashboard' },\n gitea: { subdomain: 'git', name: 'Gitea' },\n nextcloud: { subdomain: 'cloud', name: 'Nextcloud' },\n minio: { subdomain: 'minio', name: 'MinIO Console' },\n jellyfin: { subdomain: 'jellyfin', name: 'Jellyfin' },\n pgadmin: { subdomain: 'pgadmin', name: 'pgAdmin' },\n filebrowser: { subdomain: 'files', name: 'File Browser' },\n};\n\n/**\n * Generate endpoint URLs for all services based on the wizard state.\n */\nexport function generateEndpoints(\n state: WizardState,\n services: string[],\n): EndpointInfo[] {\n const endpoints: EndpointInfo[] = [];\n const domain = state.domain.name;\n const isLocal = state.domain.provider === 'local';\n const scheme = isLocal ? 'http' : 'https';\n\n const credTargets = getCredentialTargets(state as any);\n\n for (const svcId of services) {\n const mapping = SUBDOMAIN_MAP[svcId];\n if (!mapping) continue;\n\n const url = `${scheme}://${mapping.subdomain}.${domain}`;\n const endpoint: EndpointInfo = {\n service: svcId,\n url,\n };\n\n // Check if this service receives admin credentials\n const prettyName = mapping.name;\n if (credTargets.includes(prettyName) || credTargets.includes(svcId)) {\n endpoint.credentials = {\n username: state.admin.username,\n password: '(see .env file)',\n };\n }\n\n endpoints.push(endpoint);\n }\n\n return endpoints;\n}\n\n// ---------------------------------------------------------------------------\n// DNS / Endpoint Verification\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether DNS resolves for a given domain.\n * Uses Node.js dns module for actual resolution.\n */\nexport async function checkDnsResolution(domain: string): Promise<boolean> {\n try {\n const { promises: dns } = await import('node:dns');\n await dns.resolve4(domain);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check whether an HTTP(S) endpoint is reachable.\n * Returns true if the response status is 200-499 (server is responding).\n */\nexport async function checkEndpointReachable(url: string): Promise<boolean> {\n try {\n const response = await fetch(url, {\n method: 'HEAD',\n signal: AbortSignal.timeout(10000),\n });\n return response.status >= 200 && response.status < 500;\n } catch {\n return false;\n }\n}\n","/**\n * Brewnet CLI — Uninstall Manager (T113)\n *\n * Handles complete removal of a Brewnet project:\n * 1. docker compose down (optionally --volumes)\n * 2. docker network rm brewnet brewnet-internal\n * 3. rm -rf {projectPath} (unless --keep-config)\n * 4. rm -rf ~/.brewnet/status ~/.brewnet/state\n *\n * Supports --dry-run (no changes), --keep-data (preserve volumes),\n * --keep-config (preserve projectPath files), and --force (skip confirm).\n *\n * @module services/uninstall-manager\n */\n\nimport { existsSync, rmSync, readdirSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport { DOCKER_COMPOSE_FILENAME } from '@brewnet/shared';\nimport type { InstallManifest, InstallManifestStack } from '@brewnet/shared';\n\n/**\n * Augment PATH with common Docker / Homebrew install locations.\n * Mirrors docker-installer.ts augmentedEnv() to fix PATH issues in uninstall.\n */\nfunction augmentedEnv(): NodeJS.ProcessEnv {\n const base = process.env['PATH'] ?? '/usr/bin:/bin:/usr/sbin:/sbin';\n const extra = '/usr/local/bin:/opt/homebrew/bin';\n const combined = extra\n .split(':')\n .filter((p) => !base.split(':').includes(p))\n .concat(base.split(':'))\n .join(':');\n return { ...process.env, PATH: combined };\n}\n\n/**\n * Expand a leading `~` to the user's home directory.\n * All paths stored in WizardState use `~/...` for portability.\n * Node.js fs functions and execa do NOT expand `~` automatically.\n */\nfunction expandPath(p: string): string {\n if (p.startsWith('~/') || p === '~') {\n return join(homedir(), p.slice(1));\n }\n return p;\n}\nimport { getLastProject, loadState, getProjectDir } from '../wizard/state.js';\nimport { logger } from '../utils/logger.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UninstallOptions {\n /** List targets but make no changes. */\n dryRun?: boolean;\n /** Preserve Docker named volumes (databases, file data). */\n keepData?: boolean;\n /** Preserve project directory (only stop containers). */\n keepConfig?: boolean;\n /** Skip confirmation prompt (for CLI --force flag). */\n force?: boolean;\n /** Override project path (defaults to last saved project). */\n projectPath?: string;\n /** Override project name (used to find wizard state). */\n projectName?: string;\n}\n\nexport interface UninstallTarget {\n label: string;\n path: string;\n type: 'compose' | 'network' | 'directory' | 'brewnet-meta';\n skipped?: boolean;\n skipReason?: string;\n}\n\nexport interface UninstallResult {\n success: boolean;\n removed: string[];\n skipped: string[];\n errors: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst BREWNET_DIR = join(homedir(), '.brewnet');\n\n// ---------------------------------------------------------------------------\n// Manifest helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read .brewnet-manifest.json from projectPath.\n * Returns null if the file is missing or unparseable (legacy install).\n */\nfunction readManifest(projectPath: string): InstallManifest | null {\n const manifestPath = join(projectPath, '.brewnet-manifest.json');\n if (!existsSync(manifestPath)) return null;\n try {\n const raw = readFileSync(manifestPath, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<InstallManifest>;\n // Validate minimal required fields\n if (!parsed.generatedFiles || !parsed.generatedDirs || !parsed.boilerplateStacks) {\n return null;\n }\n return parsed as InstallManifest;\n } catch {\n return null;\n }\n}\n\n/**\n * Stop Docker Compose services for each boilerplate stack listed in the manifest.\n * Non-fatal — stacks may already be stopped or their compose file may be missing.\n */\nasync function stopBoilerplateContainers(\n projectPath: string,\n stacks: InstallManifestStack[],\n keepData: boolean,\n result: UninstallResult,\n): Promise<void> {\n for (const stack of stacks) {\n const stackDir = join(projectPath, stack.directory);\n const stackCompose = join(stackDir, DOCKER_COMPOSE_FILENAME);\n if (!existsSync(stackCompose)) {\n result.skipped.push(`Boilerplate containers [${stack.stackId}]: compose not found`);\n continue;\n }\n try {\n const downArgs = ['compose', '-f', DOCKER_COMPOSE_FILENAME, 'down'];\n if (!keepData) downArgs.push('--volumes', '--rmi', 'all');\n await execa('docker', downArgs, { cwd: stackDir, env: augmentedEnv(), reject: false });\n result.removed.push(\n `Boilerplate containers [${stack.stackId}]${keepData ? '' : ' + volumes + images'}`,\n );\n logger.info('uninstall', `Boilerplate containers stopped: ${stack.stackId}`, { stackDir });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n result.errors.push(`docker compose down [${stack.stackId}]: ${msg}`);\n logger.warn('uninstall', `Boilerplate compose down failed (continuing)`, { stackId: stack.stackId, error: msg });\n }\n }\n}\n\n/**\n * Remove only the files and directories listed in the install manifest.\n * Preserves any files the user added to projectPath that are not in the manifest.\n * If the project directory becomes empty after cleanup, removes it too.\n */\nfunction removeByManifest(\n projectPath: string,\n manifest: InstallManifest,\n result: UninstallResult,\n): void {\n // 1. Remove boilerplate stack directories (each is fully brewnet-owned)\n for (const stack of manifest.boilerplateStacks) {\n const stackDir = join(projectPath, stack.directory);\n if (existsSync(stackDir)) {\n try {\n rmSync(stackDir, { recursive: true, force: true });\n result.removed.push(`Boilerplate source [${stack.stackId}]: ${stackDir}`);\n logger.info('uninstall', `Boilerplate directory removed: ${stack.stackId}`, { stackDir });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n result.errors.push(`rm ${stackDir}: ${msg}`);\n }\n }\n }\n\n // 2. Remove generated directories (secrets/, logs/, configs/, etc.)\n for (const dir of manifest.generatedDirs) {\n const dirPath = join(projectPath, dir);\n if (existsSync(dirPath)) {\n try {\n rmSync(dirPath, { recursive: true, force: true });\n result.removed.push(`Generated dir: ${dir}/`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n result.errors.push(`rm ${dirPath}: ${msg}`);\n }\n }\n }\n\n // 3. Remove individual generated files (docker-compose.yml, .env, .gitignore, etc.)\n let removedFileCount = 0;\n for (const file of manifest.generatedFiles) {\n const filePath = join(projectPath, file);\n if (existsSync(filePath)) {\n try {\n rmSync(filePath, { force: true });\n removedFileCount++;\n } catch { /* best-effort */ }\n }\n }\n if (removedFileCount > 0) {\n result.removed.push(`Generated config files: ${removedFileCount} file(s)`);\n }\n\n // 4. If projectPath is now empty, remove the directory itself\n try {\n const remaining = readdirSync(projectPath);\n if (remaining.length === 0) {\n rmSync(projectPath, { recursive: true, force: true });\n result.removed.push(`Project directory (empty after cleanup): ${projectPath}`);\n } else {\n result.skipped.push(\n `Project directory preserved — ${remaining.length} user file(s) remain: ${projectPath}`,\n );\n }\n } catch { /* non-critical */ }\n}\n\n/**\n * Resolve project path + name from options or the last saved wizard state.\n * Always expands `~` so downstream fs / execa calls get an absolute path.\n */\nfunction resolveProject(options: UninstallOptions): {\n projectPath: string | null;\n projectName: string | null;\n} {\n if (options.projectPath) {\n return {\n projectPath: expandPath(options.projectPath),\n projectName: options.projectName ?? null,\n };\n }\n\n const lastProject = getLastProject();\n if (!lastProject) return { projectPath: null, projectName: null };\n\n const state = loadState(lastProject);\n if (!state) return { projectPath: null, projectName: lastProject };\n\n return {\n projectPath: expandPath(state.projectPath),\n projectName: state.projectName,\n };\n}\n\n/**\n * Build the list of targets that would be removed.\n */\nexport function buildUninstallTargets(\n projectPath: string | null,\n options: UninstallOptions,\n): UninstallTarget[] {\n const targets: UninstallTarget[] = [];\n // Expand tilde so existsSync works on the real path\n if (projectPath) projectPath = expandPath(projectPath);\n\n // 1. Docker containers + optionally volumes (main Brewnet services)\n if (projectPath && existsSync(join(projectPath, DOCKER_COMPOSE_FILENAME))) {\n targets.push({\n label: `Docker containers${options.keepData ? '' : ' + volumes + images'}`,\n path: join(projectPath, DOCKER_COMPOSE_FILENAME),\n type: 'compose',\n });\n }\n\n // 1b. Boilerplate stack containers (from install manifest)\n if (projectPath) {\n const manifest = readManifest(projectPath);\n if (manifest) {\n for (const stack of manifest.boilerplateStacks) {\n const stackCompose = join(projectPath, stack.directory, DOCKER_COMPOSE_FILENAME);\n if (existsSync(stackCompose)) {\n targets.push({\n label: `Boilerplate containers [${stack.stackId}]${options.keepData ? '' : ' + volumes + images'}`,\n path: stackCompose,\n type: 'compose',\n skipped: options.keepConfig,\n skipReason: options.keepConfig ? '--keep-config' : undefined,\n });\n }\n }\n }\n }\n\n // 2. Docker networks\n targets.push({\n label: 'Docker networks: brewnet, brewnet-internal',\n path: 'docker network rm',\n type: 'network',\n });\n\n // 3. Project directory\n if (projectPath) {\n targets.push({\n label: `Project directory: ${projectPath}`,\n path: projectPath,\n type: 'directory',\n skipped: options.keepConfig,\n skipReason: options.keepConfig ? '--keep-config' : undefined,\n });\n }\n\n // 4. Entire ~/.brewnet/ directory\n if (existsSync(BREWNET_DIR)) {\n targets.push({\n label: '~/.brewnet/ (all data, source, config)',\n path: BREWNET_DIR,\n type: 'brewnet-meta',\n });\n }\n\n // 5. CLI binary (check all known install locations)\n const possibleBins = [\n join(homedir(), '.local', 'bin', 'brewnet'),\n '/usr/local/bin/brewnet',\n '/usr/bin/brewnet',\n ];\n for (const bin of possibleBins) {\n if (existsSync(bin)) {\n targets.push({ label: `CLI binary: ${bin}`, path: bin, type: 'brewnet-meta' });\n }\n }\n\n return targets;\n}\n\n// ---------------------------------------------------------------------------\n// Main uninstall function\n// ---------------------------------------------------------------------------\n\n/**\n * Perform (or simulate) the full uninstall sequence.\n *\n * @param options - Control flags for dry-run, data/config preservation.\n * @returns UninstallResult with lists of removed, skipped, and errored items.\n */\nexport async function runUninstall(options: UninstallOptions = {}): Promise<UninstallResult> {\n const result: UninstallResult = { success: true, removed: [], skipped: [], errors: [] };\n\n const { projectPath, projectName } = resolveProject(options);\n const targets = buildUninstallTargets(projectPath, options);\n\n // Dry-run: just return the target list without making changes\n if (options.dryRun) {\n for (const t of targets) {\n if (t.skipped) {\n result.skipped.push(`${t.label} (${t.skipReason ?? 'skipped'})`);\n } else {\n result.removed.push(t.label);\n }\n }\n return result;\n }\n\n // --- 0. Stop boilerplate stack containers (before main compose down) ---\n if (projectPath && !options.keepConfig) {\n const manifest = readManifest(projectPath);\n if (manifest && manifest.boilerplateStacks.length > 0) {\n await stopBoilerplateContainers(projectPath, manifest.boilerplateStacks, options.keepData ?? false, result);\n }\n }\n\n // --- 1. docker compose down ---\n if (projectPath && existsSync(join(projectPath, DOCKER_COMPOSE_FILENAME))) {\n try {\n const downArgs = ['compose', '-f', DOCKER_COMPOSE_FILENAME, 'down'];\n if (!options.keepData) {\n downArgs.push('--volumes', '--rmi', 'all');\n }\n\n await execa('docker', downArgs, { cwd: projectPath, env: augmentedEnv() });\n result.removed.push(`Docker containers${options.keepData ? '' : ' + volumes + images'}`);\n logger.info('uninstall', 'docker compose down succeeded', { projectPath });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // Non-fatal: containers may already be stopped\n result.errors.push(`docker compose down: ${msg}`);\n logger.warn('uninstall', 'docker compose down failed (continuing)', { error: msg });\n }\n }\n\n // --- 1c. Remove orphan containers whose working_dir is under projectPath\n // but were not covered by the manifest (e.g. test-cycle leftovers).\n if (projectPath && !options.keepConfig) {\n try {\n const { default: Dockerode } = await import('dockerode');\n const docker = new Dockerode();\n const allContainers = await docker.listContainers({ all: true });\n const orphanProjects = new Map<string, string>(); // workingDir → composeFile\n for (const c of allContainers) {\n const wdir = c.Labels?.['com.docker.compose.project.working_dir'] ?? '';\n if (wdir && wdir.startsWith(projectPath) && wdir !== projectPath) {\n const composeFile = c.Labels?.['com.docker.compose.config.files'] ?? DOCKER_COMPOSE_FILENAME;\n orphanProjects.set(wdir, composeFile.split(',')[0] ?? DOCKER_COMPOSE_FILENAME);\n }\n }\n for (const [wdir, composeFile] of orphanProjects) {\n try {\n const downArgs = ['compose', '-f', composeFile, 'down'];\n if (!options.keepData) downArgs.push('--volumes', '--rmi', 'all');\n await execa('docker', downArgs, { cwd: wdir, env: augmentedEnv(), reject: false });\n result.removed.push(`Orphan containers in ${wdir}`);\n logger.info('uninstall', 'Orphan compose project stopped', { wdir });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n result.errors.push(`docker compose down (orphan ${wdir}): ${msg}`);\n }\n }\n } catch {\n // non-fatal — docker may not be accessible\n }\n }\n\n // --- 2. docker network rm (all brewnet-* networks, including project-prefixed ones) ---\n try {\n const netList = await execa('docker', ['network', 'ls', '--filter', 'name=brewnet', '-q'], {\n reject: false,\n env: augmentedEnv(),\n });\n const netIds = netList.stdout.trim().split('\\n').filter(Boolean);\n if (netIds.length > 0) {\n await execa('docker', ['network', 'rm', ...netIds], { reject: false, env: augmentedEnv() });\n result.removed.push(`Docker networks: ${netIds.length} removed`);\n } else {\n result.skipped.push('Docker networks (not found or already removed)');\n }\n } catch {\n result.skipped.push('Docker networks (not found or already removed)');\n }\n\n // --- 3. project directory ---\n if (projectPath && !options.keepConfig) {\n if (existsSync(projectPath)) {\n const manifest = readManifest(projectPath);\n if (manifest) {\n // Manifest exists: selective removal — only remove brewnet-generated files.\n // Files added by the user are preserved. Empty directory is cleaned up.\n removeByManifest(projectPath, manifest, result);\n logger.info('uninstall', 'Manifest-based removal complete', { projectPath });\n } else {\n // No manifest (legacy install): fall back to full directory removal.\n logger.warn('uninstall', 'No .brewnet-manifest.json — falling back to full rm -rf', { projectPath });\n try {\n rmSync(projectPath, { recursive: true, force: true });\n result.removed.push(`Project directory: ${projectPath}`);\n logger.info('uninstall', 'Project directory removed (legacy)', { projectPath });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n result.errors.push(`rm ${projectPath}: ${msg}`);\n result.success = false;\n }\n }\n } else {\n result.skipped.push(`Project directory not found: ${projectPath}`);\n }\n } else if (options.keepConfig) {\n result.skipped.push(`Project directory preserved (--keep-config): ${projectPath ?? 'n/a'}`);\n }\n\n // --- 4. Remove entire ~/.brewnet/ directory ---\n if (existsSync(BREWNET_DIR)) {\n try {\n rmSync(BREWNET_DIR, { recursive: true, force: true });\n result.removed.push('~/.brewnet/ (all data, source, config)');\n logger.info('uninstall', 'Brewnet data directory removed', { dir: BREWNET_DIR });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n result.errors.push(`rm ${BREWNET_DIR}: ${msg}`);\n }\n }\n\n // --- 5. Remove wizard state entry (fallback if ~/.brewnet/ was partially deleted) ---\n if (projectName) {\n const projectStateDir = getProjectDir(projectName);\n if (existsSync(projectStateDir)) {\n try {\n rmSync(projectStateDir, { recursive: true, force: true });\n result.removed.push(`Wizard state: ~/.brewnet/projects/${projectName}`);\n } catch {\n // non-critical\n }\n }\n }\n\n // --- 5.5. Remove CLI binary from all known install locations ---\n const possibleBins = [\n join(homedir(), '.local', 'bin', 'brewnet'),\n '/usr/local/bin/brewnet',\n '/usr/bin/brewnet',\n ];\n for (const bin of possibleBins) {\n if (existsSync(bin)) {\n try {\n rmSync(bin, { force: true });\n result.removed.push(`CLI binary: ${bin}`);\n logger.info('uninstall', 'CLI binary removed', { bin });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n result.errors.push(`rm ${bin}: ${msg} (may need sudo)`);\n }\n }\n }\n\n if (result.errors.length > 0) result.success = false;\n return result;\n}\n\n/**\n * Lightweight cleanup for wizard restart: stop containers, remove networks\n * and project directory. Does NOT remove ~/.brewnet/ metadata or CLI binary.\n */\nexport async function cleanupForRestart(projectPath: string): Promise<void> {\n const expanded = expandPath(projectPath);\n const composePath = join(expanded, DOCKER_COMPOSE_FILENAME);\n\n // 1. Docker compose down --volumes --remove-orphans\n if (existsSync(composePath)) {\n try {\n await execa('docker', [\n 'compose', '-f', composePath, 'down', '--volumes', '--remove-orphans',\n ], { env: augmentedEnv() });\n } catch {\n // best effort — containers may already be stopped\n }\n }\n\n // 2. Remove docker networks\n for (const netName of ['brewnet', 'brewnet-internal']) {\n try {\n await execa('docker', ['network', 'rm', netName], { env: augmentedEnv() });\n } catch {\n // best effort — network may not exist\n }\n }\n\n // 3. Remove project directory\n if (existsSync(expanded)) {\n try {\n rmSync(expanded, { recursive: true, force: true });\n } catch {\n // best effort\n }\n }\n}\n\n/**\n * List all project names + paths that can be uninstalled.\n *\n * Discovery order (deduplicates by absolute path):\n * 1. Wizard state: ~/.brewnet/projects/\n * 2. Filesystem scan: ~/brewnet/* /docker-compose.yml\n * 3. Docker containers: running brewnet-* containers → label/compose project\n */\nexport function listInstallations(): { name: string; path: string | null }[] {\n const seen = new Set<string>(); // absolute paths already added\n const results: { name: string; path: string | null }[] = [];\n\n const addIfNew = (name: string, rawPath: string | null) => {\n const absPath = rawPath ? expandPath(rawPath) : null;\n const key = absPath ?? `__name__${name}`;\n if (seen.has(key)) return;\n seen.add(key);\n results.push({ name, path: rawPath });\n };\n\n // --- 1. Wizard state: ~/.brewnet/projects/ ---\n const projectsDir = join(BREWNET_DIR, 'projects');\n if (existsSync(projectsDir)) {\n try {\n for (const e of readdirSync(projectsDir, { withFileTypes: true })) {\n if (!e.isDirectory()) continue;\n const state = loadState(e.name);\n addIfNew(e.name, state?.projectPath ?? null);\n }\n } catch { /* best-effort */ }\n }\n\n // --- 2. Filesystem scan: ~/brewnet/*/docker-compose.yml ---\n const brewnetRoot = join(homedir(), 'brewnet');\n if (existsSync(brewnetRoot)) {\n try {\n for (const e of readdirSync(brewnetRoot, { withFileTypes: true })) {\n if (!e.isDirectory()) continue;\n const composePath = join(brewnetRoot, e.name, DOCKER_COMPOSE_FILENAME);\n if (existsSync(composePath)) {\n addIfNew(e.name, `~/brewnet/${e.name}`);\n }\n }\n } catch { /* best-effort */ }\n }\n\n return results;\n}\n","/**\n * T072 — Step 7: Complete\n *\n * Displays the final summary after successful setup:\n * 1. Endpoint URLs table\n * 2. Credentials summary\n * 3. External access verification commands (for non-local domains)\n * 4. Next steps / troubleshooting tips\n *\n * @module wizard/steps/complete\n */\n\nimport chalk from 'chalk';\nimport Table from 'cli-table3';\nimport { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { WizardState } from '@brewnet/shared';\nimport { buildServiceUrlMap } from '../../utils/service-verifier.js';\nimport {\n collectAllServices,\n getCredentialTargets,\n} from '../../utils/resources.js';\nimport { sortByDependency } from '../../services/health-checker.js';\nimport { launchAdminDaemon } from '../../services/admin-launcher.js';\n\n/**\n * Kill any process listening on the given port (best-effort).\n * Prevents EADDRINUSE when restarting the admin server.\n */\nasync function killPortProcess(port: number): Promise<void> {\n try {\n const cmd = process.platform === 'darwin'\n ? `lsof -ti :${port} | xargs kill -9 2>/dev/null || true`\n : `fuser -k ${port}/tcp 2>/dev/null || true`;\n execSync(cmd, { stdio: 'ignore', timeout: 3000 });\n // Brief pause for port release\n await new Promise((r) => setTimeout(r, 300));\n } catch {\n // Non-fatal — port may not be in use\n }\n}\n\n/**\n * Wrap a URL in OSC 8 terminal hyperlink escape sequences.\n * Supported by: iTerm2, VS Code terminal, Windows Terminal, and many others.\n * Falls back to plain text in unsupported terminals.\n */\nfunction hyperlink(url: string): string {\n return `\\x1b]8;;${url}\\x07${url}\\x1b]8;;\\x07`;\n}\n\n// ---------------------------------------------------------------------------\n// runCompleteStep\n// ---------------------------------------------------------------------------\n\n/**\n * Run Step 7: Complete.\n *\n * Displays the final setup summary including endpoint URLs, credentials,\n * and next steps. This step has no interactive prompts — it is purely\n * informational.\n *\n * @param state - Completed wizard state\n */\nexport async function runCompleteStep(\n state: WizardState,\n options?: { noOpen?: boolean },\n): Promise<void> {\n // -------------------------------------------------------------------------\n // 1. Display header\n // -------------------------------------------------------------------------\n console.log();\n console.log(\n chalk.bold.green(' Step 8/8') + chalk.bold(' — Complete!'),\n );\n console.log(\n chalk.dim(' Your home server has been brewed fresh.'),\n );\n console.log();\n\n // -------------------------------------------------------------------------\n // 2. Access URLs (local + external two-column table)\n // -------------------------------------------------------------------------\n const ADMIN_PORT = 8088;\n const services = collectAllServices(state);\n const sorted = sortByDependency(services);\n const serviceUrls = buildServiceUrlMap(state);\n const hasTunnel =\n (state.domain.cloudflare.enabled || state.domain.provider === 'quick-tunnel') &&\n (state.domain.cloudflare.tunnelMode === 'quick' ||\n state.domain.cloudflare.tunnelMode === 'named');\n\n const urlMap = [...serviceUrls];\n\n if (urlMap.length > 0) {\n console.log(chalk.bold(' Access URLs'));\n console.log();\n\n if (hasTunnel) {\n const maxExtLen = Math.max(\n 10,\n ...urlMap.map((e) => (e.externalUrl ?? '—').length),\n );\n const urlTable = new Table({\n head: [chalk.bold('Service'), chalk.bold('Local'), chalk.bold('External')],\n colWidths: [20, 28, maxExtLen + 4],\n style: { head: [], border: ['dim'] },\n });\n\n for (const entry of urlMap) {\n urlTable.push([\n entry.label,\n chalk.cyan(entry.localUrl),\n entry.externalUrl ? chalk.green(entry.externalUrl) : chalk.dim('—'),\n ]);\n }\n\n console.log(urlTable.toString());\n } else {\n // No tunnel: plain list with OSC 8 clickable hyperlinks\n const maxNameLen = Math.max(...urlMap.map((e) => e.label.length));\n for (const entry of urlMap) {\n const labelPadded = entry.label.padEnd(maxNameLen);\n console.log(` ${chalk.cyan(labelPadded)} ${hyperlink(entry.localUrl)}`);\n }\n }\n\n console.log();\n }\n\n // -------------------------------------------------------------------------\n // 3. Credentials summary\n // -------------------------------------------------------------------------\n const credTargets = getCredentialTargets(state);\n if (credTargets.length > 0) {\n console.log(chalk.bold(' Credentials'));\n console.log(\n chalk.dim(` Admin username: `) + chalk.yellow(state.admin.username),\n );\n console.log(\n chalk.dim(` Admin password: `) + chalk.yellow('(see secrets/admin_password)'),\n );\n console.log(\n chalk.dim(` Propagated to: `) + credTargets.join(', '),\n );\n console.log();\n }\n\n // -------------------------------------------------------------------------\n // 4. External access info by tunnel mode\n // -------------------------------------------------------------------------\n if (state.domain.provider === 'quick-tunnel') {\n // Quick Tunnel — show URL + per-service paths + restart warning\n const quickUrl = state.domain.cloudflare.quickTunnelUrl;\n console.log(chalk.bold(' Quick Tunnel'));\n console.log(chalk.yellow(' ⚠️ 임시 URL — 서버 재시작 시 변경됩니다'));\n console.log(chalk.dim(` URL: ${quickUrl}`));\n console.log();\n // Show per-service paths for installed services only\n const quickPaths: Record<string, string> = {\n gitea: '/git',\n filebrowser: '/files',\n 'uptime-kuma': '/status',\n grafana: '/grafana',\n pgadmin: '/pgadmin',\n };\n const installedServices = new Set(sorted);\n const serviceLabels: Record<string, string> = {\n gitea: 'Gitea',\n filebrowser: 'FileBrowser',\n 'uptime-kuma': 'Uptime Kuma',\n grafana: 'Grafana',\n pgadmin: 'pgAdmin',\n };\n const activePaths = Object.entries(quickPaths)\n .filter(([id]) => installedServices.has(id));\n\n if (activePaths.length > 0) {\n console.log(chalk.dim(' 서비스 경로:'));\n const maxLen = Math.max(...activePaths.map(([id]) => (serviceLabels[id] ?? id).length));\n for (const [id, path] of activePaths) {\n const label = (serviceLabels[id] ?? id).padEnd(maxLen);\n console.log(chalk.dim(` ${label} ${quickUrl}${path}`));\n }\n console.log();\n }\n console.log(chalk.dim(' 영구 URL로 업그레이드:'));\n console.log(` ${chalk.cyan('brewnet domain connect')}`);\n console.log();\n } else if (state.domain.provider === 'tunnel') {\n console.log(chalk.bold(' Cloudflare Tunnel'));\n console.log(chalk.dim(` 터널: ${state.domain.cloudflare.tunnelName}`));\n if (state.domain.cloudflare.tunnelId) {\n console.log(chalk.dim(` ID: ${state.domain.cloudflare.tunnelId}`));\n }\n if (state.domain.cloudflare.zoneName) {\n console.log(chalk.dim(` 도메인: ${state.domain.cloudflare.zoneName}`));\n } else {\n console.log(chalk.dim(' 도메인: 미설정'));\n console.log(chalk.dim(' 도메인 연결:'));\n console.log(` ${chalk.cyan('brewnet domain connect')}`);\n }\n console.log();\n if (!state.domain.cloudflare.tunnelId) {\n console.log(chalk.dim(' 외부 접근 호스트 추가:'));\n console.log(chalk.dim(' → one.dash.cloudflare.com → Networks → Connectors → Cloudflare Tunnels'));\n console.log(chalk.dim(' → 터널 선택 → Published applications → Add'));\n console.log();\n console.log(chalk.dim(' Brewnet 명령:'));\n console.log(` ${chalk.cyan('brewnet domain tunnel status')}`);\n console.log();\n }\n }\n\n // -------------------------------------------------------------------------\n // 4b. Boilerplate project access info (supports both array and legacy object)\n // -------------------------------------------------------------------------\n const resolvedProjectPath = state.projectPath.startsWith('~')\n ? join(homedir(), state.projectPath.slice(1))\n : state.projectPath;\n const boilerplateMetaPath = join(resolvedProjectPath, '.brewnet-boilerplate.json');\n if (existsSync(boilerplateMetaPath)) {\n try {\n const raw = JSON.parse(readFileSync(boilerplateMetaPath, 'utf-8')) as\n | { stackId?: string; appDir?: string; backendUrl?: string; frontendUrl?: string; isUnified?: boolean }\n | Array<{ stackId?: string; appDir?: string; backendUrl?: string; frontendUrl?: string; isUnified?: boolean; status?: string }>;\n\n const stacks = Array.isArray(raw) ? raw : (raw.stackId ? [raw] : []);\n\n if (stacks.length > 0) {\n console.log(chalk.bold(' Dev Stack Apps'));\n console.log();\n for (const meta of stacks) {\n if (!meta.stackId) continue;\n console.log(` ${chalk.dim('Stack:')} ${chalk.yellow(meta.stackId)}`);\n if (meta.isUnified) {\n console.log(` ${chalk.dim('URL:')} ${chalk.cyan(meta.backendUrl ?? '')}`);\n } else {\n console.log(` ${chalk.dim('Backend:')} ${chalk.cyan(meta.backendUrl ?? '')}`);\n console.log(` ${chalk.dim('Frontend:')} ${chalk.cyan(meta.frontendUrl ?? '')}`);\n }\n console.log(` ${chalk.dim('Source:')} ${chalk.dim(meta.appDir ?? '')}`);\n console.log(` ${chalk.dim('Docs:')} ${chalk.cyan(`${meta.backendUrl ?? ''}/docs`)}`);\n console.log();\n console.log(chalk.dim(` Commands (run in ${meta.appDir}/):`));\n console.log(chalk.dim(` make logs # view container logs`));\n console.log(chalk.dim(` make down # stop containers`));\n console.log(chalk.dim(` make validate # verify API endpoints`));\n console.log();\n }\n }\n } catch { /* non-fatal */ }\n }\n\n // -------------------------------------------------------------------------\n // 5. Next steps\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Next Steps'));\n console.log();\n console.log(` ${chalk.dim('•')} View service status: ${chalk.cyan('brewnet status')}`);\n console.log(` ${chalk.dim('•')} View logs: ${chalk.cyan('brewnet logs [service]')}`);\n console.log(` ${chalk.dim('•')} Stop services: ${chalk.cyan('brewnet down')}`);\n console.log(` ${chalk.dim('•')} Restart services: ${chalk.cyan('brewnet up')}`);\n console.log(` ${chalk.dim('•')} Create backup: ${chalk.cyan('brewnet backup')}`);\n console.log();\n\n // -------------------------------------------------------------------------\n // 6. Troubleshooting\n // -------------------------------------------------------------------------\n console.log(chalk.bold(' Troubleshooting'));\n console.log();\n console.log(` ${chalk.dim('•')} Check Docker status: ${chalk.cyan('docker ps')}`);\n console.log(` ${chalk.dim('•')} View compose logs: ${chalk.cyan('docker compose logs -f')}`);\n console.log(` ${chalk.dim('•')} Project directory: ${chalk.dim(state.projectPath)}`);\n console.log(` ${chalk.dim('•')} Configuration: ${chalk.dim(state.projectPath + '/.env')}`);\n console.log(` ${chalk.dim('•')} Secrets: ${chalk.dim(state.projectPath + '/secrets/')}`);\n console.log();\n\n console.log(chalk.green.bold(' Happy brewing! ☕'));\n console.log();\n\n // -------------------------------------------------------------------------\n // 7. Start Admin Panel and open in browser\n // -------------------------------------------------------------------------\n const adminUrl = `http://localhost:${ADMIN_PORT}`;\n\n try {\n // Kill any existing admin server on the same port (from a previous session)\n await killPortProcess(ADMIN_PORT);\n\n // Launch admin as a detached daemon — survives terminal close, CLI exit\n const result = await launchAdminDaemon({\n port: ADMIN_PORT,\n projectPath: state.projectPath,\n });\n\n console.log(chalk.bold(' Admin Panel'));\n console.log(chalk.dim(' 홈서버 관리 대시보드가 시작되었습니다.'));\n console.log(` ${chalk.cyan.bold(adminUrl)}` + chalk.dim(` (PID ${result.pid})`));\n console.log(chalk.dim(` Log: ${result.logFile}`));\n console.log(chalk.dim(` Stop: kill ${result.pid}`));\n console.log();\n\n if (!options?.noOpen) {\n console.log(chalk.dim(' 브라우저에서 Admin Panel을 열고 있습니다...'));\n try {\n const { execa } = await import('execa');\n const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';\n await execa(cmd, [adminUrl]);\n } catch { /* best-effort */ }\n }\n\n // CLI can now exit — daemon continues independently\n } catch {\n // Non-fatal — admin panel failure should not block completion\n console.log(chalk.dim(` Admin Panel 시작 실패. 수동 실행: ${chalk.cyan('brewnet admin')}`));\n console.log();\n }\n}\n","/**\n * Launch admin server as a detached background daemon.\n *\n * The daemon process is fully independent of the parent terminal:\n * - detached: true → new process group (no SIGHUP from parent)\n * - stdio redirected to log file → no broken pipe on terminal close\n * - unref() → parent can exit without waiting for child\n *\n * @module services/admin-launcher\n */\n\nimport { spawn } from 'node:child_process';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { openSync, mkdirSync, existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { createConnection } from 'node:net';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/** Resolve path to the compiled admin-daemon.js */\nfunction getDaemonPath(): string {\n // tsup bundles admin-launcher into index.js (dist/), but admin-daemon.js\n // is a separate entry output at dist/services/admin-daemon.js\n const candidates = [\n join(__dirname, 'services', 'admin-daemon.js'), // dist/services/admin-daemon.js\n join(__dirname, 'admin-daemon.js'), // dist/admin-daemon.js (flat)\n join(__dirname, '..', 'services', 'admin-daemon.js'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n return candidates[0]!; // best guess\n}\n\nexport interface LaunchOptions {\n port: number;\n projectPath?: string;\n}\n\nexport interface LaunchResult {\n pid: number;\n port: number;\n logFile: string;\n}\n\n/**\n * Spawn admin-daemon.js as a fully detached background process.\n *\n * Returns once the daemon reports ready (HTTP server listening)\n * or after a timeout (5s).\n */\nexport async function launchAdminDaemon(opts: LaunchOptions): Promise<LaunchResult> {\n const logDir = join(homedir(), '.brewnet', 'logs');\n mkdirSync(logDir, { recursive: true });\n const logFile = join(logDir, 'admin-server.log');\n\n // Open log file for append — daemon stdout/stderr go here\n const logFd = openSync(logFile, 'a');\n\n const daemonPath = getDaemonPath();\n const nodeArgs = [daemonPath, '--port', String(opts.port)];\n if (opts.projectPath) nodeArgs.push('--path', opts.projectPath);\n\n const child = spawn(process.execPath, nodeArgs, {\n detached: true,\n stdio: ['ignore', logFd, logFd],\n env: { ...process.env },\n });\n\n child.unref();\n\n // Wait for daemon to start listening by polling the port\n const ready = await new Promise<boolean>((resolve) => {\n let attempts = 0;\n const maxAttempts = 25; // 25 × 200ms = 5s\n const timer = setInterval(() => {\n attempts++;\n const sock = createConnection({ port: opts.port, host: '127.0.0.1' });\n sock.once('connect', () => { sock.destroy(); clearInterval(timer); resolve(true); });\n sock.once('error', () => { sock.destroy(); if (attempts >= maxAttempts) { clearInterval(timer); resolve(false); } });\n sock.setTimeout(150, () => { sock.destroy(); });\n }, 200);\n });\n\n if (!ready) {\n throw new Error(`Admin daemon failed to start on port ${opts.port}. Check ${logFile}`);\n }\n\n return { pid: child.pid!, port: opts.port, logFile };\n}\n","/**\n * Minimal Install runner.\n *\n * Fast-path wizard that only prompts for project name, admin username,\n * and admin password, then spins up Traefik + Gitea + Quick Tunnel.\n *\n * All other services (DB, file server, media, SSH, boilerplate) are\n * disabled. The existing Full Install wizard is unchanged.\n *\n * @module wizard/run-minimal-install\n */\n\nimport chalk from 'chalk';\nimport { input, password } from '@inquirer/prompts';\nimport { runGenerateStep } from './steps/generate.js';\nimport { runCompleteStep } from './steps/complete.js';\nimport {\n createState,\n saveState,\n} from './state.js';\nimport { applyMinimalInstallDefaults } from '../config/defaults.js';\n\nexport async function runMinimalInstall(options: { noOpen?: boolean } = {}): Promise<void> {\n console.log(chalk.cyan(' ─────────────────────────────────────────────'));\n console.log(chalk.bold(' Minimal Install') + chalk.dim(' — Traefik + Gitea + Quick Tunnel'));\n console.log(chalk.cyan(' ─────────────────────────────────────────────'));\n console.log();\n\n // 1. Project name\n const projectName = await input({\n message: 'Project name',\n default: 'brewnet-home',\n validate: (v) => v.trim().length > 0 || 'Project name is required',\n });\n\n // 2. Admin username\n const adminUsername = await input({\n message: 'Admin username',\n default: 'admin',\n validate: (v) => v.trim().length > 0 || 'Username is required',\n });\n\n // 3. Admin password\n const adminPassword = await password({\n message: 'Admin password',\n mask: '*',\n validate: (v) => v.length >= 8 || 'Password must be at least 8 characters',\n });\n\n console.log();\n\n // 4. Build WizardState\n let state = createState();\n state = {\n ...state,\n projectName: projectName.trim(),\n projectPath: `~/brewnet/${projectName.trim()}`,\n admin: {\n ...state.admin,\n username: adminUsername.trim(),\n password: adminPassword,\n },\n };\n state = applyMinimalInstallDefaults(state);\n\n // 5. Save state so admin-server can load credentials\n saveState(state);\n\n // 6. Generate → Complete (reuses existing steps)\n const result = await runGenerateStep(state);\n // Persist any runtime values captured during generation (e.g. quickTunnelUrl)\n try { saveState(state); } catch { /* non-critical */ }\n\n if (result === 'success') {\n await runCompleteStep(state, { noOpen: options.noOpen });\n }\n}\n","/**\n * brewnet status — Show service status\n *\n * Lists all managed services and their current state (running, stopped, etc.).\n * Supports --json for machine-readable output.\n *\n * @module commands/status\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { execa } from 'execa';\nimport { DOCKER_COMPOSE_FILENAME } from '@brewnet/shared';\nimport { checkDockerAvailability } from '../services/docker-manager.js';\nimport { BrewnetError } from '../utils/errors.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ContainerInfo {\n name: string;\n state: 'running' | 'exited' | 'created' | 'restarting' | 'removing' | 'paused' | 'dead';\n status: string;\n ports: string[];\n image: string;\n created: number;\n}\n\nexport interface StatusRow {\n name: string;\n status: string;\n image: string;\n ports: string;\n uptime: string;\n}\n\n// ---------------------------------------------------------------------------\n// Status indicator\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a colored status indicator string for a given Docker container state.\n *\n * - 'running' -> green bullet + \"Running\"\n * - 'exited' or 'dead' -> red bullet + \"Stopped\"\n * - 'restarting', 'paused', 'created', 'removing' -> yellow bullet + state label\n * - unknown -> dim bullet + capitalized state\n */\nexport function getStatusIndicator(state: string): string {\n const bullet = '\\u25CF'; // ●\n\n switch (state) {\n case 'running':\n return chalk.green(`${bullet} Running`);\n case 'exited':\n return chalk.red(`${bullet} Stopped`);\n case 'dead':\n return chalk.red(`${bullet} Stopped`);\n case 'restarting':\n return chalk.yellow(`${bullet} Restarting`);\n case 'paused':\n return chalk.yellow(`${bullet} Paused`);\n case 'created':\n return chalk.yellow(`${bullet} Created`);\n case 'removing':\n return chalk.yellow(`${bullet} Removing`);\n default:\n return chalk.dim(`${bullet} ${state.charAt(0).toUpperCase()}${state.slice(1)}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Uptime parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parses a Docker status string and extracts a human-friendly uptime value.\n *\n * Examples:\n * \"Up 2 hours\" -> \"2h\"\n * \"Up 30 minutes\" -> \"30m\"\n * \"Up 3 days\" -> \"3d\"\n * \"Up About an hour\" -> \"1h\"\n * \"Up Less than a second\" -> \"<1s\"\n * \"Exited (0) 5 minutes ago\" -> \"-\"\n */\nfunction parseUptime(status: string): string {\n if (!status.startsWith('Up')) {\n return '-';\n }\n\n const text = status.slice(3); // Remove \"Up \" prefix\n\n // \"Less than a second\"\n if (/less than a second/i.test(text)) {\n return '<1s';\n }\n\n // \"About an hour\" / \"About a minute\"\n if (/about an? hour/i.test(text)) {\n return '1h';\n }\n if (/about an? minute/i.test(text)) {\n return '1m';\n }\n\n // Numeric patterns: \"2 hours\", \"30 minutes\", \"3 days\", \"5 seconds\"\n const match = text.match(/^(\\d+)\\s+(second|minute|hour|day|week|month|year)s?/i);\n if (match) {\n const value = match[1];\n const unit = match[2].toLowerCase();\n const unitMap: Record<string, string> = {\n second: 's',\n minute: 'm',\n hour: 'h',\n day: 'd',\n week: 'w',\n month: 'mo',\n year: 'y',\n };\n return `${value}${unitMap[unit] ?? unit.charAt(0)}`;\n }\n\n // Fallback for unrecognized \"Up ...\" strings\n return '-';\n}\n\n// ---------------------------------------------------------------------------\n// Format functions\n// ---------------------------------------------------------------------------\n\n/**\n * Transforms an array of Docker container info objects into formatted status rows.\n */\nexport function formatServiceStatus(containers: ContainerInfo[]): StatusRow[] {\n return containers.map((c) => ({\n name: c.name,\n status: getStatusIndicator(c.state),\n image: c.image,\n ports: c.ports.join(', '),\n uptime: parseUptime(c.status),\n }));\n}\n\n/**\n * Renders an array of status rows into a formatted CLI table string.\n * Returns a \"No services running\" message if the array is empty.\n */\nexport function formatStatusTable(rows: StatusRow[]): string {\n if (rows.length === 0) {\n return chalk.yellow('No services running');\n }\n\n // Compute column widths (minimum widths based on headers)\n const headers = ['Name', 'Status', 'Image', 'Ports', 'Uptime'];\n const stripAnsi = (s: string) => s.replace(/\\x1B\\[[0-9;]*m/g, '');\n\n const colWidths = headers.map((h) => h.length);\n\n for (const row of rows) {\n const values = [row.name, stripAnsi(row.status), row.image, row.ports, row.uptime];\n for (let i = 0; i < values.length; i++) {\n colWidths[i] = Math.max(colWidths[i], values[i].length);\n }\n }\n\n const pad = (str: string, width: number, rawLength?: number) => {\n const len = rawLength ?? str.length;\n return str + ' '.repeat(Math.max(0, width - len));\n };\n\n const separator = colWidths.map((w) => '-'.repeat(w)).join(' ');\n\n // Header line\n const headerLine = headers.map((h, i) => pad(chalk.bold(h), colWidths[i], h.length)).join(' ');\n\n // Data lines\n const dataLines = rows.map((row) => {\n const values = [row.name, row.status, row.image, row.ports, row.uptime];\n const rawValues = [row.name, stripAnsi(row.status), row.image, row.ports, row.uptime];\n return values.map((v, i) => pad(v, colWidths[i], rawValues[i].length)).join(' ');\n });\n\n return [headerLine, separator, ...dataLines].join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Docker compose ps output parsing\n// ---------------------------------------------------------------------------\n\ninterface DockerComposePsEntry {\n Name?: string;\n Service?: string;\n State?: string;\n Status?: string;\n ExitCode?: number;\n Image?: string;\n Publishers?: { PublishedPort: number; TargetPort: number; Protocol: string }[];\n Created?: number;\n}\n\nfunction parseDockerComposePsEntry(entry: DockerComposePsEntry): ContainerInfo {\n const rawState = (entry.State ?? 'unknown').toLowerCase();\n const validStates = ['running', 'exited', 'created', 'restarting', 'removing', 'paused', 'dead'];\n const state = validStates.includes(rawState)\n ? (rawState as ContainerInfo['state'])\n : 'dead';\n\n const ports = (entry.Publishers ?? [])\n .filter((p) => p.PublishedPort > 0)\n .map((p) => `${p.PublishedPort}→${p.TargetPort}/${p.Protocol}`);\n\n // Derive a friendly service name: prefer Service field, strip project prefix from Name\n const stripped = (entry.Name ?? '').replace(/^[^-]+-/, '').replace(/-\\d+$/, '');\n const name = entry.Service ?? (stripped || entry.Name) ?? 'unknown';\n\n return {\n name,\n state,\n status: entry.Status ?? state,\n ports,\n image: entry.Image ?? '',\n created: entry.Created ?? 0,\n };\n}\n\nfunction parseDockerComposePsOutput(stdout: string): ContainerInfo[] {\n const trimmed = stdout.trim();\n if (!trimmed) return [];\n\n // Try JSON array first (docker compose v2.20+)\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (Array.isArray(parsed)) {\n return parsed.map((e) => parseDockerComposePsEntry(e as DockerComposePsEntry));\n }\n } catch {\n // fall through to NDJSON\n }\n\n // NDJSON — one JSON object per line\n return trimmed\n .split('\\n')\n .filter(Boolean)\n .flatMap((line) => {\n try {\n return [parseDockerComposePsEntry(JSON.parse(line) as DockerComposePsEntry)];\n } catch {\n return [];\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Command registration\n// ---------------------------------------------------------------------------\n\nexport function registerStatusCommand(program: Command): void {\n program\n .command('status')\n .description('Show service status')\n .option('-p, --path <path>', 'Project path (defaults to current directory)', process.cwd())\n .option('--json', 'Output status as JSON for scripting')\n .action(async (options: { path: string; json: boolean }) => {\n // Guard: Docker must be running\n try {\n await checkDockerAvailability();\n } catch (err) {\n const msg = err instanceof BrewnetError ? err.message : String(err);\n console.error(chalk.red(`Error [BN001]: ${msg}`));\n process.exitCode = 1;\n return;\n }\n\n try {\n const { stdout } = await execa(\n 'docker',\n ['compose', '-f', DOCKER_COMPOSE_FILENAME, 'ps', '--all', '--format', 'json'],\n { cwd: options.path },\n );\n\n const containers = parseDockerComposePsOutput(stdout);\n\n if (options.json) {\n console.log(JSON.stringify(containers, null, 2));\n return;\n }\n\n const rows = formatServiceStatus(containers);\n console.log(formatStatusTable(rows));\n\n if (containers.length > 0) {\n const running = containers.filter((c) => c.state === 'running').length;\n console.log(\n chalk.dim(`\\n ${running}/${containers.length} service${containers.length !== 1 ? 's' : ''} running`),\n );\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (\n msg.includes('no configuration file provided') ||\n msg.includes('not found') ||\n msg.includes('No such file')\n ) {\n console.log(chalk.yellow('No brewnet project found in the current directory.'));\n console.log(\n chalk.dim(` Run ${chalk.bold('brewnet init')} to set up a project, or use `) +\n chalk.bold('-p <path>') +\n chalk.dim(' to specify a project path.'),\n );\n } else {\n console.error(chalk.red(`Error: ${msg}`));\n process.exitCode = 1;\n }\n }\n });\n}\n","/**\n * Brewnet CLI — Docker Manager (T025)\n *\n * Manages Docker daemon interactions, container lifecycle,\n * and docker-compose generation.\n *\n * Uses `dockerode` to communicate with the Docker Engine API.\n *\n * @module services/docker-manager\n */\n\nimport Dockerode from 'dockerode';\nimport { BrewnetError } from '../utils/errors.js';\n\n/**\n * Result of a Docker availability check.\n */\nexport interface DockerAvailabilityResult {\n available: true;\n}\n\n/**\n * Options for creating a DockerManager instance.\n */\nexport interface DockerManagerOptions {\n /** Custom dockerode instance (useful for testing). */\n docker?: Dockerode;\n}\n\n/**\n * Check whether the Docker daemon is reachable.\n *\n * @param options - Optional configuration (e.g. a custom Dockerode instance).\n * @returns `{ available: true }` when the daemon responds to a ping.\n * @throws {BrewnetError} BN001 when the Docker daemon is not running.\n */\nexport async function checkDockerAvailability(\n options?: DockerManagerOptions,\n): Promise<DockerAvailabilityResult> {\n const docker = options?.docker ?? new Dockerode();\n\n try {\n await docker.ping();\n return { available: true };\n } catch {\n throw BrewnetError.dockerNotRunning();\n }\n}\n","/**\n * brewnet add <service> — Add a service\n *\n * Adds a Docker-based service (e.g., jellyfin, nextcloud) to the project.\n * Looks up the service in SERVICE_REGISTRY, updates docker-compose.yml,\n * and creates a backup before any modification.\n *\n * @module commands/add\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { addService } from '../services/service-manager.js';\n\nexport function registerAddCommand(program: Command): void {\n program\n .command('add')\n .description('Add a service')\n .argument('<service>', 'Name of the service to add (e.g., jellyfin, nextcloud)')\n .option('-p, --path <path>', 'Project path (defaults to current directory)', process.cwd())\n .action(async (service: string, options: { path: string }) => {\n const spinner = ora(`Adding service ${chalk.cyan(service)}...`).start();\n\n const result = await addService(service, options.path);\n\n if (result.success) {\n spinner.succeed(\n `Service ${chalk.cyan(service)} added to ${chalk.dim(result.composePath!)}`,\n );\n if (result.backupPath) {\n console.log(chalk.dim(` Backup: ${result.backupPath}`));\n }\n console.log(\n chalk.yellow(`\\n Run ${chalk.bold('brewnet up')} to start the updated services.`),\n );\n } else {\n spinner.fail(chalk.red(result.error ?? 'Failed to add service'));\n process.exitCode = 1;\n }\n });\n}\n","/**\n * brewnet remove <service> — Remove a service\n *\n * Removes a managed service from docker-compose.yml.\n * Use --purge to also delete associated volumes and configuration data.\n * Prompts for confirmation unless --force is specified.\n *\n * @module commands/remove\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { confirm } from '@inquirer/prompts';\nimport { removeService } from '../services/service-manager.js';\n\nexport function registerRemoveCommand(program: Command): void {\n program\n .command('remove')\n .description('Remove a service')\n .argument('<service>', 'Name of the service to remove')\n .option('--purge', 'Also remove associated volumes and configuration data')\n .option('--force', 'Skip confirmation prompt')\n .option('-p, --path <path>', 'Project path (defaults to current directory)', process.cwd())\n .action(async (service: string, options: { purge?: boolean; force?: boolean; path: string }) => {\n // Confirmation prompt unless --force is provided\n if (!options.force) {\n const purgeWarning = options.purge\n ? chalk.red(' (including all associated data volumes)')\n : '';\n const confirmed = await confirm({\n message: `Remove service ${chalk.cyan(service)}${purgeWarning}?`,\n default: false,\n });\n if (!confirmed) {\n console.log(chalk.dim('Cancelled.'));\n return;\n }\n }\n\n const spinner = ora(`Removing service ${chalk.cyan(service)}...`).start();\n\n const result = await removeService(service, options.path, {\n purge: options.purge,\n });\n\n if (result.success) {\n spinner.succeed(\n `Service ${chalk.cyan(service)} removed from ${chalk.dim(result.composePath!)}`,\n );\n if (result.backupPath) {\n console.log(chalk.dim(` Backup: ${result.backupPath}`));\n }\n if (options.purge) {\n console.log(chalk.yellow(' Associated volume entries have been removed from compose.'));\n console.log(\n chalk.yellow(' Run ') +\n chalk.bold('docker volume prune') +\n chalk.yellow(' to reclaim disk space.'),\n );\n }\n console.log(\n chalk.yellow(`\\n Run ${chalk.bold('brewnet up')} to apply the updated configuration.`),\n );\n } else {\n spinner.fail(chalk.red(result.error ?? 'Failed to remove service'));\n process.exitCode = 1;\n }\n });\n}\n","/**\n * brewnet up — Start all services\n *\n * Starts all managed services via docker compose up -d.\n *\n * @module commands/up\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { execa } from 'execa';\nimport { DOCKER_COMPOSE_FILENAME } from '@brewnet/shared';\n\nexport function registerUpCommand(program: Command): void {\n program\n .command('up')\n .description('Start all services')\n .option('-p, --path <path>', 'Project path (defaults to current directory)', process.cwd())\n .option('-d, --detach', 'Run in detached mode (default)', true)\n .action(async (options: { path: string; detach: boolean }) => {\n const spinner = ora('Starting services...').start();\n\n try {\n const { stdout, stderr } = await execa(\n 'docker',\n ['compose', '-f', DOCKER_COMPOSE_FILENAME, 'up', '-d'],\n { cwd: options.path },\n );\n\n spinner.succeed(chalk.green('All services started.'));\n\n if (stdout) {\n console.log(chalk.dim(stdout));\n }\n if (stderr) {\n // docker compose often writes progress to stderr\n console.log(chalk.dim(stderr));\n }\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : 'Unknown error starting services';\n spinner.fail(chalk.red(`Failed to start services: ${message}`));\n process.exitCode = 1;\n }\n });\n}\n","/**\n * brewnet down — Stop all services\n *\n * Stops all managed services via docker compose down.\n * Use --volumes to also remove associated Docker volumes.\n *\n * @module commands/down\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { execa } from 'execa';\nimport { DOCKER_COMPOSE_FILENAME } from '@brewnet/shared';\n\nexport function registerDownCommand(program: Command): void {\n program\n .command('down')\n .description('Stop all services')\n .option('--volumes', 'Also remove associated Docker volumes')\n .option('-p, --path <path>', 'Project path (defaults to current directory)', process.cwd())\n .action(async (options: { volumes?: boolean; path: string }) => {\n const spinner = ora('Stopping services...').start();\n\n try {\n const args = ['compose', '-f', DOCKER_COMPOSE_FILENAME, 'down'];\n if (options.volumes) {\n args.push('--volumes');\n }\n\n const { stdout, stderr } = await execa('docker', args, {\n cwd: options.path,\n });\n\n spinner.succeed(chalk.green('All services stopped.'));\n\n if (stdout) {\n console.log(chalk.dim(stdout));\n }\n if (stderr) {\n console.log(chalk.dim(stderr));\n }\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : 'Unknown error stopping services';\n spinner.fail(chalk.red(`Failed to stop services: ${message}`));\n process.exitCode = 1;\n }\n });\n}\n","/**\n * brewnet logs [service] — View service logs\n *\n * Displays logs for all services or a specific one via docker compose logs.\n * Use -f/--follow to stream logs in real time.\n * Use -n/--tail to limit the number of lines shown.\n *\n * New aggregator flags (--all, --source, --level, --since, --json) read\n * from CLI JSONL, Tunnel NDJSON, Traefik access log, and Docker container logs\n * and display a unified view.\n *\n * @module commands/logs\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { execa } from 'execa';\nimport { DOCKER_COMPOSE_FILENAME } from '@brewnet/shared';\nimport type { LogSource, UnifiedLogLevel, UnifiedLogEntry } from '@brewnet/shared';\nimport { parseDuration, queryLogs } from '../utils/log-aggregator.js';\n\nconst VALID_SOURCES: LogSource[] = ['cli', 'tunnel', 'access', 'service'];\nconst VALID_LEVELS: UnifiedLogLevel[] = ['info', 'warn', 'error', 'debug'];\n\nconst SOURCE_COLORS: Record<LogSource, (s: string) => string> = {\n cli: chalk.cyan,\n tunnel: chalk.magenta,\n access: chalk.blue,\n service: chalk.white,\n};\n\nconst LEVEL_COLORS: Record<string, (s: string) => string> = {\n info: chalk.green,\n warn: chalk.yellow,\n error: chalk.red,\n debug: chalk.gray,\n};\n\nfunction formatEntry(entry: UnifiedLogEntry): string {\n const ts = entry.timestamp.replace('T', ' ').replace(/\\.\\d+Z$/, '').replace('Z', '');\n const sourceColor = SOURCE_COLORS[entry.source] ?? chalk.white;\n const levelColor = LEVEL_COLORS[entry.level] ?? chalk.white;\n const source = sourceColor(entry.source.toUpperCase().padEnd(7));\n const service = (entry.service ?? '').padEnd(12);\n const message = levelColor(entry.message);\n return `${ts} ${source} ${service} ${message}`;\n}\n\ninterface LogsOptions {\n follow?: boolean;\n tail?: string;\n path: string;\n all?: boolean;\n source?: string;\n level?: string;\n since?: string;\n json?: boolean;\n}\n\nexport function registerLogsCommand(program: Command): void {\n program\n .command('logs')\n .description('View service logs')\n .argument('[service]', 'Name of a specific service (shows all if omitted)')\n .option('-f, --follow', 'Follow log output in real time')\n .option('-n, --tail <lines>', 'Number of lines to show from the end of the logs')\n .option('-p, --path <path>', 'Project path (defaults to current directory)', process.cwd())\n .option('--all', 'Show unified logs from all sources')\n .option('--source <type>', 'Filter by source (cli|tunnel|access|service)')\n .option('--level <level>', 'Filter by severity (info|warn|error|debug)')\n .option('--since <duration>', 'Time range start (1h, 30m, 1d, or ISO date)')\n .option('--json', 'Output as JSON lines')\n .action(async (service: string | undefined, options: LogsOptions) => {\n const useAggregator = options.all || options.source || options.level || options.since;\n\n // Validate flag conflicts\n if (options.json && !useAggregator) {\n console.error(chalk.red('--json requires --all or --source'));\n process.exitCode = 1;\n return;\n }\n if (options.follow && useAggregator) {\n console.error(\n chalk.red(\n '--follow is not supported with --all/--source (use without these flags for real-time streaming)',\n ),\n );\n process.exitCode = 1;\n return;\n }\n\n if (useAggregator) {\n await handleAggregatorLogs(service, options);\n } else {\n await handleDockerLogs(service, options);\n }\n });\n}\n\nasync function handleAggregatorLogs(\n service: string | undefined,\n options: LogsOptions,\n): Promise<void> {\n // Validate --source\n if (options.source && !VALID_SOURCES.includes(options.source as LogSource)) {\n console.error(\n chalk.red(\n `Invalid source: '${options.source}'. Valid: ${VALID_SOURCES.join(', ')}`,\n ),\n );\n process.exitCode = 1;\n return;\n }\n\n // Validate --level\n if (options.level && !VALID_LEVELS.includes(options.level as UnifiedLogLevel)) {\n console.error(\n chalk.red(\n `Invalid level: '${options.level}'. Valid: ${VALID_LEVELS.join(', ')}`,\n ),\n );\n process.exitCode = 1;\n return;\n }\n\n // Parse --since\n let since: string | undefined;\n if (options.since) {\n try {\n since = parseDuration(options.since);\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Invalid time format';\n console.error(chalk.red(message));\n process.exitCode = 1;\n return;\n }\n }\n\n try {\n const result = await queryLogs(\n {\n sources: options.source ? [options.source as LogSource] : undefined,\n levels: options.level ? [options.level as UnifiedLogLevel] : undefined,\n services: service ? [service] : undefined,\n since,\n },\n options.path,\n );\n\n if (result.entries.length === 0) {\n console.log(chalk.gray('No log entries found.'));\n return;\n }\n\n if (options.json) {\n for (const entry of result.entries) {\n console.log(JSON.stringify(entry));\n }\n } else {\n for (const entry of result.entries) {\n console.log(formatEntry(entry));\n }\n if (result.hasMore) {\n console.log(chalk.gray(`\\n… ${result.total - result.entries.length} more entries`));\n }\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n console.error(chalk.red(`Failed to query logs: ${message}`));\n process.exitCode = 1;\n }\n}\n\nasync function handleDockerLogs(\n service: string | undefined,\n options: LogsOptions,\n): Promise<void> {\n try {\n const args = ['compose', '-f', DOCKER_COMPOSE_FILENAME, 'logs'];\n\n if (options.follow) {\n args.push('--follow');\n }\n if (options.tail) {\n args.push('--tail', options.tail);\n }\n if (service) {\n args.push(service);\n }\n\n // Stream logs directly to stdout/stderr using inherit\n await execa('docker', args, {\n cwd: options.path,\n stdio: 'inherit',\n });\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : 'Unknown error fetching logs';\n console.error(chalk.red(`Failed to fetch logs: ${message}`));\n process.exitCode = 1;\n }\n}\n","/**\n * brewnet backup — Create a backup\n *\n * Creates a backup of the project directory as a .tar.gz archive.\n * Includes service configurations, data, and the local SQLite database.\n *\n * @module commands/backup\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { createBackup, listBackups, checkDiskSpace } from '../services/backup-manager.js';\nimport { isBrewnetError } from '../utils/errors.js';\n\nconst DEFAULT_BACKUPS_DIR = join(homedir(), '.brewnet', 'backups');\n\nexport function registerBackupCommand(program: Command): void {\n program\n .command('backup')\n .description('Create a backup')\n .option('-p, --path <path>', 'Project path to back up (defaults to current directory)', process.cwd())\n .option('--backups-dir <dir>', 'Directory to store backups', DEFAULT_BACKUPS_DIR)\n .option('--list', 'List existing backups instead of creating one')\n .action(async (options: { path: string; backupsDir: string; list?: boolean }) => {\n try {\n // List mode: display existing backups\n if (options.list) {\n const backups = listBackups(options.backupsDir);\n\n if (backups.length === 0) {\n console.log(chalk.yellow('No backups found.'));\n return;\n }\n\n console.log(chalk.bold(`\\nBackups (${backups.length}):\\n`));\n for (const backup of backups) {\n const date = new Date(backup.timestamp).toLocaleString();\n const sizeMB = (backup.size / (1024 * 1024)).toFixed(2);\n console.log(\n ` ${chalk.cyan(backup.id)} ${chalk.dim(date)} ${chalk.dim(sizeMB + ' MB')} ${backup.projectName}`,\n );\n }\n console.log('');\n return;\n }\n\n // Check disk space before creating backup\n const diskCheck = checkDiskSpace(options.path);\n if (!diskCheck.sufficient) {\n console.log(chalk.red('Insufficient disk space for backup.'));\n process.exitCode = 1;\n return;\n }\n\n const spinner = ora('Creating backup...').start();\n\n const record = createBackup(options.path, options.backupsDir);\n\n const sizeMB = (record.size / (1024 * 1024)).toFixed(2);\n spinner.succeed(\n `Backup created: ${chalk.cyan(record.id)}`,\n );\n console.log(chalk.dim(` Project: ${record.projectName}`));\n console.log(chalk.dim(` Archive: ${record.path}`));\n console.log(chalk.dim(` Size: ${sizeMB} MB`));\n console.log('');\n console.log(\n chalk.dim(` Restore with: `) +\n chalk.bold(`brewnet restore ${record.id}`),\n );\n } catch (err) {\n if (isBrewnetError(err)) {\n console.error(chalk.red(err.format()));\n } else {\n console.error(chalk.red('Backup failed:'), err instanceof Error ? err.message : err);\n }\n process.exitCode = 1;\n }\n });\n}\n","/**\n * brewnet restore <backup-id> — Restore from backup\n *\n * Restores service configurations and data from a previously\n * created backup identified by its backup ID.\n *\n * @module commands/restore\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { confirm } from '@inquirer/prompts';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { restoreBackup, listBackups, checkDiskSpace } from '../services/backup-manager.js';\nimport { isBrewnetError } from '../utils/errors.js';\n\nconst DEFAULT_BACKUPS_DIR = join(homedir(), '.brewnet', 'backups');\n\nexport function registerRestoreCommand(program: Command): void {\n program\n .command('restore')\n .description('Restore from backup')\n .argument('<backup-id>', 'ID of the backup to restore')\n .option('-p, --path <path>', 'Target project path for restoration (defaults to current directory)', process.cwd())\n .option('--backups-dir <dir>', 'Directory where backups are stored', DEFAULT_BACKUPS_DIR)\n .option('--force', 'Skip confirmation prompt')\n .action(async (backupId: string, options: { path: string; backupsDir: string; force?: boolean }) => {\n try {\n // Validate backup ID format\n if (!backupId.startsWith('backup-')) {\n console.error(chalk.red(`Invalid backup ID format: ${backupId}`));\n console.error(chalk.dim(' Expected format: backup-<timestamp>-<random>'));\n console.error(chalk.dim(' List backups with: brewnet backup --list'));\n process.exitCode = 1;\n return;\n }\n\n // Check if backup exists by looking it up in the list\n const backups = listBackups(options.backupsDir);\n const backup = backups.find((b) => b.id === backupId);\n\n if (!backup) {\n console.error(chalk.red(`Backup not found: ${backupId}`));\n if (backups.length > 0) {\n console.error(chalk.dim('\\nAvailable backups:'));\n for (const b of backups.slice(0, 5)) {\n console.error(chalk.dim(` ${b.id} ${b.projectName}`));\n }\n } else {\n console.error(chalk.dim(' No backups found in ' + options.backupsDir));\n }\n process.exitCode = 1;\n return;\n }\n\n // Check disk space\n const diskCheck = checkDiskSpace(options.path, backup.size);\n if (!diskCheck.sufficient) {\n console.error(chalk.red('Insufficient disk space for restore.'));\n const requiredMB = (backup.size / (1024 * 1024)).toFixed(2);\n const availableMB = (diskCheck.available / (1024 * 1024)).toFixed(2);\n console.error(chalk.dim(` Required: ${requiredMB} MB`));\n console.error(chalk.dim(` Available: ${availableMB} MB`));\n process.exitCode = 1;\n return;\n }\n\n // Confirmation prompt unless --force is provided\n if (!options.force) {\n const date = new Date(backup.timestamp).toLocaleString();\n console.log('');\n console.log(chalk.bold('Restore details:'));\n console.log(chalk.dim(` Backup: ${backup.id}`));\n console.log(chalk.dim(` Project: ${backup.projectName}`));\n console.log(chalk.dim(` Date: ${date}`));\n console.log(chalk.dim(` Target: ${options.path}`));\n console.log('');\n\n const confirmed = await confirm({\n message: `Restore backup ${chalk.cyan(backupId)} to ${chalk.cyan(options.path)}?`,\n default: false,\n });\n if (!confirmed) {\n console.log(chalk.dim('Cancelled.'));\n return;\n }\n }\n\n const spinner = ora(`Restoring backup ${chalk.cyan(backupId)}...`).start();\n\n restoreBackup(backupId, options.backupsDir, options.path);\n\n spinner.succeed(`Backup ${chalk.cyan(backupId)} restored to ${chalk.dim(options.path)}`);\n console.log(\n chalk.yellow(`\\n Run ${chalk.bold('brewnet up')} to start services with the restored configuration.`),\n );\n } catch (err) {\n if (isBrewnetError(err)) {\n console.error(chalk.red(err.format()));\n } else {\n console.error(chalk.red('Restore failed:'), err instanceof Error ? err.message : err);\n }\n process.exitCode = 1;\n }\n });\n}\n","/**\n * brewnet admin — Start local admin panel as a background daemon\n *\n * Spawns a detached process that survives terminal close.\n * The CLI exits immediately after the daemon is confirmed running.\n *\n * Default port: 8088 (override with --port)\n *\n * @module commands/admin\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { launchAdminDaemon } from '../services/admin-launcher.js';\n\n/** Kill any process listening on the given port (best-effort, cross-platform). */\nasync function killPort(port: number): Promise<void> {\n try {\n const { execSync } = await import('node:child_process');\n const pids = execSync(`lsof -ti :${port} 2>/dev/null || true`, { encoding: 'utf-8' }).trim();\n if (pids) {\n for (const pid of pids.split('\\n').filter(Boolean)) {\n try { process.kill(parseInt(pid, 10), 'SIGTERM'); } catch { /* ignore */ }\n }\n // Brief wait for port release\n await new Promise((r) => setTimeout(r, 1000));\n }\n } catch { /* non-critical */ }\n}\n\nexport function registerAdminCommand(program: Command): void {\n program\n .command('admin')\n .description('Start the local admin panel at http://localhost:8088')\n .option('--port <port>', 'Port to listen on (default: 8088)', '8088')\n .option('-p, --path <path>', 'Project path (defaults to last init project)', '')\n .option('--no-open', 'Do not automatically open in browser')\n .option('--foreground', 'Run in foreground (do not daemonize)')\n .action(async (options: { port: string; path: string; open: boolean; foreground: boolean }) => {\n const port = parseInt(options.port, 10);\n if (isNaN(port) || port < 1 || port > 65535) {\n console.error(chalk.red('Invalid port number.'));\n process.exitCode = 1;\n return;\n }\n\n // --foreground: run in current process (for debugging / test-cycle.sh)\n if (options.foreground) {\n const { createAdminServer } = await import('../services/admin-server.js');\n const spinner = ora(`Starting admin panel on port ${port}...`).start();\n const { start } = createAdminServer({ port, projectPath: options.path || undefined });\n try {\n await start();\n spinner.succeed(chalk.green(`Admin panel running at `) + chalk.cyan(`http://localhost:${port}`));\n console.log(chalk.dim(' Press Ctrl+C to stop.\\n'));\n process.on('SIGHUP', () => { /* survive terminal close */ });\n if (options.open) {\n try {\n const { execa } = await import('execa');\n const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'linux' ? 'xdg-open' : 'cmd';\n const args = process.platform === 'win32' ? ['/c', 'start', `http://localhost:${port}`] : [`http://localhost:${port}`];\n await execa(cmd, args);\n } catch { /* best-effort */ }\n }\n await new Promise<void>((resolve) => {\n process.once('SIGINT', resolve);\n process.once('SIGTERM', resolve);\n });\n console.log(chalk.dim('\\nShutting down admin panel...'));\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n spinner.fail(chalk.red(msg.includes('EADDRINUSE') ? `Port ${port} is already in use.` : `Failed: ${msg}`));\n process.exitCode = 1;\n }\n return;\n }\n\n // Default: daemon mode — spawn background process and exit\n const spinner = ora(`Starting admin panel on port ${port}...`).start();\n\n try {\n // Kill any existing admin on the same port\n await killPort(port);\n\n const result = await launchAdminDaemon({\n port,\n projectPath: options.path || undefined,\n });\n\n spinner.succeed(\n chalk.green(`Admin panel running at `) + chalk.cyan(`http://localhost:${port}`) +\n chalk.dim(` (PID ${result.pid})`),\n );\n console.log(chalk.dim(` Log: ${result.logFile}`));\n console.log(chalk.dim(` Stop: kill ${result.pid} or kill $(lsof -ti :${port})`));\n console.log();\n\n // Auto-open in default browser\n if (options.open) {\n try {\n const { execa } = await import('execa');\n const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'linux' ? 'xdg-open' : 'cmd';\n const args = process.platform === 'win32' ? ['/c', 'start', `http://localhost:${port}`] : [`http://localhost:${port}`];\n await execa(cmd, args);\n } catch { /* best-effort */ }\n }\n\n // CLI exits here — daemon continues in background\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('EADDRINUSE')) {\n spinner.fail(chalk.red(`Port ${port} is already in use. Try --port <other>`));\n } else {\n spinner.fail(chalk.red(`Failed to start admin panel: ${msg}`));\n }\n process.exitCode = 1;\n }\n });\n}\n","/**\n * brewnet shutdown — Stop the admin panel daemon\n *\n * Finds and kills the admin server process running on port 8088 (or --port).\n *\n * @module commands/shutdown\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { execSync } from 'node:child_process';\n\nexport function registerShutdownCommand(program: Command): void {\n program\n .command('shutdown')\n .description('Stop the admin panel daemon')\n .option('--port <port>', 'Admin panel port (default: 8088)', '8088')\n .action((options: { port: string }) => {\n const port = parseInt(options.port, 10);\n\n try {\n const pids = execSync(`lsof -ti :${port} 2>/dev/null || true`, { encoding: 'utf-8' })\n .trim()\n .split('\\n')\n .filter(Boolean);\n\n if (pids.length === 0) {\n console.log(chalk.dim(` No process found on port ${port}.`));\n return;\n }\n\n for (const pid of pids) {\n try {\n process.kill(parseInt(pid, 10), 'SIGTERM');\n } catch { /* already dead */ }\n }\n\n console.log(chalk.green(` Admin panel stopped.`) + chalk.dim(` (PID ${pids.join(', ')}, port ${port})`));\n } catch {\n console.log(chalk.dim(` No admin panel running on port ${port}.`));\n }\n });\n}\n","/**\n * brewnet uninstall — Complete uninstall\n *\n * Removes all Brewnet services, volumes, networks, project files,\n * and ~/.brewnet metadata. Accepts --dry-run, --keep-data,\n * --keep-config, and --force flags.\n *\n * @module commands/uninstall\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { confirm, select } from '@inquirer/prompts';\nimport {\n buildUninstallTargets,\n listInstallations,\n runUninstall,\n} from '../services/uninstall-manager.js';\nimport { getLastProject, loadState } from '../wizard/state.js';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction printTargets(\n targets: ReturnType<typeof buildUninstallTargets>,\n): void {\n for (const t of targets) {\n const tag = t.skipped\n ? chalk.dim(` [skip] ${t.label} (${t.skipReason ?? 'preserved'})`)\n : chalk.red(` [remove] ${t.label}`);\n console.log(tag);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command registration (T114 + T115)\n// ---------------------------------------------------------------------------\n\nexport function registerUninstallCommand(program: Command): void {\n program\n .command('uninstall')\n .description('Remove all Brewnet services, volumes, and project files')\n .option('--dry-run', 'List what would be removed without making changes')\n .option('--keep-data', 'Preserve Docker volumes (database and file data)')\n .option('--keep-config', 'Preserve project directory (stop containers only)')\n .option('--force', 'Skip confirmation prompt')\n .action(\n async (options: {\n dryRun: boolean;\n keepData: boolean;\n keepConfig: boolean;\n force: boolean;\n }) => {\n // --- Gather project info ---\n const installations = listInstallations();\n let lastProject = getLastProject();\n let state = lastProject ? loadState(lastProject) : null;\n let discoveredPath: string | null = null;\n\n // If wizard state is missing but filesystem scan found installations,\n // pick the target from discovered projects instead.\n if (!state && installations.length > 0) {\n let picked = installations[0];\n if (installations.length > 1) {\n picked = await select({\n message: '삭제할 프로젝트를 선택하세요',\n choices: installations.map((inst) => ({\n value: inst,\n name: `${inst.name} ${chalk.dim(inst.path ?? '')}`,\n })),\n });\n }\n lastProject = picked.name;\n state = lastProject ? loadState(lastProject) : null;\n discoveredPath = picked.path ?? null;\n }\n\n // --- Dry-run banner ---\n if (options.dryRun) {\n console.log(chalk.cyan('\\nDry-run mode — no changes will be made.\\n'));\n }\n\n // --- Show what is installed ---\n if (installations.length === 0) {\n console.log(chalk.yellow('No Brewnet installations found in wizard state.'));\n console.log(chalk.dim(' Checking for orphaned Docker resources...\\n'));\n\n // Even without project state, try to clean up brewnet-* containers/volumes/networks\n if (!options.dryRun) {\n const { execa: execaFn } = await import('execa');\n const env = { ...process.env, PATH: `/usr/local/bin:/opt/homebrew/bin:${process.env['PATH'] ?? ''}` };\n\n // Stop & remove brewnet-* containers\n const psResult = await execaFn('docker', ['ps', '-a', '--filter', 'name=brewnet', '-q'], { env, reject: false });\n const containerIds = psResult.stdout.trim().split('\\n').filter(Boolean);\n if (containerIds.length > 0) {\n await execaFn('docker', ['rm', '-f', ...containerIds], { env, reject: false });\n console.log(chalk.green(` ✓ Removed ${containerIds.length} orphaned container(s)`));\n }\n\n // Remove brewnet-* volumes\n const volResult = await execaFn('docker', ['volume', 'ls', '--filter', 'name=brewnet', '-q'], { env, reject: false });\n const volNames = volResult.stdout.trim().split('\\n').filter(Boolean);\n if (volNames.length > 0) {\n await execaFn('docker', ['volume', 'rm', ...volNames], { env, reject: false });\n console.log(chalk.green(` ✓ Removed ${volNames.length} orphaned volume(s)`));\n }\n\n // Remove brewnet-* networks\n const netResult = await execaFn('docker', ['network', 'ls', '--filter', 'name=brewnet', '-q'], { env, reject: false });\n const netIds = netResult.stdout.trim().split('\\n').filter(Boolean);\n if (netIds.length > 0) {\n await execaFn('docker', ['network', 'rm', ...netIds], { env, reject: false });\n console.log(chalk.green(` ✓ Removed ${netIds.length} orphaned network(s)`));\n }\n\n if (containerIds.length === 0 && volNames.length === 0 && netIds.length === 0) {\n console.log(chalk.dim(' Nothing to remove.'));\n }\n }\n return;\n }\n\n console.log(chalk.bold('\\nInstalled projects:'));\n for (const inst of installations) {\n const isCurrent = inst.name === lastProject;\n console.log(\n ` ${isCurrent ? chalk.green('▶') : ' '} ${chalk.cyan(inst.name)} ${chalk.dim(inst.path ?? '(path unknown)')}`,\n );\n }\n\n console.log(\n chalk.yellow(\n '\\n ⚠ CAUTION: This is a clean uninstall. All Brewnet services,\\n' +\n ' containers, volumes, and project files will be removed.\\n',\n ),\n );\n\n // --- Build target list ---\n // Use wizard state path first, then discovered filesystem path\n const projectPath = state?.projectPath ?? discoveredPath ?? null;\n const targets = buildUninstallTargets(projectPath, {\n keepData: options.keepData,\n keepConfig: options.keepConfig,\n });\n\n console.log(chalk.bold('\\nThe following will be removed:'));\n printTargets(targets);\n\n if (!options.keepData) {\n console.log(\n chalk.yellow(\n '\\n ⚠ WARNING: Database and file server data will be permanently deleted.',\n ),\n );\n console.log(chalk.dim(' Use --keep-data to preserve volumes.'));\n }\n\n // --- Dry-run: stop here ---\n if (options.dryRun) {\n console.log(chalk.dim('\\nDry-run complete. No changes made.'));\n return;\n }\n\n // --- Confirmation prompt (skip with --force) ---\n if (!options.force) {\n let confirmed = false;\n try {\n confirmed = await confirm({\n message: chalk.red('Proceed with uninstall? This cannot be undone.'),\n default: false,\n });\n } catch {\n // Ctrl+C or non-interactive\n console.log(chalk.dim('\\nAborted.'));\n return;\n }\n\n if (!confirmed) {\n console.log(chalk.dim('Aborted.'));\n return;\n }\n }\n\n // --- Run uninstall ---\n console.log('');\n const result = await runUninstall({\n keepData: options.keepData,\n keepConfig: options.keepConfig,\n force: options.force,\n projectPath: projectPath ?? undefined,\n projectName: lastProject || undefined,\n });\n\n // --- Display results ---\n for (const item of result.removed) {\n console.log(` ${chalk.green('✓')} Removed: ${item}`);\n }\n for (const item of result.skipped) {\n console.log(` ${chalk.dim('○')} Skipped: ${chalk.dim(item)}`);\n }\n for (const err of result.errors) {\n console.log(` ${chalk.red('✗')} Error: ${chalk.red(err)}`);\n }\n\n if (result.success) {\n console.log(chalk.green('\\n✓ Uninstall complete.\\n'));\n } else {\n console.log(chalk.yellow('\\n⚠ Uninstall completed with errors.\\n'));\n process.exitCode = 1;\n }\n\n // --- Cloudflare Tunnel notice (context-aware) ---\n const tunnelMode = state?.domain?.cloudflare?.tunnelMode;\n if (tunnelMode === 'named') {\n const tunnelName = state?.domain?.cloudflare?.tunnelName ?? '';\n const zoneName = state?.domain?.cloudflare?.zoneName ?? '';\n console.log(chalk.yellow(' ⚠ Cloudflare Named Tunnel 리소스가 남아있습니다.'));\n console.log(chalk.dim(' API 토큰이 저장되지 않아 자동 삭제가 불가합니다.'));\n console.log(chalk.dim(' 아래 항목을 CF 대시보드에서 수동 삭제하세요:\\n'));\n console.log(chalk.dim(' 1. 터널 삭제:'));\n console.log(chalk.dim(' → https://one.dash.cloudflare.com → Networks → Tunnels'));\n if (tunnelName) {\n console.log(chalk.dim(` → \"${tunnelName}\" 선택 → Delete`));\n }\n if (zoneName) {\n console.log(chalk.dim(' 2. DNS CNAME 레코드 삭제:'));\n console.log(chalk.dim(` → ${zoneName} → DNS → Records`));\n console.log(chalk.dim(' → *.cfargotunnel.com 을 가리키는 CNAME 삭제'));\n }\n console.log(chalk.dim('\\n 삭제하지 않으면 inactive 상태로 남습니다.'));\n console.log();\n } else if (tunnelMode === 'quick') {\n console.log(chalk.dim(' Quick Tunnel은 계정 연동이 없어 CF 측 정리가 필요 없습니다.'));\n console.log();\n }\n\n // --- Re-install hint ---\n console.log(\n chalk.dim(\n ' To reinstall, run:\\n' +\n ` ${chalk.bold('curl -fsSL https://raw.githubusercontent.com/claude-code-expert/brewnet/main/install.sh | bash')}`,\n ),\n );\n },\n );\n}\n","/**\n * brewnet domain — Cloudflare Tunnel management commands.\n *\n * Subcommands:\n * brewnet domain connect — Attach a domain to an existing tunnel (or migrate Quick→Named)\n * brewnet domain tunnel status — Query tunnel health and service accessibility\n * brewnet domain tunnel restart — Restart the cloudflared container\n *\n * @module commands/domain\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { input, select } from '@inquirer/prompts';\nimport Dockerode from 'dockerode';\nimport type { WizardState } from '@brewnet/shared';\nimport {\n verifyToken,\n getAccounts,\n getZones,\n createTunnel,\n configureTunnelIngress,\n createDnsRecord,\n getTunnelHealth,\n deleteTunnel,\n getActiveServiceRoutes,\n} from '../services/cloudflare-client.js';\nimport { TunnelLogger } from '../utils/tunnel-logger.js';\nimport { QuickTunnelManager } from '../services/quick-tunnel.js';\nimport { loadState, saveState, getLastProject } from '../wizard/state.js';\nimport { DomainManager } from '../services/domain-manager.js';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Loads the wizard state for the most recently initialized brewnet project. */\nfunction loadCurrentState(): WizardState | null {\n const lastProject = getLastProject();\n if (!lastProject) return null;\n return loadState(lastProject);\n}\n\n// ---------------------------------------------------------------------------\n// Register command\n// ---------------------------------------------------------------------------\n\n/**\n * Registers all `brewnet domain` subcommands on the given Commander program.\n *\n * Subcommands added:\n * - `domain connect` — Attach a domain / migrate Quick→Named Tunnel\n * - `domain tunnel status` — Query tunnel health and service reachability\n * - `domain tunnel restart` — Restart the cloudflared container\n */\nexport function registerDomainCommand(program: Command): void {\n const domain = program\n .command('domain')\n .description('Cloudflare Tunnel domain management');\n\n // ── domain connect ──────────────────────────────────────────────────────\n\n domain\n .command('connect')\n .argument('[app]', 'Name of the local app/service to connect (required when using --domain)')\n .option('--domain <hostname>', 'Target external hostname (e.g., my-api.yourdomain.com)')\n .option('--force', 'Overwrite existing CNAME record if conflict detected')\n .description('도메인을 기존 터널에 연결합니다 (Quick Tunnel → Named Tunnel 마이그레이션 또는 개별 앱 외부 도메인 연결)')\n .helpOption('-h, --help', 'Show help information')\n .addHelpText('after', `\nExamples:\n $ brewnet domain connect Migrate Quick Tunnel → Named Tunnel\n $ brewnet domain connect my-api --domain my-api.example.com Connect a specific app to an external domain\n $ brewnet domain connect my-api --domain my-api.example.com --force Overwrite existing CNAME\n\nPrerequisites:\n - A Cloudflare API Token with Zone:Read, DNS:Edit, Tunnel:Edit permissions\n - An existing brewnet installation (run \\`brewnet init\\` first)\n\nWhat this command does:\n With --domain (NEW):\n Connects a specific local app to an external domain via Cloudflare Tunnel.\n Creates DNS CNAME, configures ingress, and adds Traefik routing labels.\n\n Without --domain (legacy):\n Path A (Quick Tunnel → Named Tunnel):\n Creates a new Named Tunnel, configures ingress rules, creates DNS CNAME records,\n stops the old Quick Tunnel container, and updates the project state.\n\n Path B (Named Tunnel, no DNS yet):\n Reuses the existing tunnel, selects a Cloudflare zone, configures ingress,\n and creates DNS CNAME records for all active services.\n`)\n .action(async (app: string | undefined, opts: { domain?: string; force?: boolean }) => {\n // NEW: If --domain is provided, use DomainManager for external domain connection\n if (opts.domain) {\n if (!app) {\n console.error(chalk.red(' 앱 이름이 필요합니다. 예: brewnet domain connect my-api --domain my-api.example.com'));\n process.exit(1);\n }\n await handleDomainConnect(app, opts.domain, opts.force ?? false);\n return;\n }\n\n // LEGACY: Original Quick→Named migration flow (no --domain option)\n const tunnelLogger = new TunnelLogger();\n\n const state = loadCurrentState();\n if (!state) {\n console.error(chalk.red(' 설치된 brewnet 프로젝트를 찾을 수 없습니다.'));\n console.error(chalk.dim(' 먼저 `brewnet init`을 실행하세요.'));\n process.exit(1);\n }\n\n const { tunnelMode } = state.domain.cloudflare;\n\n if (tunnelMode === 'none') {\n console.error(chalk.red(' 이 프로젝트는 터널 없이 시작되었습니다.'));\n console.error(chalk.dim(' `brewnet init`을 다시 실행하세요.'));\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.bold.cyan(' brewnet domain connect'));\n console.log(chalk.dim(` 현재 모드: ${tunnelMode}`));\n console.log();\n\n // Prompt for CF API token\n let apiToken = await input({\n message: 'Cloudflare API Token을 입력하세요',\n default: '',\n validate: (v) => v.trim().length > 0 ? true : 'API Token이 필요합니다',\n });\n apiToken = apiToken.trim();\n\n // Verify token\n const verifySpinner = ora('API 토큰 검증 중...').start();\n try {\n const result = await verifyToken(apiToken);\n if (!result.valid) {\n verifySpinner.fail(chalk.red('유효하지 않은 API 토큰입니다. [BN004]'));\n process.exit(2);\n }\n verifySpinner.succeed(\n chalk.green('토큰 검증 완료') +\n (result.email ? chalk.dim(` (${result.email})`) : ''),\n );\n } catch {\n verifySpinner.fail(chalk.red('토큰 검증 실패'));\n process.exit(2);\n }\n console.log();\n\n // Auto-detect account\n const accountsSpinner = ora('Cloudflare 계정 조회 중...').start();\n const accounts = await getAccounts(apiToken).catch(() => []);\n accountsSpinner.stop();\n\n let selectedAccountId: string;\n if (accounts.length === 0) {\n const manual = await input({\n message: 'Cloudflare Account ID를 입력하세요',\n validate: (v) => v.trim().length > 0 ? true : 'Account ID가 필요합니다',\n });\n selectedAccountId = manual.trim();\n } else if (accounts.length === 1) {\n selectedAccountId = accounts[0].id;\n console.log(chalk.dim(` 계정: ${accounts[0].name} (자동 선택)`));\n } else {\n selectedAccountId = await select<string>({\n message: '계정을 선택하세요',\n choices: accounts.map((a) => ({ name: a.name, value: a.id })),\n });\n }\n console.log();\n\n // Prompt for zone (domain)\n const zonesSpinner = ora('DNS 존 조회 중...').start();\n const zones = await getZones(apiToken).catch(() => []);\n zonesSpinner.stop();\n\n const activeZones = zones.filter((z) => z.status === 'active');\n if (activeZones.length === 0) {\n console.error(chalk.red(' 활성 도메인이 없습니다. domains.cloudflare.com에서 도메인을 등록해주세요.'));\n process.exit(4);\n }\n\n let selectedZoneId: string;\n let selectedZoneName: string;\n\n if (activeZones.length === 1) {\n selectedZoneId = activeZones[0].id;\n selectedZoneName = activeZones[0].name;\n console.log(chalk.dim(` 도메인: ${selectedZoneName} (자동 선택)`));\n } else {\n selectedZoneId = await select<string>({\n message: '도메인(존)을 선택하세요',\n choices: activeZones.map((z) => ({ name: z.name, value: z.id })),\n });\n selectedZoneName = activeZones.find((z) => z.id === selectedZoneId)?.name ?? selectedZoneId;\n }\n console.log();\n\n let updatedState = structuredClone(state);\n\n if (tunnelMode === 'quick') {\n // Path A: Quick Tunnel → Named Tunnel migration\n updatedState = await domainConnectPathA(\n updatedState, apiToken, selectedAccountId, selectedZoneId, selectedZoneName, tunnelLogger,\n );\n } else if (tunnelMode === 'named' && !state.domain.cloudflare.zoneId) {\n // Path B: Named Tunnel without DNS — attach domain\n updatedState = await domainConnectPathB(\n updatedState, apiToken, selectedAccountId, selectedZoneId, selectedZoneName, tunnelLogger,\n );\n } else {\n // Path C: Named Tunnel with existing domain — re-sync\n updatedState = await domainConnectPathC(\n updatedState, apiToken, selectedAccountId, selectedZoneId, selectedZoneName, tunnelLogger,\n );\n }\n\n // Clear API token from state (security)\n updatedState.domain.cloudflare.apiToken = '';\n\n // Log DOMAIN_CONNECT event\n tunnelLogger.log({\n event: 'DOMAIN_CONNECT',\n tunnelMode: 'named',\n tunnelId: updatedState.domain.cloudflare.tunnelId || undefined,\n tunnelName: updatedState.domain.cloudflare.tunnelName || undefined,\n domain: selectedZoneName,\n detail: 'Domain connected successfully',\n });\n\n // Persist state\n saveState(updatedState);\n\n // Display service URLs\n const routes = getActiveServiceRoutes(updatedState);\n console.log(chalk.bold.green(' 도메인 연결 완료!'));\n console.log();\n if (routes.length > 0) {\n console.log(chalk.bold(' 서비스 주소:'));\n for (const route of routes) {\n console.log(chalk.dim(` https://${route.subdomain}.${selectedZoneName}`));\n }\n console.log();\n }\n });\n\n // ── domain disconnect ─────────────────────────────────────────────────\n\n domain\n .command('disconnect')\n .argument('<app>', 'Name of the app to disconnect')\n .description('앱의 외부 도메인 연결을 해제합니다')\n .helpOption('-h, --help', 'Show help information')\n .addHelpText('after', `\nExamples:\n $ brewnet domain disconnect my-api Remove external domain access for my-api\n\nWhat this command does:\n Removes the tunnel ingress rule, deletes the DNS CNAME record,\n removes Traefik external labels, and updates the project state.\n The local service continues running.\n`)\n .action(async (app: string) => {\n await handleDomainDisconnect(app);\n });\n\n // ── domain status ────────────────────────────────────────────────────\n\n domain\n .command('status')\n .argument('[app]', 'Optional app name (shows all if omitted)')\n .description('도메인 연결 상태를 조회합니다')\n .helpOption('-h, --help', 'Show help information')\n .action(async (app: string | undefined) => {\n await handleDomainStatus(app);\n });\n\n // ── domain list ──────────────────────────────────────────────────────\n\n domain\n .command('list')\n .description('모든 외부 도메인 연결을 나열합니다')\n .helpOption('-h, --help', 'Show help information')\n .action(async () => {\n await handleDomainList();\n });\n\n // ── domain tunnel ───────────────────────────────────────────────────────\n\n const tunnel = domain\n .command('tunnel')\n .description('터널 관리');\n\n // domain tunnel status\n tunnel\n .command('status')\n .description('터널 상태 조회 — 연결 수 및 서비스 접근 가능 여부 확인')\n .helpOption('-h, --help', 'Show help information')\n .addHelpText('after', `\nExamples:\n $ brewnet domain tunnel status Show tunnel health and per-service accessibility\n\nOutput includes:\n - Tunnel mode (quick / named)\n - Tunnel URL or domain\n - Number of active connections\n - Per-service HTTP reachability check (git, files, etc.)\n\nPrerequisites:\n - An existing brewnet installation with a tunnel configured\n`)\n .action(async () => {\n const state = loadCurrentState();\n if (!state) {\n console.error(chalk.red(' 설치된 brewnet 프로젝트를 찾을 수 없습니다.'));\n process.exit(1);\n }\n\n const { tunnelMode, quickTunnelUrl, tunnelId, tunnelName, accountId, tunnelToken } = state.domain.cloudflare;\n\n if (tunnelMode === 'none') {\n console.log(chalk.dim(' 터널이 설정되지 않았습니다.'));\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.bold(' 🚇 Tunnel Status'));\n console.log(chalk.dim(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));\n console.log();\n\n if (tunnelMode === 'quick') {\n const url = quickTunnelUrl || '(URL 없음 — 컨테이너 재시작 필요)';\n console.log(chalk.dim(` 모드: Quick Tunnel (임시, 재시작 시 URL 변경)`));\n console.log(chalk.dim(` URL: ${url}`));\n console.log();\n if (url !== '(URL 없음 — 컨테이너 재시작 필요)') {\n console.log(chalk.dim(' 서비스:'));\n console.log(chalk.dim(` FileBrowser ${url}/files`));\n console.log(chalk.dim(` Gitea ${url}/git`));\n console.log(chalk.dim(` Uptime Kuma ${url}/status`));\n }\n console.log();\n console.log(chalk.yellow(` ⚠️ 영구 URL을 원하면 \\`brewnet domain connect\\`를 실행하세요.`));\n console.log();\n return;\n }\n\n // Named Tunnel — query CF API\n if (!tunnelId || !accountId) {\n console.error(chalk.red(' 터널 정보가 없습니다. `brewnet domain connect`를 실행하세요.'));\n process.exit(2);\n }\n\n // Use tunnelToken as a proxy for API token (this is a limitation — named tunnels need apiToken for health check)\n // If we don't have apiToken persisted, we can still check the container status\n const spinner = ora('터널 상태 조회 중...').start();\n try {\n // Try to get health from CF API if we have a persisted token; otherwise show container status only\n const apiTokenForHealth = state.domain.cloudflare.apiToken;\n if (apiTokenForHealth) {\n const health = await getTunnelHealth(apiTokenForHealth, accountId, tunnelId);\n spinner.stop();\n console.log(chalk.dim(` 터널: ${tunnelName || tunnelId}`));\n console.log(chalk.dim(` ID: ${tunnelId}`));\n const statusIcon = health.status === 'healthy' ? chalk.green('✅ healthy') : chalk.yellow(`⚠️ ${health.status}`);\n console.log(` 상태: ${statusIcon} (커넥터 ${health.connectorCount}개)`);\n if (state.domain.cloudflare.zoneName) {\n console.log(chalk.dim(` 도메인: ${state.domain.cloudflare.zoneName}`));\n }\n console.log(chalk.dim(` 확인: ${new Date().toISOString()}`));\n } else {\n spinner.stop();\n console.log(chalk.dim(` 터널: ${tunnelName || tunnelId}`));\n console.log(chalk.dim(` ID: ${tunnelId}`));\n console.log(chalk.yellow(' 상태: (API 토큰 없음 — 컨테이너 상태만 확인 가능)'));\n if (state.domain.cloudflare.zoneName) {\n console.log(chalk.dim(` 도메인: ${state.domain.cloudflare.zoneName}`));\n }\n }\n\n // Show service URLs from routes\n const routes = getActiveServiceRoutes(state);\n if (routes.length > 0 && state.domain.cloudflare.zoneName) {\n console.log();\n console.log(chalk.dim(' 서비스:'));\n for (const route of routes) {\n console.log(chalk.dim(` ${route.subdomain.padEnd(12)} https://${route.subdomain}.${state.domain.cloudflare.zoneName}`));\n }\n }\n console.log();\n } catch (err) {\n spinner.fail(chalk.red('터널 상태 조회 실패'));\n console.error(chalk.dim(` 오류: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(2);\n }\n\n void tunnelToken; // used in container operations\n });\n\n // domain tunnel restart\n tunnel\n .command('restart')\n .description('cloudflared 컨테이너를 재시작하고 터널 재연결을 확인합니다')\n .helpOption('-h, --help', 'Show help information')\n .addHelpText('after', `\nExamples:\n $ brewnet domain tunnel restart Restart the cloudflared container and verify reconnection\n\nWhen to use:\n - After updating tunnel configuration\n - When the tunnel shows as disconnected in \\`brewnet domain tunnel status\\`\n - After changing DNS records or ingress rules\n\nWhat this command does:\n Stops and removes the existing cloudflared container, recreates it with the\n stored tunnel token, waits for the tunnel to reconnect, then reports the result.\n`)\n .action(async () => {\n const tunnelLogger = new TunnelLogger();\n const state = loadCurrentState();\n if (!state) {\n console.error(chalk.red(' 설치된 brewnet 프로젝트를 찾을 수 없습니다.'));\n process.exit(1);\n }\n\n const { tunnelMode, tunnelId, accountId } = state.domain.cloudflare;\n\n if (tunnelMode === 'none') {\n console.log(chalk.dim(' 터널이 설정되지 않았습니다.'));\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.bold(' 🔄 Cloudflare 터널을 재시작합니다...'));\n console.log();\n\n const docker = new Dockerode();\n const containerName = 'brewnet-cloudflared';\n\n // Stop container\n const stopSpinner = ora(` ${containerName} 컨테이너 중지 중...`).start();\n try {\n const container = docker.getContainer(containerName);\n await container.stop({ t: 10 });\n stopSpinner.succeed(chalk.green(' 중지 완료'));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('not running')) {\n stopSpinner.succeed(chalk.dim(' (이미 중지됨)'));\n } else {\n stopSpinner.fail(chalk.red(' 컨테이너 중지 실패'));\n console.error(chalk.dim(` 오류: ${msg}`));\n process.exit(2);\n }\n }\n\n // Start container\n const startSpinner = ora(` ${containerName} 컨테이너 시작 중...`).start();\n try {\n const container = docker.getContainer(containerName);\n await container.start();\n startSpinner.succeed(chalk.green(' 시작 완료'));\n } catch (err) {\n startSpinner.fail(chalk.red(' 컨테이너 시작 실패'));\n console.error(chalk.dim(` 오류: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(2);\n }\n\n // For Quick Tunnel, re-parse container logs for new URL\n if (tunnelMode === 'quick') {\n const urlSpinner = ora(' 새 Quick Tunnel URL 캡처 중...').start();\n try {\n const qtManager = new QuickTunnelManager(tunnelLogger);\n const container = docker.getContainer('brewnet-tunnel-quick');\n const newUrl = await captureQuickTunnelUrl(container);\n\n const updatedState = structuredClone(state);\n updatedState.domain.cloudflare.quickTunnelUrl = newUrl;\n updatedState.domain.name = new URL(newUrl).hostname;\n saveState(updatedState);\n\n urlSpinner.succeed(chalk.green(` 새 URL: ${newUrl}`));\n void qtManager; // keep reference\n } catch {\n urlSpinner.warn(' URL 캡처 실패 — 컨테이너 로그를 직접 확인하세요');\n }\n } else if (tunnelMode === 'named' && tunnelId && accountId && state.domain.cloudflare.apiToken) {\n // Poll CF API for healthy status\n const healthSpinner = ora(' 터널 연결 확인 중... (최대 30초)').start();\n try {\n await waitForHealthy(state.domain.cloudflare.apiToken, accountId, tunnelId, 30_000);\n healthSpinner.succeed(chalk.green(' 터널이 정상 연결되었습니다 (healthy)'));\n } catch {\n healthSpinner.warn(' 터널 상태 확인 실패 (30초 초과)');\n }\n } else {\n // Just wait a few seconds for container to initialize\n await new Promise((resolve) => setTimeout(resolve, 3_000));\n console.log(chalk.green(' 터널 재시작 완료'));\n }\n\n tunnelLogger.log({\n event: 'RESTART',\n tunnelMode: tunnelMode as 'quick' | 'named',\n tunnelId: tunnelId || undefined,\n tunnelName: state.domain.cloudflare.tunnelName || undefined,\n detail: `Tunnel container restarted: ${containerName}`,\n });\n\n console.log();\n\n // Display current service URLs\n const routes = getActiveServiceRoutes(state);\n if (routes.length > 0) {\n const baseUrl = tunnelMode === 'quick'\n ? state.domain.cloudflare.quickTunnelUrl\n : state.domain.cloudflare.zoneName\n ? `https://*.${state.domain.cloudflare.zoneName}`\n : '';\n\n if (baseUrl) {\n console.log(chalk.bold(' 서비스 주소:'));\n if (tunnelMode === 'quick') {\n for (const route of routes) {\n const path = getQuickTunnelPath(route.subdomain);\n if (path) console.log(chalk.dim(` ${baseUrl}${path}`));\n }\n } else {\n for (const route of routes) {\n console.log(chalk.dim(` https://${route.subdomain}.${state.domain.cloudflare.zoneName}`));\n }\n }\n console.log();\n }\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// domain connect Path A — Quick Tunnel → Named Tunnel migration\n// ---------------------------------------------------------------------------\n\nasync function domainConnectPathA(\n state: WizardState,\n apiToken: string,\n accountId: string,\n zoneId: string,\n zoneName: string,\n tunnelLogger: TunnelLogger,\n): Promise<WizardState> {\n const updated = structuredClone(state);\n\n console.log(chalk.bold(' Path A: Quick Tunnel → Named Tunnel 마이그레이션'));\n console.log();\n\n // Create new Named Tunnel\n const tunnelName = updated.domain.cloudflare.tunnelName || updated.projectName;\n const createSpinner = ora('Named Tunnel 생성 중...').start();\n let createdTunnelId = '';\n\n try {\n const result = await createTunnel(apiToken, accountId, tunnelName);\n createdTunnelId = result.tunnelId;\n updated.domain.cloudflare.tunnelId = result.tunnelId;\n updated.domain.cloudflare.tunnelToken = result.tunnelToken;\n updated.domain.cloudflare.tunnelName = tunnelName;\n updated.domain.cloudflare.accountId = accountId;\n createSpinner.succeed(chalk.green(`터널 생성됨: ${tunnelName}`));\n\n tunnelLogger.log({\n event: 'CREATE',\n tunnelMode: 'named',\n tunnelId: result.tunnelId,\n tunnelName,\n domain: zoneName,\n detail: 'Named tunnel created via domain connect (migrating from Quick Tunnel)',\n });\n } catch (err) {\n createSpinner.fail(chalk.red('터널 생성 실패'));\n throw err;\n }\n console.log();\n\n // Configure ingress\n const routes = getActiveServiceRoutes(updated);\n if (routes.length > 0) {\n const ingressSpinner = ora('인그레스 설정 중...').start();\n try {\n await configureTunnelIngress(apiToken, accountId, createdTunnelId, zoneName, routes);\n ingressSpinner.succeed(chalk.green(`인그레스 설정 완료 (${routes.length}개 서비스)`));\n } catch (err) {\n ingressSpinner.fail(chalk.red('인그레스 설정 실패'));\n await deleteTunnel(apiToken, accountId, createdTunnelId).catch(() => {/* best-effort */});\n throw err;\n }\n\n // Create DNS records\n const dnsSpinner = ora('DNS 레코드 생성 중...').start();\n const created: string[] = [];\n for (const route of routes) {\n try {\n await createDnsRecord(apiToken, zoneId, createdTunnelId, route.subdomain, zoneName);\n created.push(route.subdomain);\n } catch {\n // Non-fatal — best-effort\n }\n }\n dnsSpinner.succeed(chalk.green(`DNS 레코드 생성 완료 (${created.length}/${routes.length}개)`));\n console.log();\n }\n\n // Stop Quick Tunnel container\n const stopSpinner = ora('Quick Tunnel 중지 중...').start();\n try {\n const qtManager = new QuickTunnelManager(tunnelLogger);\n await qtManager.stop();\n stopSpinner.succeed(chalk.green('Quick Tunnel 중지 완료'));\n } catch {\n stopSpinner.warn('Quick Tunnel 중지 실패 (수동으로 중지하세요: docker stop brewnet-tunnel-quick)');\n }\n console.log();\n\n // Update state\n updated.domain.provider = 'tunnel';\n updated.domain.cloudflare.tunnelMode = 'named';\n updated.domain.cloudflare.zoneId = zoneId;\n updated.domain.cloudflare.zoneName = zoneName;\n updated.domain.name = zoneName;\n updated.domain.cloudflare.quickTunnelUrl = '';\n\n return updated;\n}\n\n// ---------------------------------------------------------------------------\n// domain connect Path B — Named Tunnel (no DNS) → attach domain\n// ---------------------------------------------------------------------------\n\nasync function domainConnectPathB(\n state: WizardState,\n apiToken: string,\n accountId: string,\n zoneId: string,\n zoneName: string,\n _tunnelLogger: TunnelLogger,\n): Promise<WizardState> {\n const updated = structuredClone(state);\n\n console.log(chalk.bold(' Path B: 기존 터널에 도메인 연결'));\n console.log();\n\n const tunnelId = updated.domain.cloudflare.tunnelId;\n if (!tunnelId) {\n throw new Error('터널 ID가 없습니다. `brewnet init`을 다시 실행하세요.');\n }\n\n const routes = getActiveServiceRoutes(updated);\n\n // Configure ingress\n if (routes.length > 0) {\n const ingressSpinner = ora('인그레스 규칙 설정 중...').start();\n try {\n await configureTunnelIngress(apiToken, accountId, tunnelId, zoneName, routes);\n ingressSpinner.succeed(chalk.green(`인그레스 설정 완료 (${routes.length}개 서비스)`));\n } catch (err) {\n ingressSpinner.fail(chalk.red('인그레스 설정 실패'));\n throw err;\n }\n console.log();\n\n // Create DNS records (upsert)\n const dnsSpinner = ora('DNS CNAME 레코드 생성 중...').start();\n const created: string[] = [];\n for (const route of routes) {\n try {\n await createDnsRecord(apiToken, zoneId, tunnelId, route.subdomain, zoneName);\n created.push(route.subdomain);\n } catch {\n // Non-fatal — record may already exist\n }\n }\n dnsSpinner.succeed(chalk.green(`DNS 레코드 완료 (${created.length}/${routes.length}개)`));\n console.log();\n }\n\n updated.domain.cloudflare.zoneId = zoneId;\n updated.domain.cloudflare.zoneName = zoneName;\n updated.domain.name = zoneName;\n\n return updated;\n}\n\n// ---------------------------------------------------------------------------\n// domain connect Path C — Named Tunnel with existing domain — re-sync\n// ---------------------------------------------------------------------------\n\nasync function domainConnectPathC(\n state: WizardState,\n apiToken: string,\n accountId: string,\n zoneId: string,\n zoneName: string,\n _tunnelLogger: TunnelLogger,\n): Promise<WizardState> {\n const updated = structuredClone(state);\n\n console.log(chalk.bold(' Path C: 인그레스 + DNS 재동기화'));\n console.log();\n\n const tunnelId = updated.domain.cloudflare.tunnelId;\n if (!tunnelId) {\n throw new Error('터널 ID가 없습니다. `brewnet init`을 다시 실행하세요.');\n }\n\n const routes = getActiveServiceRoutes(updated);\n\n // Re-configure ingress (upsert)\n if (routes.length > 0) {\n const ingressSpinner = ora('인그레스 재동기화 중...').start();\n try {\n await configureTunnelIngress(apiToken, accountId, tunnelId, zoneName, routes);\n ingressSpinner.succeed(chalk.green('인그레스 재동기화 완료'));\n } catch (err) {\n ingressSpinner.warn(chalk.yellow(`인그레스 재동기화 실패: ${err instanceof Error ? err.message : String(err)}`));\n }\n console.log();\n\n // Upsert DNS records\n const dnsSpinner = ora('DNS 레코드 재동기화 중...').start();\n let count = 0;\n for (const route of routes) {\n try {\n await createDnsRecord(apiToken, zoneId, tunnelId, route.subdomain, zoneName);\n count++;\n } catch {\n // Non-fatal — upsert pattern\n }\n }\n dnsSpinner.succeed(chalk.green(`DNS 재동기화 완료 (${count}/${routes.length}개)`));\n console.log();\n }\n\n updated.domain.cloudflare.zoneId = zoneId;\n updated.domain.cloudflare.zoneName = zoneName;\n updated.domain.name = zoneName;\n\n return updated;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function waitForHealthy(\n apiToken: string,\n accountId: string,\n tunnelId: string,\n timeoutMs: number,\n): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const health = await getTunnelHealth(apiToken, accountId, tunnelId).catch(() => null);\n if (health?.status === 'healthy' && health.connectorCount > 0) return;\n await new Promise((r) => setTimeout(r, 2_000));\n }\n throw new Error('Timeout');\n}\n\nasync function captureQuickTunnelUrl(container: Dockerode.Container): Promise<string> {\n const URL_REGEX = /https?:\\/\\/([\\w-]+\\.trycloudflare\\.com)/i;\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error('URL capture timeout')), 30_000);\n\n container.logs({ follow: true, stdout: true, stderr: true, tail: 0 }, (err, stream) => {\n if (err || !stream) {\n clearTimeout(timer);\n return reject(err ?? new Error('No stream'));\n }\n stream.on('data', (chunk: Buffer) => {\n const text = chunk.toString('utf8');\n const match = URL_REGEX.exec(text);\n if (match) {\n clearTimeout(timer);\n stream.destroy();\n resolve(match[0].startsWith('http') ? match[0] : `https://${match[1]}`);\n }\n });\n stream.on('error', (e) => { clearTimeout(timer); reject(e); });\n });\n });\n}\n\n/** Returns the URL path prefix used for a Quick Tunnel subdomain (e.g. `gitea` → `/git`). */\nfunction getQuickTunnelPath(subdomain: string): string {\n const map: Record<string, string> = {\n gitea: '/git',\n filebrowser: '/files',\n 'uptime-kuma': '/status',\n grafana: '/grafana',\n pgadmin: '/pgadmin',\n };\n return map[subdomain] ?? '';\n}\n\n// ---------------------------------------------------------------------------\n// handleDomainConnect — External domain connection via DomainManager\n// ---------------------------------------------------------------------------\n\nasync function handleDomainConnect(\n appName: string,\n domainHostname: string,\n force: boolean,\n): Promise<void> {\n const tunnelLogger = new TunnelLogger();\n\n const lastProject = getLastProject();\n if (!lastProject) {\n console.error(chalk.red(' 설치된 brewnet 프로젝트를 찾을 수 없습니다.'));\n console.error(chalk.dim(' 먼저 `brewnet init`을 실행하세요.'));\n process.exit(1);\n }\n\n // Parse subdomain and domain from hostname\n const dotIndex = domainHostname.indexOf('.');\n if (dotIndex === -1) {\n console.error(chalk.red(` 잘못된 도메인 형식입니다: ${domainHostname}`));\n console.error(chalk.dim(' 예: my-api.yourdomain.com'));\n process.exit(1);\n }\n const subdomain = domainHostname.substring(0, dotIndex);\n const domain = domainHostname.substring(dotIndex + 1);\n\n console.log();\n console.log(chalk.bold.cyan(' brewnet domain connect'));\n console.log(chalk.dim(` 앱: ${appName} → ${domainHostname}`));\n console.log();\n\n let manager: DomainManager;\n try {\n manager = new DomainManager(lastProject);\n } catch (err) {\n console.error(chalk.red(` 프로젝트 로드 실패: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(1);\n }\n\n const state = manager.getState();\n\n // Check if this is a Scenario C (CNAME-only) setup\n if (!state.domain.cloudflare.zoneId && state.domain.cloudflare.tunnelId) {\n // Scenario C: CNAME-only mode — show manual instructions\n const tunnelUuid = state.domain.cloudflare.tunnelId;\n console.log(chalk.bold.yellow(' 📋 Scenario C: CNAME 수동 설정 필요'));\n console.log();\n console.log(chalk.dim(' 도메인 네임서버가 Cloudflare에 위임되지 않았습니다.'));\n console.log(chalk.dim(' DNS 제공자에서 아래 CNAME 레코드를 직접 추가하세요:'));\n console.log();\n console.log(chalk.bold(` 이름: ${subdomain}`));\n console.log(chalk.bold(` 유형: CNAME`));\n console.log(chalk.bold(` 값: ${tunnelUuid}.cfargotunnel.com`));\n console.log();\n console.log(chalk.dim(' 주요 DNS 제공자 설정 방법:'));\n console.log(chalk.dim(' GoDaddy: DNS 관리 → 레코드 추가 → CNAME'));\n console.log(chalk.dim(' Namecheap: 고급 DNS → 새 레코드 → CNAME'));\n console.log(chalk.dim(' 가비아: DNS 관리 → 레코드 추가 → CNAME'));\n console.log(chalk.dim(' Cafe24: DNS 관리 → CNAME 추가'));\n console.log();\n return;\n }\n\n // Media streaming ToS warning (FR-012)\n const mediaServices = ['jellyfin', 'media', 'plex', 'emby'];\n if (mediaServices.includes(appName.toLowerCase())) {\n console.log(chalk.yellow(' ⚠️ 주의: Cloudflare는 대용량 미디어 스트리밍(비디오 등)을'));\n console.log(chalk.yellow(' 프록시를 통해 전송하는 것을 서비스 약관(ToS)에서 제한합니다.'));\n console.log(chalk.yellow(' 미디어 서비스 외부 연결은 ToS 위반 위험이 있습니다.'));\n console.log();\n }\n\n const spinner = ora('외부 도메인 연결 중...').start();\n\n const result = await manager.connect(appName, subdomain, domain, { force });\n\n spinner.stop();\n\n // Display step results\n for (const step of result.steps) {\n const icon = step.status === 'completed' ? chalk.green('✅')\n : step.status === 'skipped' ? chalk.yellow('⏭️')\n : chalk.red('❌');\n const duration = step.durationMs ? chalk.dim(` (${(step.durationMs / 1000).toFixed(1)}s)`) : '';\n const stepName = {\n health_check: 'Local health check',\n ingress_update: 'Tunnel ingress updated',\n dns_creation: 'DNS CNAME record created',\n traefik_labels: 'Traefik labels updated',\n dns_propagation: 'DNS propagation verified',\n }[step.step] ?? step.step;\n console.log(` ${icon} ${stepName}${duration}`);\n if (step.status === 'failed' && step.error) {\n console.log(chalk.dim(` ${step.error}`));\n }\n }\n console.log();\n\n if (result.success) {\n console.log(chalk.bold.green(` ✅ ${result.externalUrl} is live!`));\n\n tunnelLogger.log({\n event: 'DOMAIN_CONNECT',\n tunnelMode: 'named',\n tunnelId: state.domain.cloudflare.tunnelId || undefined,\n tunnelName: state.domain.cloudflare.tunnelName || undefined,\n domain: domainHostname,\n detail: `External domain connected: ${appName} → ${domainHostname}`,\n });\n } else {\n console.error(chalk.red(` ❌ 연결 실패: ${result.error}`));\n if (result.error === 'CNAME_CONFLICT') {\n console.log(chalk.dim(' 기존 CNAME을 덮어쓰려면 --force 옵션을 사용하세요:'));\n console.log(chalk.dim(` brewnet domain connect ${appName} --domain ${domainHostname} --force`));\n }\n process.exit(1);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// handleDomainDisconnect — T018-T021\n// ---------------------------------------------------------------------------\n\nasync function handleDomainDisconnect(appName: string): Promise<void> {\n const tunnelLogger = new TunnelLogger();\n const lastProject = getLastProject();\n if (!lastProject) {\n console.error(chalk.red(' 설치된 brewnet 프로젝트를 찾을 수 없습니다.'));\n process.exit(1);\n }\n\n let manager: DomainManager;\n try {\n manager = new DomainManager(lastProject);\n } catch (err) {\n console.error(chalk.red(` 프로젝트 로드 실패: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(1);\n }\n\n console.log();\n console.log(chalk.bold.cyan(' brewnet domain disconnect'));\n console.log(chalk.dim(` 앱: ${appName}`));\n console.log();\n\n const spinner = ora('외부 도메인 연결 해제 중...').start();\n const result = await manager.disconnect(appName);\n spinner.stop();\n\n for (const step of result.steps) {\n const icon = step.status === 'completed' ? chalk.green('✅') : chalk.red('❌');\n const stepName = {\n ingress_removal: 'Tunnel ingress rule removed',\n dns_deletion: 'DNS CNAME record deleted',\n traefik_cleanup: 'Traefik external labels removed',\n }[step.step] ?? step.step;\n console.log(` ${icon} ${stepName}`);\n if (step.status === 'failed' && step.error) {\n console.log(chalk.dim(` ${step.error}`));\n }\n }\n console.log();\n\n if (result.success) {\n console.log(chalk.green(` ✅ ${appName}의 외부 도메인 연결이 해제되었습니다.`));\n console.log(chalk.dim(` ℹ ${appName}은(는) 로컬에서 계속 실행 중입니다.`));\n\n tunnelLogger.log({\n event: 'DOMAIN_CONNECT',\n tunnelMode: 'named',\n detail: `External domain disconnected: ${appName} (${result.removedHostname})`,\n });\n } else {\n console.error(chalk.red(` ❌ 연결 해제 실패: ${result.error}`));\n process.exit(1);\n }\n console.log();\n}\n\n// ---------------------------------------------------------------------------\n// handleDomainStatus — T023-T026\n// ---------------------------------------------------------------------------\n\nasync function handleDomainStatus(appName?: string): Promise<void> {\n const lastProject = getLastProject();\n if (!lastProject) {\n console.error(chalk.red(' 설치된 brewnet 프로젝트를 찾을 수 없습니다.'));\n process.exit(1);\n }\n\n let manager: DomainManager;\n try {\n manager = new DomainManager(lastProject);\n } catch (err) {\n console.error(chalk.red(` 프로젝트 로드 실패: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(1);\n }\n\n const spinner = ora('도메인 상태 조회 중...').start();\n const statuses = await manager.status(appName);\n spinner.stop();\n\n if (statuses.length === 0) {\n console.log(chalk.dim(' 연결된 외부 도메인이 없습니다.'));\n console.log(chalk.dim(' `brewnet domain connect <app> --domain <hostname>` 명령으로 연결하세요.'));\n console.log();\n return;\n }\n\n console.log();\n for (const s of statuses) {\n const localIcon = s.local.healthy ? chalk.green('✅') : chalk.red('❌');\n const externalIcon = s.external.httpsReachable ? chalk.green('✅') : chalk.red('❌');\n const tunnelIcon = s.tunnel.status === 'healthy' ? chalk.green('✅')\n : s.tunnel.status === 'degraded' ? chalk.yellow('⚠️')\n : chalk.red('❌');\n const dnsIcon = s.external.dnsResolved ? chalk.green('✅') : chalk.red('❌');\n\n console.log(chalk.bold(` ${s.appName}`));\n console.log(` Local: ${s.local.url.padEnd(40)} ${localIcon}`);\n console.log(` External: ${s.external.url.padEnd(40)} ${externalIcon}${s.external.httpsReachable ? '' : ' unreachable'}`);\n console.log(` Tunnel: ${(s.tunnel.status).padEnd(40)} ${tunnelIcon} (${s.tunnel.connectorCount} connections)`);\n if (s.dns) {\n console.log(` DNS: CNAME → ${s.dns.content.padEnd(28)} ${dnsIcon}`);\n }\n console.log();\n }\n}\n\n// ---------------------------------------------------------------------------\n// handleDomainList — T028-T029\n// ---------------------------------------------------------------------------\n\nasync function handleDomainList(): Promise<void> {\n const lastProject = getLastProject();\n if (!lastProject) {\n console.error(chalk.red(' 설치된 brewnet 프로젝트를 찾을 수 없습니다.'));\n process.exit(1);\n }\n\n let manager: DomainManager;\n try {\n manager = new DomainManager(lastProject);\n } catch (err) {\n console.error(chalk.red(` 프로젝트 로드 실패: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(1);\n }\n\n const connections = manager.list();\n\n if (connections.length === 0) {\n console.log();\n console.log(chalk.dim(' 연결된 외부 도메인이 없습니다.'));\n console.log(chalk.dim(' `brewnet domain connect <app> --domain <hostname>` 명령으로 연결하세요.'));\n console.log();\n return;\n }\n\n console.log();\n console.log(chalk.bold(' External Domain Connections'));\n console.log(chalk.dim(' ─────────────────────────────────────────────────────────────'));\n\n // Simple table output\n const header = ` ${'App'.padEnd(12)} ${'External URL'.padEnd(40)} ${'Connected'}`;\n console.log(chalk.bold(header));\n console.log(chalk.dim(' ' + '─'.repeat(65)));\n\n for (const conn of connections) {\n const date = new Date(conn.connectedAt);\n const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;\n console.log(` ${conn.appName.padEnd(12)} ${`https://${conn.hostname}`.padEnd(40)} ${dateStr}`);\n }\n console.log();\n}\n","/**\n * brewnet create-app — App Scaffolding Command\n *\n * Scaffolds a full-stack application from one of 16 pre-built boilerplate\n * stacks hosted on the brewnet-boilerplate GitHub repository.\n *\n * Usage:\n * brewnet create-app <project-name> [--stack <STACK_ID>] [--database <DB_DRIVER>]\n *\n * Execution flow (10 steps):\n * 1. Pre-flight: Docker check + directory existence check\n * 2. Stack resolution: --stack flag OR interactive prompts\n * 3. Rust warning (if applicable)\n * 4. Clone: git clone --depth=1 -b stack/<id>\n * 5. Env: generate .env from .env.example with secure secrets\n * 6. Git reinit: clean history for the scaffolded project\n * 7. Start: docker compose up -d --build\n * 8. Health poll: GET /health until ready\n * 9. Verify: GET /api/hello + POST /api/echo\n * 10. Success box\n *\n * @module commands/create-app\n */\n\nimport { existsSync, rmSync, mkdirSync, appendFileSync } from 'node:fs';\nimport { resolve, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { select } from '@inquirer/prompts';\nimport {\n STACK_CATALOG,\n VALID_STACK_IDS,\n VALID_DB_DRIVERS,\n getStackById,\n getStacksByLanguage,\n} from '../config/stacks.js';\nimport {\n cloneStack,\n generateEnv,\n reinitGit,\n startContainers,\n pollHealth,\n verifyEndpoints,\n} from '../services/boilerplate-manager.js';\nimport { checkDocker } from '../services/system-checker.js';\nimport { BrewnetError } from '../utils/errors.js';\nimport type { StackEntry } from '@brewnet/shared';\n\n// ---------------------------------------------------------------------------\n// T010 — Command Registration\n// ---------------------------------------------------------------------------\n\nexport interface CreateAppCommandOptions {\n stack?: string;\n database?: string;\n}\n\n/**\n * Register the `brewnet create-app` command on the given Commander program.\n */\nexport function registerCreateAppCommand(program: Command): void {\n program\n .command('create-app <project-name>')\n .description('Scaffold a full-stack app from a pre-built boilerplate stack')\n .option(\n '--stack <STACK_ID>',\n `Pre-select a stack (skips interactive prompts). Valid IDs:\\n ${STACK_CATALOG.map((s) => s.id).join(', ')}`,\n )\n .option(\n '--database <DB_DRIVER>',\n 'Database driver: sqlite3 (default, no container), postgres, mysql',\n 'sqlite3',\n )\n .addHelpText(\n 'after',\n `\nExamples:\n $ brewnet create-app my-app # Interactive selection, SQLite\n $ brewnet create-app my-api --stack go-gin # Direct stack, SQLite\n $ brewnet create-app my-api --stack nodejs-express --database postgres\n`,\n )\n .action(async (projectName: string, options: CreateAppCommandOptions) => {\n await runCreateApp(projectName, options);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Audit Logging — Constitution Principle III compliance (C1)\n// ---------------------------------------------------------------------------\n\ninterface AuditEntry {\n timestamp: string;\n command: 'create-app';\n projectName: string;\n stackId: string;\n dbDriver: string;\n status: 'started' | 'success' | 'failed';\n elapsedMs?: number;\n error?: string;\n}\n\n/**\n * Append a JSONL audit log entry to ~/.brewnet/logs/create-app.log.\n * Errors are silently suppressed — audit logging MUST NOT break the command.\n */\nfunction writeAuditLog(entry: AuditEntry): void {\n try {\n const logDir = join(homedir(), '.brewnet', 'logs');\n mkdirSync(logDir, { recursive: true });\n appendFileSync(join(logDir, 'create-app.log'), JSON.stringify(entry) + '\\n', 'utf-8');\n } catch {\n // Audit logging failure must not affect command execution\n }\n}\n\n// ---------------------------------------------------------------------------\n// T011 — Interactive Stack Selection\n// ---------------------------------------------------------------------------\n\n/**\n * Present a two-step interactive selection:\n * 1. Select language (6 choices)\n * 2. Select framework (filtered by language)\n *\n * Returns the selected StackEntry.\n */\nasync function selectStackInteractively(): Promise<StackEntry> {\n const byLanguage = getStacksByLanguage();\n const languages = Object.keys(byLanguage);\n\n const selectedLanguage = await select<string>({\n message: 'Select a language',\n choices: languages.map((lang) => ({\n name: `${lang} (${byLanguage[lang]!.length} framework${byLanguage[lang]!.length > 1 ? 's' : ''})`,\n value: lang,\n })),\n });\n\n const frameworkChoices = byLanguage[selectedLanguage]!;\n const selectedStack = await select<StackEntry>({\n message: 'Select a framework',\n choices: frameworkChoices.map((stack) => ({\n name: `${stack.framework} ${chalk.dim(`(${stack.orm}, v${stack.version})`)}`,\n value: stack,\n })),\n });\n\n return selectedStack;\n}\n\n// ---------------------------------------------------------------------------\n// T012 — Main Command Handler (10-step flow)\n// ---------------------------------------------------------------------------\n\nasync function runCreateApp(\n projectName: string,\n options: CreateAppCommandOptions,\n): Promise<void> {\n const projectDir = resolve(projectName);\n const dbDriver = options.database ?? 'sqlite3';\n const startTime = Date.now();\n\n // Tracks whether clone completed — used by cleanup logic in catch + SIGINT\n let cloneSucceeded = false;\n // Tracks resolved stack ID for audit log (updated after interactive selection)\n let resolvedStackId = options.stack ?? '(interactive)';\n\n // ── U1: SIGINT handler — clean up partial directory on Ctrl+C ───────────\n const sigintHandler = (): void => {\n process.removeListener('SIGINT', sigintHandler);\n if (!cloneSucceeded && existsSync(projectDir)) {\n rmSync(projectDir, { recursive: true, force: true });\n console.log(chalk.yellow('\\n\\nAborted. Partial directory removed.'));\n } else {\n console.log(chalk.yellow('\\n\\nAborted.'));\n }\n writeAuditLog({\n timestamp: new Date().toISOString(),\n command: 'create-app',\n projectName,\n stackId: resolvedStackId,\n dbDriver,\n status: 'failed',\n elapsedMs: Date.now() - startTime,\n error: 'User aborted (SIGINT)',\n });\n process.exit(130);\n };\n process.on('SIGINT', sigintHandler);\n\n try {\n // ── US5: Pre-flight — Docker check ─────────────────────────────────────\n const dockerCheck = await checkDocker();\n if (dockerCheck.status !== 'pass') {\n throw BrewnetError.dockerNotRunning();\n }\n\n // ── US5: Pre-flight — directory existence ──────────────────────────────\n if (existsSync(projectDir)) {\n throw BrewnetError.directoryConflict(projectName);\n }\n\n // ── US2: Stack validation / US1: Interactive selection ─────────────────\n let stack: StackEntry;\n if (options.stack) {\n if (!VALID_STACK_IDS.has(options.stack)) {\n throw BrewnetError.resourceNotFound(\n `stack ID \"${options.stack}\"\\n\\n Valid IDs:\\n ${STACK_CATALOG.map((s) => s.id).join(', ')}`,\n );\n }\n stack = getStackById(options.stack)!;\n } else {\n stack = await selectStackInteractively();\n }\n resolvedStackId = stack.id;\n\n // ── US3: Database validation ────────────────────────────────────────────\n if (!VALID_DB_DRIVERS.includes(dbDriver as (typeof VALID_DB_DRIVERS)[number])) {\n throw BrewnetError.resourceNotFound(\n `database driver \"${dbDriver}\"\\n\\n Valid options: ${VALID_DB_DRIVERS.join(', ')}`,\n );\n }\n\n // ── C1: Audit log — operation started ──────────────────────────────────\n writeAuditLog({\n timestamp: new Date().toISOString(),\n command: 'create-app',\n projectName,\n stackId: stack.id,\n dbDriver,\n status: 'started',\n });\n\n // ── US4: Rust build warning ─────────────────────────────────────────────\n if (stack.buildSlow) {\n console.log(\n chalk.yellow(\n `\\n⚠ Rust Warning: First build may take 3-10 minutes due to Rust compilation.\\n` +\n ` Health check timeout extended to 10 minutes. Please be patient.\\n`,\n ),\n );\n }\n\n const healthTimeoutMs = stack.buildSlow ? 600_000 : 120_000;\n const backendPort = stack.isUnified ? 3000 : 8080;\n const baseUrl = `http://127.0.0.1:${backendPort}`;\n\n // ── Step 1: Clone ───────────────────────────────────────────────────\n const cloneSpinner = ora(`Cloning stack/${stack.id} from brewnet-boilerplate…`).start();\n try {\n await cloneStack(stack.id, projectDir);\n cloneSucceeded = true;\n cloneSpinner.succeed(`Cloned ${stack.id}`);\n } catch {\n cloneSpinner.fail('Clone failed');\n throw BrewnetError.cloneFailed(stack.id);\n }\n\n // ── Step 2: Generate .env ───────────────────────────────────────────\n const envSpinner = ora('Generating .env with secure credentials…').start();\n try {\n generateEnv(projectDir, stack.id, dbDriver);\n envSpinner.succeed('.env generated');\n } catch (err) {\n envSpinner.fail('.env generation failed');\n throw err;\n }\n\n // ── Step 3: Git reinit ──────────────────────────────────────────────\n const gitSpinner = ora('Initializing git repository…').start();\n try {\n await reinitGit(projectDir);\n gitSpinner.succeed('Git repository initialized');\n } catch (err) {\n gitSpinner.fail('Git initialization failed');\n throw err;\n }\n\n // ── Step 4: Start containers ────────────────────────────────────────\n const buildMsg = stack.buildSlow\n ? 'Building and starting containers (up to 10 min for Rust)…'\n : 'Building and starting containers…';\n const buildSpinner = ora(buildMsg).start();\n try {\n await startContainers(projectDir);\n buildSpinner.succeed('Containers started');\n } catch (err) {\n buildSpinner.fail('Container startup failed');\n // U2: Detect port-already-in-use from docker compose output (BN002)\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('address already in use') || msg.includes('port is already allocated')) {\n const portMatch = msg.match(/:(\\d+)/);\n const port = portMatch ? parseInt(portMatch[1]!, 10) : backendPort;\n throw BrewnetError.portConflict(port);\n }\n throw BrewnetError.buildFailed(msg.slice(0, 500));\n }\n\n // ── Step 5: Health polling with elapsed timer ───────────────────────\n const healthStart = Date.now();\n const healthSpinner = ora('Waiting for backend to become healthy… (0s elapsed)').start();\n const healthInterval = setInterval(() => {\n const elapsed = Math.round((Date.now() - healthStart) / 1000);\n healthSpinner.text = `Waiting for backend to become healthy… (${elapsed}s elapsed)`;\n }, 1000);\n\n let healthResult;\n try {\n healthResult = await pollHealth(baseUrl, healthTimeoutMs);\n } finally {\n clearInterval(healthInterval);\n }\n\n if (!healthResult.healthy) {\n healthSpinner.fail('Health check timed out');\n throw BrewnetError.healthCheckTimeout(healthTimeoutMs / 1000);\n }\n healthSpinner.succeed(\n `Backend healthy (${Math.round(healthResult.elapsedMs / 1000)}s)` +\n (healthResult.dbConnected === false ? chalk.yellow(' — DB not connected') : ''),\n );\n\n // ── Step 6: Verify API endpoints ────────────────────────────────────\n const verifySpinner = ora('Verifying API endpoints…').start();\n try {\n await verifyEndpoints(baseUrl);\n verifySpinner.succeed('API endpoints verified');\n } catch (err) {\n verifySpinner.fail('Endpoint verification failed');\n throw err;\n }\n\n // ── C1: Audit log — success ─────────────────────────────────────────\n writeAuditLog({\n timestamp: new Date().toISOString(),\n command: 'create-app',\n projectName,\n stackId: stack.id,\n dbDriver,\n status: 'success',\n elapsedMs: Date.now() - startTime,\n });\n\n // ── T013: Success box ─────────────────────────────────────────────────\n printSuccessBox(projectName, stack, dbDriver, backendPort);\n } catch (err) {\n // ── Cleanup partial directory ────────────────────────────────────────\n if (!cloneSucceeded && existsSync(projectDir)) {\n rmSync(projectDir, { recursive: true, force: true });\n }\n\n // ── C1: Audit log — failure ─────────────────────────────────────────\n const errorMsg = err instanceof Error ? err.message : String(err);\n writeAuditLog({\n timestamp: new Date().toISOString(),\n command: 'create-app',\n projectName,\n stackId: resolvedStackId,\n dbDriver,\n status: 'failed',\n elapsedMs: Date.now() - startTime,\n error: errorMsg,\n });\n\n // ── Format and display error ─────────────────────────────────────────\n if (err instanceof BrewnetError) {\n console.error('\\n' + err.format() + '\\n');\n } else {\n console.error(chalk.red(`\\nError: ${errorMsg}\\n`));\n }\n process.exit(1);\n } finally {\n process.removeListener('SIGINT', sigintHandler);\n }\n}\n\n// ---------------------------------------------------------------------------\n// T013 — printSuccessBox\n// ---------------------------------------------------------------------------\n\n/**\n * Print a bordered success summary after successful scaffolding.\n */\nfunction printSuccessBox(\n projectName: string,\n stack: StackEntry,\n dbDriver: string,\n backendPort: number,\n): void {\n const stackLabel = `${stack.id} (${stack.language} · ${stack.framework} · ${stack.orm})`;\n const dbLabel =\n dbDriver === 'sqlite3'\n ? `sqlite3 (no external container)`\n : `${dbDriver} (container on port ${dbDriver === 'postgres' ? '5433' : '3307'})`;\n\n const lines: string[] = [];\n\n lines.push(chalk.green(`✅ ${projectName} is ready!`));\n lines.push('');\n\n if (stack.isUnified) {\n lines.push(`App (UI + API) → ${chalk.cyan('http://localhost:3000')}`);\n } else {\n lines.push(`Frontend → ${chalk.cyan('http://localhost:3000')}`);\n lines.push(`Backend → ${chalk.cyan(`http://localhost:${backendPort}`)}`);\n }\n\n lines.push(`Stack → ${chalk.bold(stackLabel)}`);\n lines.push(`Database → ${dbLabel}`);\n lines.push('');\n lines.push(`cd ${projectName}`);\n lines.push(`make logs ${chalk.dim('# view container logs')}`);\n lines.push(`make down ${chalk.dim('# stop containers')}`);\n\n const maxLen = Math.max(...lines.map((l) => stripAnsi(l).length));\n const border = '─'.repeat(maxLen + 4);\n\n console.log('\\n' + chalk.green('┌' + border + '┐'));\n for (const line of lines) {\n const pad = maxLen - stripAnsi(line).length;\n console.log(chalk.green('│') + ' ' + line + ' '.repeat(pad) + ' ' + chalk.green('│'));\n }\n console.log(chalk.green('└' + border + '┘') + '\\n');\n}\n\n/**\n * Strip ANSI escape codes for length calculation.\n * Minimal implementation — covers common chalk escape sequences.\n */\nfunction stripAnsi(str: string): string {\n return str.replace(/\\x1B\\[[0-9;]*m/g, '');\n}\n"],"mappings":"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","names":["existsSync","fileURLToPath","chalk","select","chalk","ora","input","execa","checkDiskSpace","resolve","execa","os","chalk","platform","resolve","os","chalk","ora","execa","input","action","input","select","chalk","chalk","input","select","input","select","confirm","chalk","chalk","select","confirm","input","select","confirm","chalk","chalk","select","confirm","input","select","confirm","chalk","ora","execa","os","logger","resolve","execa","chalk","select","input","ora","confirm","resolve","mkdirSync","select","input","chalk","mkdirSync","chalk","select","input","writeFileSync","mkdirSync","readFileSync","existsSync","chmodSync","join","homedir","chalk","ora","Table","confirm","select","writeFileSync","mkdirSync","join","password","execSync","password","join","mkdirSync","writeFileSync","join","mkdirSync","writeFileSync","execa","resolve","chalk","select","join","mkdirSync","writeFileSync","homedir","ora","confirm","readFileSync","cloneStack","existsSync","Table","chmodSync","existsSync","readFileSync","homedir","join","execa","augmentedEnv","join","homedir","existsSync","readFileSync","execa","Dockerode","chalk","Table","execSync","existsSync","readFileSync","join","homedir","join","dirname","mkdirSync","existsSync","homedir","resolve","execSync","chalk","Table","path","join","homedir","existsSync","readFileSync","execa","chalk","input","password","chalk","input","password","fileURLToPath","chalk","existsSync","select","chalk","execa","Dockerode","Dockerode","chalk","stripAnsi","execa","chalk","ora","ora","chalk","chalk","ora","confirm","chalk","confirm","ora","chalk","ora","execa","ora","execa","chalk","chalk","ora","execa","ora","execa","chalk","chalk","execa","chalk","execa","chalk","ora","homedir","join","join","homedir","chalk","sizeMB","ora","chalk","ora","confirm","homedir","join","DEFAULT_BACKUPS_DIR","join","homedir","chalk","confirm","ora","chalk","ora","execSync","chalk","spinner","ora","execa","resolve","chalk","execSync","chalk","confirm","select","chalk","select","confirm","chalk","ora","input","select","Dockerode","chalk","input","ora","select","statusIcon","Dockerode","captureQuickTunnelUrl","resolve","path","URL_REGEX","existsSync","rmSync","mkdirSync","appendFileSync","resolve","join","homedir","chalk","ora","select","join","homedir","mkdirSync","appendFileSync","select","chalk","resolve","existsSync","rmSync","ora"]}