diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..526c8a3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf \ No newline at end of file diff --git a/Design_Doc/DevProc.md b/Design_Doc/DevProc.md index 4eee47b..4ab707a 100644 --- a/Design_Doc/DevProc.md +++ b/Design_Doc/DevProc.md @@ -1277,6 +1277,36 @@ ## make a generic rvs-prxy system + +- Using codebase `C:\Users\Yan\ws\cmptr-infra\traefik-mech-win11`; You evaluate this code; Currently, it has outcome like below: + ``` + curl -s "https://www.ivantito.com/app-pub/demos/test/deploy-rvs-prxy.sh" | bash + [INFO] Detected running Traefik reverse proxy + ... + Select option [1-2] (default: 1 for Domain): + Selected: Setup New Domain + + --- Setup New Domain --- + Let's configure a new domain for your existing reverse proxy. + + Enter the domain name for this service: + Domain (e.g., app.example.com): www.sendbox.com.hk + Enter your Docker application container name: + Service name (e.g., my-app): + ``` + - the current operation as connecting a domain to local docker container (Service). You understand how it works. Then i aim to add a selection as current "Docker application container" or a new function as let traefik connect a domain, eg. app.example.com, to another web service, eg. https://cornersendbox-impact-vms.lovable.app/. + + - I have these two .env files, how can i select .env-ext-sendbox-com-hk to run? + ``` + .env-ext-sendbox-com-hk + .env-ext-www-sendbox-com-hk + deploy-rvs-prxy.sh + --- + cat .env-ext-sendbox-com-hk + DOMAIN_NAME="sendbox.com.hk" + EXTERNAL_URL="https://cornersendbox-impact-vms.lovable.app/" + ``` + - In a case, i have two domains name, that means two .env-www-domain-com and .env-domain-com, their service are same to one container, say web-fe-1, i find it has one domain work, another does not. I see the reason as: it has two dynamic configuration files inside /etc/traefik/config/, say app-service-www.domain.com-web-fe-1.yml, and app-service-domain.com-web-fe-1.yml, inside them, the routers are same names, say web-fe-1-https and web-fe-1-http, the services name are same too, say web-fe-1-service. By observation, the names format of routers and services is container-name-https, container-name-http and container-name-service. So i aim to improve this name format rule, do not use container-name, it may share to diff. domains, so should use domain name inside the rule, eg. domain-name-https, domain-name-http, domain-name-service, etc. - Currently .env-p2prly1-ivantito-com as: diff --git a/Design_Doc/Operations.md b/Design_Doc/Operations.md index 819b237..f40b7e6 100644 --- a/Design_Doc/Operations.md +++ b/Design_Doc/Operations.md @@ -49,6 +49,8 @@ curl -s "https://www.ivantito.com/app-pub/demos/test/deploy-rvs-prxy.sh" | bash 2. Application service name (Docker container name) 3. Application service port +(Select `1) Setup New Domain` from the setup menu. Options `2) Setup Port Exposure` and `3) Setup External Service Domain` are covered in Step 4 and Step 5.) + **What Happens:** - **Checks if your application service is running** - Creates `.env-` with domain settings @@ -112,6 +114,58 @@ You can setup multiple port exposures at once by creating multiple `.env-port-*` ``` The script will process all port configuration files in batch mode. +### Step 5: Setup External Service Domain (Route a Domain to an External Web Service) + +Use this mode when the upstream is **not** a local container on this host but an existing web service reached by URL (a hosted app, SaaS, or another server). No local container is required. + +```bash +curl -s "https://www.ivantito.com/app-pub/demos/test/deploy-rvs-prxy.sh" | bash +``` + +**The wizard will ask you (External service settings only):** + +1. Domain name, sub-path optional (e.g., `box.example.com` or `box.example.com/api`) +2. External service URL (e.g., `https://cornersendbox-impact-vms.lovable.app/`) + +**What Happens:** +- **Checks if Traefik reverse proxy is running** (Traefik mode only -- FRP is not supported for HTTP routing) +- Creates `.env-ext-` with the domain and external URL +- Writes a dynamic Traefik config that terminates TLS for the public domain and forwards to the external URL +- Obtains a Let's Encrypt certificate for the public domain + +**Configuration File Created:** +- `.env-ext-` - External service configuration (e.g., `.env-ext-box-example-com-api`) + +**Example `.env-ext-box-example-com-api`:** +```bash +DOMAIN_NAME="box.example.com/api" +EXTERNAL_URL="https://cornersendbox-impact-vms.lovable.app/" +``` + +**Example output:** +``` +[INFO] Setup External Service Domain +[INFO] New external service domain has been configured: box.example.com/api +[INFO] Public URL: https://box.example.com/api +[INFO] Forwarding to: https://cornersendbox-impact-vms.lovable.app +``` + +**Routing Semantics:** +- `https://box.example.com/api/x` → `https://cornersendbox-impact-vms.lovable.app/x` (public sub-path stripped, origin base path added) +- The origin receives its own `Host` header (`passHostHeader: false`), so the upstream must accept the request by its own hostname +- Origin TLS is verified with the system trust store; no `insecureSkipVerify` is generated +- Origin default port is `443` for `https` and `80` for `http` unless a port is present in the URL + +**Batch Processing:** +Create multiple `.env-ext-*` files to publish several external services in one run: +``` +.env-ext-box-example-com-api +.env-ext-demo-example-net +``` + +**Troubleshooting:** +- The script checks whether the external hostname resolves and only warns if it does not; note that Traefik resolves the name itself, inside its container + ### Alternative: Using Configuration Files Directly **For Reverse Proxy Setup:** @@ -189,12 +243,39 @@ The script supports port-based routing without domain names: [INFO] Processing port 2 of 2: .env-port-18082 [INFO] ========================================= ... - [INFO] Batch processing completed: 2 port(s) processed - ``` +[INFO] Batch processing completed: 2 port(s) processed + ``` -### Step 5: Manage Existing Configurations (Optional) +**For External Service Setup:** -To view and remove existing domain or port configurations: +The script supports routing a domain to an external web service URL: + +1. **With `.env-ext-` file:** + - Create `.env-ext-` file with the domain and external URL + - Ensure Traefik reverse proxy is running + - Run: `curl -s "https://www.ivantito.com/app-pub/demos/test/deploy-rvs-prxy.sh" | bash` + +2. **Batch processing (multiple external services):** + - Create multiple `.env-ext-*` files in the same directory + - Ensure Traefik reverse proxy is running + - Run: `curl -s "https://www.ivantito.com/app-pub/demos/test/deploy-rvs-prxy.sh" | bash` + - Script automatically processes all `.env-ext-*` files in batch mode + - Example output: + ``` + [INFO] Found 2 external service config files, will process all in batch mode + [INFO] ========================================= + [INFO] Processing external service 1 of 2: .env-ext-box-example-com-api + [INFO] ========================================= + [INFO] Setting up external service domain: box.example.com/api + ... + [INFO] Batch processing completed: 2 external service(s) processed + ``` + +3. **Mode precedence:** if `.env-port-*` files exist, the script runs port batch processing and skips domain and external service files (same precedence as before: port, then external service, then domain) + +### Step 6: Manage Existing Configurations (Optional) + +To view and remove existing domain, external service or port configurations: ```bash curl -s "https://www.ivantito.com/app-pub/demos/test/deploy-rvs-prxy.sh" | bash -s -- --manage @@ -202,9 +283,11 @@ curl -s "https://www.ivantito.com/app-pub/demos/test/deploy-rvs-prxy.sh" | bash **What Happens:** - Lists all current domain configurations (`.env-` files) +- Lists all current external service configurations (`.env-ext-` files) - Lists all current port configurations (`.env-port-` files) - Prompts you to select which configurations to remove (enter numbers separated by space) - **For domains**: Removes the configuration file AND removes the routing config from Traefik +- **For external services**: Removes the configuration file AND removes the routing config from Traefik - **For ports**: Removes the configuration file, removes the routing config from Traefik, removes the entry point, and recreates Traefik container without that port **Example Output:** @@ -223,25 +306,24 @@ Ports: 3. .env-port-18081 - Host port 18081 -> nginx-container 4. .env-port-18082 - Host port 18082 -> api-container -Select configurations to remove (enter numbers separated by space, or 'q' to quit): 1 3 +External Services: + 5. .env-ext-box-example-com-api - box.example.com/api -> https://cornersendbox-impact-vms.lovable.app + +Select configurations to remove (enter numbers separated by space, or 'q' to quit): 1 5 [INFO] Removing: .env-app-example-com [INFO] Removing domain config from Traefik: app.example.com [INFO] Removed: .env-app-example-com -[INFO] Removing: .env-port-18081 -[INFO] Removing port config from Traefik: port 18081 -[INFO] Removed: .env-port-18081 -[INFO] Updating Traefik container to remove entry points... -[INFO] Stopping Traefik container... -[INFO] Traefik container recreated without removed entry points -[INFO] Configuration cleanup complete. +[INFO] Removing: .env-ext-box-example-com-api +[INFO] Removing external service config from Traefik: box.example.com/api +[INFO] Removed: .env-ext-box-example-com-api ``` **Important Notes:** - Removing a port configuration will restart the Traefik container (brief downtime ~5 seconds) -- Domain removal does not require Traefik restart (dynamic config removal) +- Domain and external service removal does not require Traefik restart (dynamic config removal) - All changes are permanent and cannot be undone - make sure you want to remove before confirming -### Step 6: Configure Extra Traefik Entrypoints (QUIC, Custom Ports, etc.) +### Step 7: Configure Extra Traefik Entrypoints (QUIC, Custom Ports, etc.) Beyond the default entrypoints (`web` on :80, `websecure` on :443), you can add extra entrypoints such as HTTP/3 QUIC (UDP 443) or other custom protocols. Entrypoints are defined via command-line arguments to the Traefik container, so the container must be recreated to apply them. @@ -536,12 +618,17 @@ If environment variables are not set, the script will prompt for: - Domain name for routing - Docker network selection +When the wizard runs for an existing proxy, the setup menu offers three options: +- `1) Setup New Domain` - routes the domain to a local Docker container +- `2) Setup Port Exposure` - exposes a host port to a container port +- `3) Setup External Service Domain` - routes the domain to an external web service URL (asks for domain and URL only) + ### Container Management Commands #### Traefik Container (Traefik Mode) **Entrypoints:** -Default entrypoints are `web` (:80) and `websecure` (:443). See **[Step 6: Configure Extra Traefik Entrypoints](#step-6-configure-extra-traefik-entrypoints-quic-custom-ports-etc)** for a complete guide on adding/removing extra entrypoints (QUIC, custom ports, etc.) during both first-time setup and on a running Traefik container. +Default entrypoints are `web` (:80) and `websecure` (:443). See **[Step 7: Configure Extra Traefik Entrypoints](#step-7-configure-extra-traefik-entrypoints-quic-custom-ports-etc)** for a complete guide on adding/removing extra entrypoints (QUIC, custom ports, etc.) during both first-time setup and on a running Traefik container. ```bash # Start/run container diff --git a/Design_Doc/Spec.md b/Design_Doc/Spec.md index a06dbe3..de7b250 100644 --- a/Design_Doc/Spec.md +++ b/Design_Doc/Spec.md @@ -7,18 +7,20 @@ - `.env-your-domain2-com` -- **parameters for the domain your.domain2.com of your Application** - `.env-your-domainN-com` -- **parameters for the domain your.domainN.com of your Application** - `.env-port-` -- **parameters for port-based routing (e.g., .env-port-18081)** -- Assume your "Application" is already existing. +- `.env-ext-your-domain-com` -- **parameters for routing a domain to an external web service URL (e.g. .env-ext-app-example-com)** +- Assume your "Application" is already existing. (For external service routing there is no local Application - an upstream URL is used instead.) ### Key Capabilities -- **Separate Configuration Files**: `.env.rvs-prxy` for reverse proxy instance, `.env-your-domain-com` (or any domain format) for each domain, `.env-port-` for port-based routing +- **Separate Configuration Files**: `.env.rvs-prxy` for reverse proxy instance, `.env-your-domain-com` (or any domain format) for each domain, `.env-port-` for port-based routing, `.env-ext-your-domain-com` for external service routing - **Interactive Setup Wizard**: Guided configuration when no configuration files exist -- **Automatic Mode Detection**: Automatically detects whether to setup reverse proxy, add new domain, or setup port exposure -- **Three Deployment Modes**: +- **Automatic Mode Detection**: Automatically detects whether to setup reverse proxy, add new domain, setup port exposure, or route a domain to an external service +- **Four Deployment Modes**: - **Setup Reverse Proxy**: Deploys Traefik or FRP Tunnel with Traefik - - **Setup New Domain**: Adds new domain to existing reverse proxy (supports both Traefik and FRP modes) + - **Setup New Domain**: Adds new domain to existing reverse proxy, routed to a local Docker application container (supports both Traefik and FRP modes) - **Setup Port Exposure**: Exposes host port to connect directly to container port (Traefik mode only) -- **Batch Processing**: Automatically processes multiple `.env-` and `.env-port-*` files in a single run + - **Setup External Service Domain**: Routes a domain to an external web service URL - no local container required (Traefik mode only) +- **Batch Processing**: Automatically processes multiple `.env-`, `.env-port-*` and `.env-ext-*` files in a single run - **Traefik Mode**: Deploy and configure Traefik reverse proxy on the same server as your application - **FRP Mode**: Optional Fast Reverse Proxy support for tunneling connections to a remote proxy server - **FRP Domain Setup**: Add new domains to existing FRP tunnel by allocating new remote ports and updating proxy server configuration @@ -26,6 +28,7 @@ - **Docker Integration**: Seamless integration with Docker containers and networks - **HTTPS/TLS Support**: Automatic SSL certificate management via Let's Encrypt - **Port-Based Routing**: Direct host port to container port routing without domain names +- **External Service Routing**: Public domain with automatic Let's Encrypt certificate, TLS terminated by Traefik and proxied to an upstream HTTPS/HTTP URL; origin receives its own `Host` header, and path mapping (sub-path strip / origin base-path add) is automatic ### Workflow @@ -44,6 +47,13 @@ - **Checks if application service is running** - Configures domain in existing reverse proxy +3. **Add external service domain (proxy running)**: Setup External Service Domain + - Same as step 2, but the upstream is an external web service URL instead of a local container + - Interactive wizard asks for domain name and external service URL (if no `.env-ext-` exists) + - Creates `.env-ext-` with `DOMAIN_NAME` and `EXTERNAL_URL` + - **No application service check** (no local container is involved) + - Configures the domain in the existing Traefik proxy (Traefik mode only) + **Proxy Mode Detection:** - Script automatically detects running proxy from Docker containers - Traefik mode: detects `traefik` container @@ -102,6 +112,14 @@ - Accepts any domain format: `.com`, `.net`, `.org`, `.org.hk`, `.com.hk`, subdomains, etc. - **Simplified format:** Only requires 3 variables (see below) +**External Service Configuration (`.env-ext-`):** +- Pattern: `.env-ext-` (e.g., `.env-ext-app-example-com`, `.env-ext-box-example-com-api`) +- Contains routing settings for a public domain forwarded to an external web service URL (DOMAIN_NAME, EXTERNAL_URL) +- One file per domain +- Multiple external service files can coexist +- Traefik mode only (no FRP support - FRP tunnels TCP ports, not HTTP routing) +- **Simplified format:** Only requires 2 variables (see below) + #### Key Environment Variables **Domain-Specific Configuration (`.env-`):** @@ -169,6 +187,29 @@ CONTAINER_PORT="80" - Access services via `http://:` without domain name routing - Example: `http://this-host:18081` → connects to container port 80 +**External Service Configuration (`.env-ext-`):** + +**Required Variables (only 2):** +- `DOMAIN_NAME` - Public domain handled by this proxy, optional sub-path allowed (e.g., `app.example.com` or `box.example.com/api`) +- `EXTERNAL_URL` - Full upstream URL of the external web service (e.g., `https://service.example.net/` or `https://service.example.net:8443/api`) + +**Example `.env-ext-box-example-com-api`:** +```bash +DOMAIN_NAME="box.example.com/api" +EXTERNAL_URL="https://cornersendbox-impact-vms.lovable.app/" +``` + +**Routing Semantics:** +- Scheme is derived from `EXTERNAL_URL`; an `https` origin is contacted over TLS with full certificate verification (no `insecureSkipVerify`) +- Default origin port: `443` for `https`, `80` for `http`; a port in `EXTERNAL_URL` overrides it +- `passHostHeader: false` - the origin receives the upstream host, not the public domain +- `DOMAIN_NAME` may carry a sub-path: the sub-path is stripped before forwarding and any base path in `EXTERNAL_URL` is added back, so `https://box.example.com/api/x` → `https://cornersendbox-impact-vms.lovable.app/x` +- Automatic HTTPS via Let's Encrypt for the public domain; the origin certificate is verified, not managed + +**Use Case:** +- Publish an existing web service (SaaS, hosted app, another server) on a domain handled by this proxy, without deploying a local container +- Example: `https://box.example.com/` → `https://cornersendbox-impact-vms.lovable.app/` + ### Approaches #### Traefik Only @@ -211,13 +252,14 @@ CONTAINER_PORT="80" - HTTP to HTTPS redirect support - Let's Encrypt SSL certificate automation - Sub-path routing support (e.g., `example.com/api`) + - External service routing to a URL upstream (`.env-ext-`), with or without sub-path - Non-destructive configuration updates (no restart required by default) - Docker network integration (`traefik-public` network) - **Pre-requisites:** - Docker installed on the local server (same server where script runs) - User has permission to execute Docker commands - - Local application container must be running and connected to `traefik-public` network + - Local application container must be running and connected to `traefik-public` network (not required for external service routing - no local container is involved) #### Traefik + FRP diff --git a/Design_Doc/Tech_Desc.md b/Design_Doc/Tech_Desc.md index f1e8a18..9c908b3 100644 --- a/Design_Doc/Tech_Desc.md +++ b/Design_Doc/Tech_Desc.md @@ -9,6 +9,8 @@ The `deploy-rvs-prxy.sh` script is an automated deployment tool supporting both **Deployment Modes:** 1. **Setup Reverse Proxy**: Deploys new Traefik or FRP Tunnel with Traefik 2. **Setup New Domain**: Adds new domain configuration to existing reverse proxy +3. **Setup Port Exposure**: Exposes a host port to a container port +4. **Setup External Service Domain**: Adds a domain routed to an external web service URL **Configuration File Structure:** - `.env.rvs-prxy` - Reverse proxy instance configuration (FRP mode, Traefik version, etc.) @@ -16,18 +18,23 @@ The `deploy-rvs-prxy.sh` script is an automated deployment tool supporting both - Domain format: Any domain format supported (`.com`, `.net`, `.org`, `.org.hk`, `.com.hk`, subdomains, etc.) - Filename pattern: Dots replaced with dashes (e.g., `app.example.com` → `.env-app-example-com`) - `.env-port-` - Port-based routing configuration (HOST_PORT, CONTAINER_NAME, CONTAINER_PORT) -- Multiple domain and port files can coexist for multiple domains and ports +- `.env-ext-` - External service routing configuration (DOMAIN_NAME, EXTERNAL_URL) + - Filename pattern: Dots replaced with dashes (e.g., `box.example.com/api` → `.env-ext-box-example-com-api`) +- Multiple domain, port and external service files can coexist **Automatic Mode Detection:** - No config files → Interactive wizard for proxy setup - `.env.rvs-prxy` exists, proxy not running → Setup Reverse Proxy -- `.env.rvs-prxy` exists, proxy running, no domain/port file → Interactive wizard for domain/port setup +- `.env.rvs-prxy` exists, proxy running, no domain/port/ext file → Interactive wizard for domain/port/external service setup - `.env.rvs-prxy` + `.env-` exists → Setup New Domain - `.env.rvs-prxy` + `.env-port-` exists → Setup Port Exposure +- `.env.rvs-prxy` + `.env-ext-` exists → Setup External Service Domain +- Batch precedence when several types coexist: port files first, then external service files, then domain files **Validation:** - **Proxy Setup Mode**: Validates proxy-specific settings (TRAEFIK_VER, LETSENCRYPT_EMAIL for Traefik; FRP_SERVER_ADDR for FRP) - **Domain Setup Mode**: Validates domain-specific settings (DOMAIN_NAME, LOCAL_SERVICE_NAME, LOCAL_SERVICE_PORT) +- **External Service Mode**: Validates DOMAIN_NAME and EXTERNAL_URL; requires Traefik mode (`FRP=false`), since FRP tunnels TCP ports rather than performing HTTP routing **Workflow:** @@ -40,17 +47,18 @@ First Run (No .env.rvs-prxy): Second Run (.env.rvs-prxy exists, proxy running): 1. Detects running proxy - 2. Interactive wizard asks for domain or port settings - 3. Creates .env- or .env-port- with settings - 4. Validation checks domain/port settings - 5. Checks if application service/container is running - 6. Configures domain/port in existing reverse proxy + 2. Interactive wizard asks for domain, port or external service settings + 3. Creates .env-, .env-port- or .env-ext- with settings + 4. Validation checks domain/port/external service settings + 5. Checks if application service/container is running (skipped for external service routing) + 6. Configures domain/port/external service in existing reverse proxy 7. Shows: "Service will be accessible at: https://domain.name or http://host-ip:port" -Three Deployment Modes: +Four Deployment Modes: 1. Setup Reverse Proxy - Deploys Traefik or FRP Tunnel - 2. Setup New Domain - Adds domain-based routing + 2. Setup New Domain - Adds domain-based routing to a local container 3. Setup Port Exposure - Adds port-based routing + 4. Setup External Service Domain - Adds domain-based routing to an external URL (Traefik only) ### Core Components @@ -60,15 +68,16 @@ Three Deployment Modes: - **Interactive Setup Wizard**: If no `.env.rvs-prxy` file is found, automatically launches a guided setup wizard - Normalizes boolean values (FRP variable) - Validates required parameters for each mode -- Supports three modes: proxy setup, domain setup, and port exposure +- Supports four modes: proxy setup, domain setup, port exposure, and external service routing +- Classifies config files into `DOMAIN_ENV_FILES`, `PORT_ENV_FILES`, and `EXT_ENV_FILES`; the first file of each type determines the detected mode (port > external > domain) **Interactive Setup Wizard Features:** - Step-by-step guided configuration - Mode selection (Traefik or FRP) for proxy setup -- Choice between domain setup or port exposure when proxy exists +- Choice between domain setup, port exposure, or external service routing when proxy exists - Mode-specific configuration prompts - Sensible defaults for all settings -- Automatic saving to `.env.rvs-prxy`, `.env-`, or `.env-port-` files +- Automatic saving to `.env.rvs-prxy`, `.env-`, `.env-port-`, or `.env-ext-` files **2. Instance Management (`prompt_instance_name`)** - Auto-detects existing instances from Docker volumes (FRP mode) @@ -117,6 +126,12 @@ Three Deployment Modes: - Creates unique configuration filenames to avoid conflicts - Supports dynamic configuration loading (no restart required by default) +5. **External Service Configuration** (`configure_traefik_external`, called by `setup_external_domain`) + - Generates routing rules for a public domain whose upstream is an external URL + - Requires Traefik mode (`FRP=false`) - rejects `.env-ext-*` files when FRP is enabled + - No local container lookup and no Docker network requirement for the upstream + - Persists settings as `.env-ext-` via `save_ext_env()` for later re-runs + ### Local Docker Operations - `local_docker_exec()`: Executes Docker commands directly on the local server @@ -184,6 +199,48 @@ http: Router, service, and middleware names use the domain (or domain-without-sub-path) as the prefix instead of the container name. This ensures unique resource names when multiple domains share the same backend container. The backend URL still references the container hostname for Docker networking. +**External Service Config** (dynamic, per-domain, written by `configure_traefik_external`): +```yaml +http: + routers: + -https: + rule: 'Host(``)' + service: -service + tls: + certResolver: letsencrypt + entryPoints: + - websecure + -http: + rule: 'Host(``)' + service: -service + entryPoints: + - web + middlewares: + - redirect-to-https + middlewares: + -strip: + stripPrefix: + paths: + - '' + -add: + addPrefix: + path: '' + services: + -service: + loadBalancer: + servers: + - url: '://[:]' + passHostHeader: false +``` + +Implementation notes: +- Config file name: `app-ext-.yml` inside the `traefik_data` volume, written via a temporary `alpine` container (`local_volume_copy`) +- `parse_external_url()` splits `EXTERNAL_URL` into scheme, host, port (default `443` for `https`, `80` for `http`) and base path; a non-numeric or out-of-range port is rejected +- The origin path is never part of the server URL; it is added back with `addPrefix` so a public sub-path and an origin base path compose correctly +- `passHostHeader: false` makes the origin receive its own hostname instead of the public domain, so upstream vhost routing and origin certificates keep working +- Origin TLS uses the container trust store (no `insecureSkipVerify`); only the public domain gets a Let's Encrypt certificate +- DNS for the external host is resolved inside the Traefik container, so `check_external_service_reachable()` only performs a local `nslookup`/`host` check and warns without aborting + **Entrypoints Configuration:** Default entrypoints (`web` on :80, `websecure` on :443) are defined in the docker-compose.yml command args. Additional entrypoints can be configured via the `EXTRA_ENTRYPOINTS` environment variable in `.env.rvs-prxy`: - Format: `name=:port/protocol,name2=:port` (comma-separated) diff --git a/deploy-rvs-prxy.sh b/deploy-rvs-prxy.sh index 1bf3ef2..2126f74 100755 --- a/deploy-rvs-prxy.sh +++ b/deploy-rvs-prxy.sh @@ -1,10 +1,12 @@ #!/bin/bash # deploy-rvs-prxy.sh - Automated deployment script for Reverse Proxy setup -# This script supports two modes: +# This script supports four modes: # 1) Setup Reverse Proxy (Traefik or FRP Tunnel with Traefik) -# 2) Setup New Domain into existing reverse proxy -# This script runs on the same computer as the service it connects to +# 2) Setup New Domain into existing reverse proxy (routes to a local Docker container) +# 3) Setup Port Exposure (exposes a host port to a container port) +# 4) Setup External Service Domain (routes a domain to an external web service URL) +# This script runs on the same computer as the reverse proxy set -e # Exit on any error @@ -45,7 +47,8 @@ LOCAL_SERVICE_NAME="" LOCAL_SERVICE_PORT="" LOCAL_DOCKER_NETWORK="" -# Deployment mode: "proxy" (setup reverse proxy), "domain" (setup new domain), or "port" (expose host port) +# Deployment mode: "proxy" (setup reverse proxy), "domain" (setup new domain), +# "port" (expose host port), or "external" (route a domain to an external service) DEPLOY_MODE="" # Domain configuration file pattern: .env-your-domain-com @@ -54,12 +57,21 @@ DOMAIN_ENV_FILE="" # Port configuration file pattern: .env-port- PORT_ENV_FILE="" +# External service configuration file pattern: .env-ext-your-domain-com +EXT_ENV_FILE="" + +# External service target (Traefik mode only) - upstream URL of the remote web service +EXTERNAL_URL="" + # Array to store all domain configuration files for batch processing declare -a DOMAIN_ENV_FILES=() # Array to store all port configuration files for batch processing declare -a PORT_ENV_FILES=() +# Array to store all external service configuration files for batch processing +declare -a EXT_ENV_FILES=() + # Function to load environment variables from .env.rvs-prxy files load_env() { local proxy_env_loaded=false @@ -89,6 +101,10 @@ load_env() { if [[ "$env_file" == .env-port-* ]]; then continue fi + # Skip external service configuration files (.env-ext-*) + if [[ "$env_file" == .env-ext-* ]]; then + continue + fi if [ -f "$env_file" ]; then DOMAIN_ENV_FILES+=("$env_file") fi @@ -102,6 +118,14 @@ load_env() { fi done + # Collect external service configuration files (.env-ext-* pattern) + # These files contain external URL routing settings: DOMAIN_NAME, EXTERNAL_URL + for ext_file in .env-ext-*; do + if [ -f "$ext_file" ]; then + EXT_ENV_FILES+=("$ext_file") + fi + done + # Check for legacy .env.port-* files and auto-rename to .env-port-* for legacy_file in .env.port-*; do if [ -f "$legacy_file" ]; then @@ -142,6 +166,17 @@ load_env() { fi fi + # Load the first external service config file for initial variable setup + if [ ${#EXT_ENV_FILES[@]} -gt 0 ]; then + echo -e "${BLUE}[INFO]${NC} Loading external service config from ${EXT_ENV_FILES[0]}" >&2 + source "${EXT_ENV_FILES[0]}" + EXT_ENV_FILE="${EXT_ENV_FILES[0]}" + + if [ ${#EXT_ENV_FILES[@]} -gt 1 ]; then + log_info "Found ${#EXT_ENV_FILES[@]} external service config files, will process all in batch mode" + fi + fi + # Step 3: Detect proxy mode from running containers # This allows domain setup even without .env.rvs-prxy file, by detecting running proxy # IMPORTANT: Running container detection takes precedence over .env settings @@ -186,6 +221,10 @@ load_env() { # Port config exists - setup port-based routing DEPLOY_MODE="port" log_info "Deployment mode: Setup Port Exposure (found port config: ${PORT_ENV_FILE})" + elif [ ${#EXT_ENV_FILES[@]} -gt 0 ]; then + # External service config exists - route domain to external URL + DEPLOY_MODE="external" + log_info "Deployment mode: Setup External Service Domain (found external service config: ${EXT_ENV_FILE})" elif [ "$domain_env_loaded" = true ]; then # Domain config exists - setup new domain DEPLOY_MODE="domain" @@ -207,6 +246,8 @@ load_env() { source "$DOMAIN_ENV_FILE" elif [ "$DEPLOY_MODE" = "port" ] && [ -n "$PORT_ENV_FILE" ]; then source "$PORT_ENV_FILE" + elif [ "$DEPLOY_MODE" = "external" ] && [ -n "$EXT_ENV_FILE" ]; then + source "$EXT_ENV_FILE" else source .env.rvs-prxy fi @@ -237,6 +278,18 @@ load_env() { log_error "Please set these in your .env- file" exit 1 fi + elif [ "$DEPLOY_MODE" = "external" ]; then + # External service config file exists - validate it + if [ -z "$DOMAIN_NAME" ]; then + log_error "DOMAIN_NAME is required for external service setup mode" + log_error "Please set DOMAIN_NAME in your .env-ext- file" + exit 1 + fi + if [ -z "$EXTERNAL_URL" ]; then + log_error "EXTERNAL_URL is required for external service setup" + log_error "Please set it in your .env-ext- file (e.g. https://app.example.com/)" + exit 1 + fi elif [ "$DEPLOY_MODE" = "proxy" ]; then # For proxy setup mode, validate proxy-specific settings if [ "$FRP" = false ]; then @@ -354,6 +407,110 @@ EOF PORT_ENV_FILE="$port_file" } +# Function to save external service configuration to .env-ext- file +save_ext_env() { + local domain_name="$1" + local external_url="$2" + + # Convert domain name to filename format: your-domain-com -> .env-ext-your-domain-com + # Sub-path (/) is also converted to keep the filename flat + local domain_file=".env-ext-$(echo "$domain_name" | sed 's/\./-/g; s/\//-/g')" + + echo -e "${BLUE}[INFO]${NC} Saving external service configuration to $domain_file" >&2 + + # Create external service-specific env file + cat > "$domain_file" << EOF +# External service configuration for ${domain_name} +# Generated by deploy-rvs-prxy.sh on $(date) +# Traefik terminates TLS for this domain and proxies to EXTERNAL_URL (origin Host header) + +DOMAIN_NAME="${domain_name}" +EXTERNAL_URL="${external_url}" +EOF + + echo -e "${GREEN}[INFO]${NC} External service configuration saved to $domain_file" >&2 + EXT_ENV_FILE="$domain_file" +} + +# Function to parse EXTERNAL_URL into scheme/host/port/base-path components +# Sets: EXT_SCHEME, EXT_HOST, EXT_PORT, EXT_BASE_PATH +parse_external_url() { + EXT_SCHEME="" + EXT_HOST="" + EXT_PORT="" + EXT_BASE_PATH="" + + local url="${EXTERNAL_URL# }" + url="${url% }" + + case "$url" in + https://*) EXT_SCHEME="https" ;; + http://*) EXT_SCHEME="http" ;; + *) return 1 ;; + esac + + # Strip scheme + local remainder="${url#*://}" + + # Split host(+port) from path + local host_part="${remainder%%/*}" + local path_part="" + if [[ "$remainder" == *"/"* ]]; then + path_part="/${remainder#*/}" + fi + + # Split host and port + if [[ "$host_part" == *:* ]]; then + EXT_HOST="${host_part%%:*}" + EXT_PORT="${host_part##*:}" + else + EXT_HOST="$host_part" + fi + + # Validate host + if [ -z "$EXT_HOST" ]; then + return 1 + fi + + # Validate port (numeric, 1-65535) or default by scheme + if [ -z "$EXT_PORT" ]; then + if [ "$EXT_SCHEME" = "https" ]; then + EXT_PORT="443" + else + EXT_PORT="80" + fi + elif ! [[ "$EXT_PORT" =~ ^[0-9]+$ ]] || [ "$EXT_PORT" -lt 1 ] || [ "$EXT_PORT" -gt 65535 ]; then + return 1 + fi + + # Normalise base path (strip trailing slash, keep leading slash) + if [ "$path_part" != "/" ]; then + EXT_BASE_PATH="${path_part%/}" + fi + + return 0 +} + +# Function to warn when the external host cannot be resolved (non-fatal) +check_external_service_reachable() { + log_info "Checking external service ${EXT_SCHEME}://${EXT_HOST}:${EXT_PORT} ..." + + local resolved="" + if command -v getent >/dev/null 2>&1; then + resolved=$(getent hosts "$EXT_HOST" 2>/dev/null || true) + fi + + if [ -n "$resolved" ]; then + log_info "External host ${EXT_HOST} resolves to: $(echo "$resolved" | awk '{print $1}' | head -3 | tr '\n' ' ')" + return 0 + fi + + log_warn "Could not resolve external host '${EXT_HOST}' from this server." + log_warn "Traefik resolves and connects to it from inside the container - DNS there may still work." + log_warn "If the domain does not load, verify ${EXTERNAL_URL} is reachable and publicly resolvable." + return 0 +} + # Function to read from terminal instead of pipe read_terminal() { local prompt="$1" @@ -386,10 +543,11 @@ interactive_setup_wizard() { echo -e "${BLUE}--- What would you like to setup? ---${NC}" >&2 echo -e " 1) ${GREEN}Setup New Domain${NC} - Route traffic based on domain name" >&2 echo -e " 2) ${GREEN}Setup Port Exposure${NC} - Expose host port to container port" >&2 + echo -e " 3) ${GREEN}Setup External Service Domain${NC} - Route a domain to an external web service (URL)" >&2 echo "" >&2 while true; do - setup_choice=$(read_terminal "Select option [1-2] (default: 1 for Domain): " "1") + setup_choice=$(read_terminal "Select option [1-3] (default: 1 for Domain): " "1") case "$setup_choice" in 1|"") # Setup New Domain @@ -502,8 +660,54 @@ interactive_setup_wizard() { log_info "Deployment mode: Setup Port Exposure (interactive setup)" break ;; + 3) + # Setup External Service Domain + echo -e "${GREEN}Selected: Setup External Service Domain${NC}" >&2 + echo "" >&2 + echo -e "${BLUE}--- Setup External Service Domain ---${NC}" >&2 + echo -e "Let's route a new domain to an external web service." >&2 + echo -e "No local Docker container is required - Traefik proxies to the URL below." >&2 + echo "" >&2 + + # Get domain name (optional sub-path supported, e.g. app.example.com/api) + echo -e "Enter the domain name for this external service:" >&2 + while [ -z "$DOMAIN_NAME" ]; do + DOMAIN_NAME=$(read_terminal "Domain (e.g., app.example.com or app.example.com/api): " "") + if [ -z "$DOMAIN_NAME" ]; then + echo -e "${RED}Domain name cannot be empty.${NC}" >&2 + fi + done + + # Get external service URL + echo -e "Enter the external web service URL:" >&2 + while [ -z "$EXTERNAL_URL" ]; do + EXTERNAL_URL=$(read_terminal "External URL (e.g., https://cornersendbox-impact-vms.lovable.app/): " "") + if [ -z "$EXTERNAL_URL" ]; then + echo -e "${RED}External URL cannot be empty.${NC}" >&2 + elif ! [[ "$EXTERNAL_URL" =~ ^https?://[^[:space:]]+$ ]]; then + echo -e "${RED}Invalid URL. It must start with http:// or https://${NC}" >&2 + echo -e "${RED}Example: https://cornersendbox-impact-vms.lovable.app/${NC}" >&2 + EXTERNAL_URL="" + fi + done + + # Save to external service .env-ext- file + save_ext_env "$DOMAIN_NAME" "$EXTERNAL_URL" + + # Load FRP setting from existing .env if it exists + if [ -f ".env.rvs-prxy" ]; then + source .env.rvs-prxy + else + # Set default FRP mode if .env doesn't exist + FRP=false + fi + + DEPLOY_MODE="external" + log_info "Deployment mode: Setup External Service Domain (interactive setup)" + break + ;; *) - echo -e "${RED}Invalid choice. Please enter 1 or 2.${NC}" >&2 + echo -e "${RED}Invalid choice. Please enter 1, 2 or 3.${NC}" >&2 ;; esac done @@ -2063,6 +2267,185 @@ EOF fi } +# Function to configure Traefik to route a domain to an EXTERNAL web service (Traefik Mode) +# The backend is a remote URL (e.g. https://app.lovable.app/), not a local container. +configure_traefik_external() { + log_info "Configuring Traefik locally to route ${DOMAIN_NAME} to external service ${EXTERNAL_URL} ..." + + # Validate and parse the external URL into scheme / host / port / base path + if ! parse_external_url; then + log_error "Invalid EXTERNAL_URL '${EXTERNAL_URL}'" + log_error "Expected format: https://host[:port][/path] (e.g. https://cornersendbox-impact-vms.lovable.app/)" + exit 1 + fi + + log_info "External target: ${EXT_SCHEME}://${EXT_HOST}:${EXT_PORT} (base path: ${EXT_BASE_PATH:-none})" + + # Split DOMAIN_NAME into domain part and optional sub-path part + local domain_part + local path_part="" + if [[ "$DOMAIN_NAME" == *"/"* ]]; then + domain_part=$(echo "$DOMAIN_NAME" | cut -d'/' -f1) + path_part="/$(echo "$DOMAIN_NAME" | cut -d'/' -f2-)" + path_part="${path_part%/}" + if [ -z "$path_part" ]; then + path_part="" + fi + log_info "Detected sub-path configuration: domain=${domain_part}, path=${path_part}" + else + domain_part="$DOMAIN_NAME" + fi + + # Non-fatal reachability hint (Traefik does the real resolution from its container) + check_external_service_reachable + + # Middleware chain: strip our sub-path, then re-apply the origin base path + local prefix_middlewares=() + if [ -n "$path_part" ]; then + prefix_middlewares+=("${domain_part}-strip-prefix") + fi + if [ -n "$EXT_BASE_PATH" ]; then + prefix_middlewares+=("${domain_part}-add-prefix") + fi + + # Router rule: whole domain, or domain plus sub-path + local router_rule="Host(\`${domain_part}\`)" + if [ -n "$path_part" ]; then + router_rule="${router_rule} && PathPrefix(\`${path_part}\`)" + fi + + local temp_config="/tmp/app-ext-service.yml" + + # HTTPS router (TLS terminated by Traefik, Let's Encrypt certificate) + cat > "$temp_config" << EOF +http: + routers: + ${domain_part}-https: + rule: '${router_rule}' + service: ${domain_part}-ext + tls: + certResolver: letsencrypt + entryPoints: + - websecure +EOF + + if [ ${#prefix_middlewares[@]} -gt 0 ]; then + printf ' middlewares:\n' >> "$temp_config" + for mw in "${prefix_middlewares[@]}"; do + printf ' - %s\n' "$mw" >> "$temp_config" + done + fi + + # HTTP router (redirect to HTTPS) + cat >> "$temp_config" << EOF + ${domain_part}-http: + rule: '${router_rule}' + service: ${domain_part}-ext + entryPoints: + - web + middlewares: + - redirect-to-https +EOF + + if [ ${#prefix_middlewares[@]} -gt 0 ]; then + for mw in "${prefix_middlewares[@]}"; do + printf ' - %s\n' "$mw" >> "$temp_config" + done + fi + + # Middleware definitions (only when a sub-path or origin base path is used) + if [ ${#prefix_middlewares[@]} -gt 0 ]; then + printf ' middlewares:\n' >> "$temp_config" + if [ -n "$path_part" ]; then + cat >> "$temp_config" << EOF + ${domain_part}-strip-prefix: + stripPrefix: + prefixes: + - ${path_part} +EOF + fi + if [ -n "$EXT_BASE_PATH" ]; then + cat >> "$temp_config" << EOF + ${domain_part}-add-prefix: + addPrefix: + prefix: ${EXT_BASE_PATH} +EOF + fi + fi + + # Backend service: absolute URL of the external service, origin Host header + cat >> "$temp_config" << EOF + services: + ${domain_part}-ext: + loadBalancer: + servers: + - url: '${EXT_SCHEME}://${EXT_HOST}:${EXT_PORT}' + passHostHeader: false +EOF + + # Write the dynamic configuration into the Traefik volume + local config_file_name="app-ext-${domain_part}.yml" + local TRAEFIK_VOLUME_NAME="traefik_data" + + local config_exists + config_exists=$(docker run --rm -v "${TRAEFIK_VOLUME_NAME}:/etc/traefik" alpine:latest ls -la /etc/traefik/config/ 2>/dev/null | grep "$config_file_name" || true) + + if [ -n "$config_exists" ]; then + log_warn "Configuration file $config_file_name already exists, it will be overwritten" + log_info "This will update the existing external service configuration for ${DOMAIN_NAME}" + else + log_info "Creating new external service configuration for domain ${domain_part}" + fi + + docker run --rm -v "${TRAEFIK_VOLUME_NAME}:/etc/traefik" -v "/tmp:/tmp" alpine:latest sh -c "cp /tmp/app-ext-service.yml /etc/traefik/config/$config_file_name" + rm -f "$temp_config" + + log_info "Added external service configuration ${EXTERNAL_URL} for domain ${domain_part} to Traefik (file: $config_file_name)" + + # Restart is optional - the file provider watches /etc/traefik/config + if [ "${RESTART_TRAEFIK:-false}" = "true" ]; then + docker restart traefik + log_info "Traefik restarted to apply new configuration" + else + log_info "Traefik dynamic configuration added (restart not performed, Traefik should auto-load the configuration)" + log_info "If needed, you can manually restart Traefik with: docker restart traefik" + fi + + log_info "Traefik configured to proxy https://${DOMAIN_NAME} → ${EXTERNAL_URL}" + log_info "Origin receives the Host header of ${EXT_HOST} (passHostHeader=false)" + + if [ -n "$path_part" ] || [ -n "$EXT_BASE_PATH" ]; then + log_info "Path mapping: https://${DOMAIN_NAME} → ${EXT_SCHEME}://${EXT_HOST}:${EXT_PORT}${EXT_BASE_PATH}" + fi +} + +# Function to set up a domain routed to an external web service (Traefik Mode) +setup_external_domain() { + log_info "Setting up external service domain: ${DOMAIN_NAME}" + log_info "External service configuration loaded from: ${EXT_ENV_FILE:-.env-ext-*}" + + # External URL routing is a Traefik-only capability + if [ "$FRP" = "true" ]; then + log_error "External service routing is supported in Traefik mode only (FRP=false)." + log_error "FRP mode detected - the tunnel cannot proxy to an external URL." + exit 1 + fi + + # Check if Traefik is running + if ! docker ps --filter name=^${INSTANCE_NAME}$ --format '{{.Names}}' | grep -q "^traefik$"; then + log_error "Traefik reverse proxy is not running!" + log_error "Please ensure the reverse proxy is setup first" + exit 1 + fi + + # No container / docker-network checks: the backend is an external URL + + configure_traefik_external + + log_info "External service domain ${DOMAIN_NAME} has been configured in Traefik" + log_info "Service will be accessible at: https://${DOMAIN_NAME}" +} + # Function to check if Application Service is running locally (Traefik Mode) check_app_service_local() { log_info "Checking if Application Service is running locally..." @@ -3065,15 +3448,17 @@ Usage: curl -s "/deploy-rvs-prxy.sh" | bash Automated deployment script for Reverse Proxy setup. Modes: - 1) Setup Reverse Proxy - Deploys Traefik or FRP Tunnel with Traefik - 2) Setup New Domain - Adds a new domain to existing reverse proxy - 3) Setup Port Exposure - Exposes a host port to connect to a container port - 4) Manage Configurations - List and remove existing domains and ports + 1) Setup Reverse Proxy - Deploys Traefik or FRP Tunnel with Traefik + 2) Setup New Domain - Adds a new domain to a local Docker application container + 3) Setup Port Exposure - Exposes a host port to connect to a container port + 4) Setup External Service Domain - Routes a domain to an external web service URL (Traefik mode only) + 5) Manage Configurations - List and remove existing domains, external services and ports The script automatically detects the mode based on your configuration: - If no reverse proxy exists: Setup Reverse Proxy mode - If reverse proxy exists and DOMAIN_NAME is set: Setup New Domain mode - If reverse proxy exists and HOST_PORT is set: Setup Port Exposure mode + - If reverse proxy exists and EXTERNAL_URL is set: Setup External Service Domain mode Environment Variables (in .env.rvs-prxy file): FRP=true|false Set to 'false' for Traefik mode, 'true' for FRP mode @@ -3098,6 +3483,11 @@ Port Exposure Variables (in .env-port- file): CONTAINER_NAME Name of the target Docker container CONTAINER_PORT Port number inside the container to connect to +External Service Variables (in .env-ext- file): + DOMAIN_NAME Public domain to route (sub-path allowed, e.g. app.example.com/api) + EXTERNAL_URL Upstream URL of the external web service + (e.g. https://cornersendbox-impact-vms.lovable.app/) + Examples: # First run - will trigger interactive wizard curl -s "/deploy-rvs-prxy.sh" | bash @@ -3121,10 +3511,19 @@ Port Exposure: CONTAINER_NAME="your-container-name" CONTAINER_PORT="80" Then run the script to expose host port 18081 -> container port 80 + +External Service Domain: + Create a file named .env-ext- (e.g., .env-ext-app-example-com) with: + DOMAIN_NAME="app.example.com" + EXTERNAL_URL="https://cornersendbox-impact-vms.lovable.app/" + Then run the script. Traefik terminates TLS for app.example.com (Let's Encrypt) + and proxies to the external URL. The origin receives its own Host header + (passHostHeader=false); a sub-path in DOMAIN_NAME is stripped, and any path in + EXTERNAL_URL is added back to the origin request. Traefik mode only. EOF } -# Function to manage existing domain and port configurations +# Manage existing domain, external service and port configurations manage_configurations() { echo -e "${BLUE}=============================================${NC}" >&2 echo -e "${GREEN}Reverse Proxy Configuration Manager${NC}" >&2 @@ -3137,6 +3536,7 @@ manage_configurations() { [ -f "$f" ] || continue [[ "$f" == .env.rvs-prxy* ]] && continue [[ "$f" == .env-port-* ]] && continue + [[ "$f" == .env-ext-* ]] && continue domain_files+=("$f") done @@ -3146,10 +3546,16 @@ manage_configurations() { [ -f "$f" ] && port_files+=("$f") done - local total_configs=$((${#domain_files[@]} + ${#port_files[@]})) + # Collect external service files + local ext_files=() + for f in .env-ext-*; do + [ -f "$f" ] && ext_files+=("$f") + done + + local total_configs=$((${#domain_files[@]} + ${#port_files[@]} + ${#ext_files[@]})) if [ "$total_configs" -eq 0 ]; then - log_info "No domain or port configurations found." + log_info "No domain, external service or port configurations found." return 0 fi @@ -3185,6 +3591,19 @@ manage_configurations() { echo "" >&2 fi + if [ ${#ext_files[@]} -gt 0 ]; then + echo -e "${BLUE}External Services:${NC}" >&2 + for f in "${ext_files[@]}"; do + local domain_name + domain_name=$(grep -o 'DOMAIN_NAME="[^"]*"' "$f" 2>/dev/null | sed 's/DOMAIN_NAME="//;s/"//' || echo "unknown") + local external_url + external_url=$(grep -o 'EXTERNAL_URL="[^"]*"' "$f" 2>/dev/null | sed 's/EXTERNAL_URL="//;s/"//' || echo "unknown") + echo -e " ${GREEN}${idx}.${NC} ${f} - ${domain_name} → ${external_url}" >&2 + idx=$((idx + 1)) + done + echo "" >&2 + fi + # Prompt for removal echo -e "${YELLOW}Select configurations to remove (enter numbers separated by space, or 'q' to quit):${NC}" >&2 read -r selection /dev/null | sed 's/DOMAIN_NAME="//;s/"//' || echo "") + + if [ -n "$domain_name" ]; then + log_info "Removing external service config from Traefik: ${domain_name}" + # Remove the dynamic config file from Traefik volume + # Same domain_base logic as configure_traefik_external(): + # cut -d'/' -f1 to match the actual filename + local domain_base + domain_base=$(echo "$domain_name" | cut -d'/' -f1) + docker run --rm -v "traefik_data:/etc/traefik" alpine:latest sh -c "rm -f /etc/traefik/config/app-ext-${domain_base}.yml 2>/dev/null" || true + fi + # Check if it's a domain config - if [[ "$file_to_remove" == .env-* ]] && [[ "$file_to_remove" != .env-port-* ]]; then + elif [[ "$file_to_remove" == .env-* ]] && [[ "$file_to_remove" != .env-port-* ]] && [[ "$file_to_remove" != .env-ext-* ]]; then # Remove domain config from Traefik volume local domain_name domain_name=$(grep -o 'DOMAIN_NAME="[^"]*"' "$file_to_remove" 2>/dev/null | sed 's/DOMAIN_NAME="//;s/"//' || echo "") @@ -3479,14 +3914,66 @@ main() { # Fallback for backward compatibility setup_new_domain fi + elif [ "$DEPLOY_MODE" = "external" ]; then + log_info "Setting up external service domain in existing Traefik proxy..." + + # Process all external service config files in batch mode + if [ ${#EXT_ENV_FILES[@]} -gt 0 ]; then + local total=${#EXT_ENV_FILES[@]} + local count=0 + + for env_file in "${EXT_ENV_FILES[@]}"; do + count=$((count + 1)) + + echo "" >&2 + log_info "=========================================" + log_info "Processing external service $count of $total: $env_file" + log_info "=========================================" + echo "" >&2 + + # Source the external service config file + source "$env_file" + EXT_ENV_FILE="$env_file" + + # Validate external service config + if [ -z "$DOMAIN_NAME" ]; then + log_error "DOMAIN_NAME is required in $env_file" + continue + fi + if [ -z "$EXTERNAL_URL" ]; then + log_error "EXTERNAL_URL is required in $env_file" + continue + fi + + # Setup the external service domain + setup_external_domain + done + + echo "" >&2 + log_info "=========================================" + log_info "Batch processing completed: $count external service domain(s) processed" + log_info "=========================================" + else + # Fallback for single external service config + setup_external_domain + fi elif [ "$DEPLOY_MODE" = "domain_wizard" ]; then log_info "Starting interactive wizard for domain setup..." interactive_setup_wizard - # Source the newly created domain config - if [ -n "$DOMAIN_ENV_FILE" ]; then - source "$DOMAIN_ENV_FILE" + + # The wizard may create a domain, port exposure or external service config + if [ "$DEPLOY_MODE" = "external" ] && [ -n "$EXT_ENV_FILE" ]; then + source "$EXT_ENV_FILE" + setup_external_domain + elif [ "$DEPLOY_MODE" = "port" ] && [ -n "$PORT_ENV_FILE" ]; then + source "$PORT_ENV_FILE" + setup_port_exposure + else + if [ -n "$DOMAIN_ENV_FILE" ]; then + source "$DOMAIN_ENV_FILE" + fi + setup_new_domain fi - setup_new_domain else log_info "Setting up reverse proxy..." setup_reverse_proxy