@@ -2,74 +2,147 @@ import { Command } from "@oclif/core";
22import * as fs from "fs" ;
33import * as path from "path" ;
44import chalk from "chalk" ;
5- import { getCaddyfileTemplate , ENV_EXAMPLE_TLS } from "../../../../templates/tls/templates.js" ;
5+ import { input , confirm } from "@inquirer/prompts" ;
6+ import {
7+ getCaddyfileTemplate ,
8+ getTlsEnvBlock ,
9+ TLS_ENV_EXAMPLE_BLOCK ,
10+ } from "../../../../templates/tls/templates.js" ;
11+
12+ function envFileHasTlsConfig ( filePath : string ) : boolean {
13+ if ( ! fs . existsSync ( filePath ) ) return false ;
14+ const content = fs . readFileSync ( filePath , "utf-8" ) ;
15+ return / ^ D O M A I N = / m. test ( content ) ;
16+ }
617
718export default class ConfigureTLS extends Command {
819 static description = "Configure TLS for your application" ;
920
10- static summary = `Adds TLS configuration to your EigenCloud application.
21+ static summary = `Interactively configures TLS for your EigenCloud application.
1122
12- This command creates:
13- - Caddyfile: Reverse proxy configuration for automatic HTTPS
14- - .env.example.tls: Example environment variables for TLS
23+ Prompts for domain and TLS settings, then:
24+ - Creates a Caddyfile for automatic HTTPS via Caddy reverse proxy
25+ - Appends TLS variables to .env with your values
26+ - Appends TLS placeholders to .env.example
1527
1628TLS certificates are automatically obtained via Let's Encrypt using the tls-keygen tool.` ;
1729
1830 async run ( ) {
1931 const cwd = process . cwd ( ) ;
2032
33+ // Check if TLS is already configured in .env
34+ const envPath = path . join ( cwd , ".env" ) ;
35+ if ( envFileHasTlsConfig ( envPath ) ) {
36+ this . warn ( "TLS is already configured in .env (DOMAIN is set). Skipping." ) ;
37+ return ;
38+ }
39+
2140 // Write Caddyfile
2241 const caddyfilePath = path . join ( cwd , "Caddyfile" ) ;
2342 if ( fs . existsSync ( caddyfilePath ) ) {
24- this . warn ( "Caddyfile already exists. Skipping.. ." ) ;
43+ this . log ( "Caddyfile already exists, keeping existing file ." ) ;
2544 } else {
2645 const caddyfileContent = getCaddyfileTemplate ( ) ;
2746 fs . writeFileSync ( caddyfilePath , caddyfileContent , { mode : 0o644 } ) ;
2847 this . log ( "Created Caddyfile" ) ;
2948 }
3049
31- // Write .env.example.tls
32- const envTLSPath = path . join ( cwd , ".env.example.tls" ) ;
33- if ( fs . existsSync ( envTLSPath ) ) {
34- this . warn ( ".env.example.tls already exists. Skipping..." ) ;
35- } else {
36- fs . writeFileSync ( envTLSPath , ENV_EXAMPLE_TLS , { mode : 0o644 } ) ;
37- this . log ( "Created .env.example.tls" ) ;
38- }
39-
40- // Print success message and instructions
41- this . log ( "" ) ;
42- this . log ( chalk . green ( "TLS configuration added successfully" ) ) ;
43- this . log ( "" ) ;
44- this . log ( "Created:" ) ;
45- this . log ( " - Caddyfile" ) ;
46- this . log ( " - .env.example.tls" ) ;
4750 this . log ( "" ) ;
4851
49- this . log ( "To enable TLS:" ) ;
52+ // Prompt for TLS variables
53+ const domain = await input ( {
54+ message : "Domain name:" ,
55+ validate : ( value ) => {
56+ const trimmed = value . trim ( ) ;
57+ if ( ! trimmed ) return "Domain is required" ;
58+ if ( trimmed . toLowerCase ( ) === "localhost" ) return "Domain cannot be localhost" ;
59+ if ( ! / ^ [ a - z A - Z 0 - 9 ] ( [ a - z A - Z 0 - 9 - ] * \. ) + [ a - z A - Z ] { 2 , } $ / . test ( trimmed ) )
60+ return "Enter a valid domain (e.g. myapp.example.com)" ;
61+ return true ;
62+ } ,
63+ } ) ;
64+
65+ const appPort = await input ( {
66+ message : "App port:" ,
67+ default : "3000" ,
68+ validate : ( value ) => {
69+ const num = Number ( value . trim ( ) ) ;
70+ if ( ! Number . isInteger ( num ) || num < 1 || num > 65535 ) return "Enter a valid port (1-65535)" ;
71+ return true ;
72+ } ,
73+ } ) ;
74+
75+ const acmeStaging = await confirm ( {
76+ message : "Use Let's Encrypt staging? (recommended for first deploy to avoid rate limits)" ,
77+ default : true ,
78+ } ) ;
79+
80+ const enableCaddyLogs = await confirm ( {
81+ message : "Enable Caddy debug logs?" ,
82+ default : false ,
83+ } ) ;
84+
85+ // Show summary
5086 this . log ( "" ) ;
51- this . log ( "1. Add TLS variables to .env:" ) ;
52- this . log ( " cat .env.example.tls >> .env" ) ;
87+ this . log ( chalk . bold ( "TLS Configuration:" ) ) ;
88+ this . log ( ` Domain: ${ domain . trim ( ) } ` ) ;
89+ this . log ( ` App port: ${ appPort . trim ( ) } ` ) ;
90+ this . log ( ` ACME staging: ${ acmeStaging } ` ) ;
91+ this . log ( ` Caddy logs: ${ enableCaddyLogs } ` ) ;
5392 this . log ( "" ) ;
5493
55- this . log ( "2. Configure required variables:" ) ;
56- this . log ( " DOMAIN=yourdomain.com" ) ;
94+ const confirmed = await confirm ( {
95+ message : "Write these settings to .env?" ,
96+ default : true ,
97+ } ) ;
98+
99+ if ( ! confirmed ) {
100+ this . log ( "Cancelled." ) ;
101+ return ;
102+ }
103+
104+ const vars = {
105+ domain : domain . trim ( ) ,
106+ appPort : appPort . trim ( ) ,
107+ acmeStaging,
108+ enableCaddyLogs,
109+ } ;
110+
111+ // Append to .env
112+ const envBlock = getTlsEnvBlock ( vars ) ;
113+ fs . appendFileSync ( envPath , envBlock , { mode : 0o644 } ) ;
114+ this . log ( `Updated .env` ) ;
115+
116+ // Append to .env.example (with placeholders, skip if already has DOMAIN)
117+ const envExamplePath = path . join ( cwd , ".env.example" ) ;
118+ if ( ! envFileHasTlsConfig ( envExamplePath ) ) {
119+ fs . appendFileSync ( envExamplePath , TLS_ENV_EXAMPLE_BLOCK , { mode : 0o644 } ) ;
120+ this . log ( `Updated .env.example` ) ;
121+ }
122+
123+ // Print next steps
57124 this . log ( "" ) ;
58- this . log ( " For first deployment (recommended):" ) ;
59- this . log ( " ENABLE_CADDY_LOGS=true" ) ;
60- this . log ( " ACME_STAGING=true" ) ;
125+ this . log ( chalk . green ( "TLS configured successfully" ) ) ;
61126 this . log ( "" ) ;
62-
63- this . log ( "3. Set up DNS A record pointing to instance IP" ) ;
127+ this . log ( "Next steps:" ) ;
128+ this . log ( "" ) ;
129+ this . log ( "1. Set up DNS A record pointing to your instance IP" ) ;
64130 this . log ( " Run 'ecloud compute app list' to get IP address" ) ;
65131 this . log ( "" ) ;
66-
67- this . log ( "4. Upgrade: " ) ;
68- this . log ( " ecloud compute app upgrade" ) ;
132+ this . log ( "2. Deploy or upgrade:" ) ;
133+ this . log ( " ecloud compute app deploy # new app " ) ;
134+ this . log ( " ecloud compute app upgrade # existing app " ) ;
69135 this . log ( "" ) ;
70136
71- this . log ( "Note: Let's Encrypt rate limit is 5 certificates/week per domain" ) ;
72- this . log ( " To switch staging -> production: set ACME_STAGING=false" ) ;
73- this . log ( " If cert exists, use ACME_FORCE_ISSUE=true once to replace" ) ;
137+ if ( acmeStaging ) {
138+ this . log ( chalk . yellow ( "Note: ACME_STAGING is enabled (recommended for first deploy)" ) ) ;
139+ this . log ( "Once verified, switch to production certs:" ) ;
140+ this . log ( " 1. Set ACME_STAGING=false in .env" ) ;
141+ this . log ( " 2. Set ACME_FORCE_ISSUE=true in .env (one-time)" ) ;
142+ this . log ( " 3. Run: ecloud compute app upgrade" ) ;
143+ this . log ( "" ) ;
144+ }
145+
146+ this . log ( "Let's Encrypt rate limit: 5 certificates/week per domain" ) ;
74147 }
75148}
0 commit comments