1) dynamic to add ENTRYPOINTS, 2) fixed the bug by manage cmd to remove sub-domain / domain

This commit is contained in:
chungyan5 2026-06-22 18:04:36 +08:00 committed by chungyan5
parent d825a46ded
commit 20c55e0c4b
6 changed files with 1734 additions and 1345 deletions

File diff suppressed because it is too large Load Diff

View File

@ -241,6 +241,116 @@ Select configurations to remove (enter numbers separated by space, or 'q' to qui
- Domain 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.)
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.
**Configuration Variable:**
- `EXTRA_ENTRYPOINTS` in `.env.rvs-prxy` (global proxy config) or per-domain `.env-<domain>` files
- Format: `name=:port/protocol,name2=:port` (comma-separated)
- Protocol defaults to `tcp` if omitted; use `/udp` for UDP entrypoints
- Example: `quic=:443/udp` creates entrypoint `quic` listening on UDP port 443
- Example: `quic=:443/udp, mqtt=:1883` adds both QUIC and a custom TCP entrypoint on port 1883
---
#### Scenario A: First-Time Setup (Traefik Not Yet Created)
The wizard asks for extra entrypoints during the Traefik configuration step. You can also pre-configure them:
1. Create `.env.rvs-prxy` with the entrypoints setting:
```bash
EXTRA_ENTRYPOINTS="quic=:443/udp"
```
2. Run the script as usual:
```bash
curl -s "https://www.ivantito.com/app-pub/demos/test/deploy-rvs-prxy.sh" | bash
```
3. **What happens:**
- The docker-compose.yml generated by the script includes the extra entrypoints in the `command:` section and their port mappings in the `ports:` section
- The Traefik container starts with `--entrypoints.quic.address=:443/udp` and `-p 443:443/udp`
- Both TCP and UDP on port 443 are now mapped (QUIC shares port 443 with HTTPS)
---
#### Scenario B: Running Traefik — Add New Extra Entrypoints
To add a new entrypoint (e.g., QUIC) to a Traefik container that is already running:
1. Set `EXTRA_ENTRYPOINTS` in `.env.rvs-prxy` (proxy config) or in the per-domain `.env-<domain>` file:
```bash
EXTRA_ENTRYPOINTS="quic=:443/udp"
```
If the variable already has values, add the new one with a comma:
```bash
EXTRA_ENTRYPOINTS="quic=:443/udp, mqtt=:1883"
```
2. Run the script:
```bash
curl -s "https://www.ivantito.com/app-pub/demos/test/deploy-rvs-prxy.sh" | bash
```
The script detects the running Traefik container and checks if `EXTRA_ENTRYPOINTS` has changed.
3. **What happens:**
- The script calls `parse_extra_entrypoints()` which parses the variable
- It inspects the current Traefik container's command-line args
- If any extra entrypoints are missing from the running container, the script:
1. Stops and removes the current Traefik container (data in `traefik_data` volume is preserved)
2. Recreates the container with all standard entrypoints plus the new extra entrypoints and their port mappings
- Brief downtime (~5 seconds) while the container is recreated
- Existing domain routing and port exposure configurations remain intact (they are stored in the volume)
4. **Expected output** (proxy setup mode — `deploy-rvs-prxy.sh`):
```
[INFO] Extra entrypoints changed, recreating Traefik container to apply...
[INFO] Updating Traefik container...
[INFO] Traefik container recreated with new entry points
```
**Expected output** (domain setup mode — `.env-p2prly1-ivantito-com`):
```
[INFO] Loading domain config from .env-p2prly1-ivantito-com
[INFO] Detected running Traefik reverse proxy
[INFO] Extra entrypoints changed, recreating Traefik container to apply...
[INFO] Checking if Application Service is running locally...
...
[INFO] Traefik configured to route traffic to Application Service
```
5. **Verify the new entrypoint is active:**
```bash
# Check container args for entrypoint definitions
docker inspect traefik --format '{{range .Config.Cmd}}{{println .}}{{end}}' | grep entrypoints
# Should show: --entrypoints.quic.address=:443/udp
# Check port mappings (TCP and UDP on 443)
docker port traefik
# Should show: 443/tcp → 0.0.0.0:443 and 443/udp → 0.0.0.0:443
```
---
#### Scenario C: Running Traefik — Remove an Extra Entrypoint
1. Edit `.env.rvs-prxy` and remove the entrypoint from `EXTRA_ENTRYPOINTS`
2. Run the script to trigger the recreation with the updated entrypoints (same as Scenario B steps 2-4)
---
#### Scenario D: Running Traefik — Add/Remove During Port Exposure Setup
When you use port exposure (`--manage` or `.env-port-<port>`), the `add_traefik_entry_point()` and `manage_configurations()` functions automatically preserve all `EXTRA_ENTRYPOINTS`. Extra entrypoints are re-injected whenever the Traefik container is recreated for port operations, so they are never lost.
**No additional steps needed** — extra entrypoints configured in `.env.rvs-prxy` are automatically preserved across all port management operations.
---
**Important Notes:**
- Recreating the Traefik container causes ~5 seconds of downtime
- The `traefik_data` volume (config files, SSL certificates, ACME data) is preserved across recreations
- `EXTRA_ENTRYPOINTS` adds entrypoints to the Traefik container's command-line args. When a domain config also has a UDP port in `LOCAL_SERVICE_PORT` (e.g., `80,4443/udp`), the script automatically creates a Traefik UDP router in the domain config, so no manual routing setup is needed for that UDP port
- For port-mapped entrypoints (like port-exposure), existing `--manage` flows already handle adding/removing
## Run this System
### Using with curl (Remote Execution)
@ -400,6 +510,8 @@ When the FRP reverse proxy is already running, you can add new domains:
- **Configuration Files:**
- `.env.rvs-prxy` - Reverse proxy instance configuration (contains `INSTANCE_NAME`, `FRP_SERVER_ADDR`, etc.)
- `.env-<domain>` - Domain-specific configuration (contains `DOMAIN_NAME`, `LOCAL_SERVICE_NAME`, `LOCAL_SERVICE_PORT`)
- `LOCAL_SERVICE_PORT` supports comma-separated ports with protocol: `80` or `80,4443/udp`
- First TCP port is used for HTTPS routing; additional UDP ports get automatic Traefik UDP router config
- **What Happens:**
- A new TCP proxy is added to frpc.toml for the domain's service
@ -428,6 +540,9 @@ If environment variables are not set, the script will prompt for:
#### 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.
```bash
# Start/run container
docker run -d --name traefik --restart=unless-stopped \
@ -453,8 +568,11 @@ docker inspect traefik
# Remove container
docker rm -f traefik
# Access Traefik Dashboard
http://localhost:8080
# Access Traefik Dashboard (port from DASHBOARD_PORT, default 18080)
http://localhost:${DASHBOARD_PORT:-18080}/dashboard/
# Check docker-compose.yml used to create the container (preserved after each run)
cat /tmp/docker-compose.yml
# Complete removal (including data)
docker rm -f traefik

View File

@ -109,13 +109,15 @@
**Required Variables (only 3):**
- `DOMAIN_NAME` - Domain name for reverse proxy routing (e.g., `app.example.com`)
- `LOCAL_SERVICE_NAME` - Name of the local Docker application container
- `LOCAL_SERVICE_PORT` - Port number of the local application service
- `LOCAL_SERVICE_PORT` - Port(s) of the local application service, comma-separated with optional protocol suffix (e.g., `80` or `80,4443/udp`)
**Example `.env-app-example-com`:**
```bash
DOMAIN_NAME="app.example.com"
LOCAL_SERVICE_NAME="my-app-container"
LOCAL_SERVICE_PORT="3000"
LOCAL_SERVICE_PORT="3000" # Single port (TCP)
# LOCAL_SERVICE_PORT="80,4443/udp" # Multiple ports (TCP + UDP)
```
**Reverse Proxy Instance Configuration (`.env.rvs-prxy`):**
@ -127,6 +129,7 @@ LOCAL_SERVICE_PORT="3000"
- `INSTANCE_NAME` - Container name for Traefik instance (default: `traefik`)
- `DOCKER_NETWORK` - Docker network name for container connectivity (default: `traefik-public`)
- `DASHBOARD_PORT` - Traefik dashboard port exposed on localhost (default: `18080`)
- `EXTRA_ENTRYPOINTS` - Additional Traefik entrypoints beyond defaults (web/websecure), format: `name=:port/protocol,name2=:port`, e.g., `quic=:443/udp` for HTTP/3 QUIC support (default: empty). Can be set in `.env.rvs-prxy` or per-domain `.env-<domain>` files
- `TRAEFIK_VER` - Traefik version (default: `v3.6.9`)
- `LETSENCRYPT_EMAIL` - Email for Let's Encrypt certificates
- `RESTART_TRAEFIK` - Whether to restart Traefik after config update (`true`/`false`, default: `false`)
@ -146,7 +149,7 @@ LOCAL_SERVICE_PORT="3000"
**Required Variables:**
- `DOMAIN_NAME` - Domain name for reverse proxy routing (e.g., `app.example.com`, `sub.domain.org.hk`)
- `LOCAL_SERVICE_NAME` - Name of the local Docker application container
- `LOCAL_SERVICE_PORT` - Port number of the local application service
- `LOCAL_SERVICE_PORT` - Port(s) of the local application service, comma-separated with optional protocol suffix (e.g., `80` or `80,4443/udp`)
**Port-Based Routing Configuration (`.env-port-<port>`):**

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
# Before Starting Any Work
- Please read the following documents carefully **before taking any actions** on this project: `README.md`, `Design_Doc/Spec.md`, `Design_Doc/Operations.md` and `Design_Doc/Tech_Desc.md`. You may skip the `Design_Doc/Tech_Desc.md` -> "AI Prompt" section and its sub-sections when reading. These files provide essential background information and project understanding. Only after reviewing them should you proceed with the following tasks.
- Please read the following documents carefully **before taking any actions** on this project: `README.md`, `Design_Doc/Spec.md`, `Design_Doc/Operations.md` and `Design_Doc/Tech_Desc.md`. These files provide essential background information and project understanding. Only after reviewing them should you proceed with the following tasks.
# Your Current Tasks
- Listing the specific tasks for this session.
@ -10,6 +11,7 @@ Whenever you finish making code changes, please take a few minutes to update the
This keeps everything clear and up-to-date for everyone on the team.
## Which files might need an update?
Only touch the files that your changes actually affect. The most common ones are:
- `README.md`
@ -18,45 +20,11 @@ Only touch the files that your changes actually affect. The most common ones are
- `Design_Doc/Tech_Desc.md`
## How should you update them?
Just follow the style and purpose each file already has.
(You can check the "Documentation Overview" part in `README.md` if you're not sure.)
---
## ⚠️ CRITICAL RULE for `Design_Doc/Tech_Desc.md` - The "AI Prompt" Section
**The "AI Prompt" section at the end of `Design_Doc/Tech_Desc.md` must NEVER be modified, deleted, or rewritten by you.**
### What is the "AI Prompt" section?
- It is the section that starts with `# AI Prompt` (level-1 heading)
- It is always located at the **very end** of `Design_Doc/Tech_Desc.md`
- It contains sub-sections like `## AI Prompt habit as a reminder`, `## Script Code`, `## Doc.`
- It contains **task history and instructions from previous sessions** written by the user
### Why must you NOT touch it?
- This section is **written and maintained by the user only**
- It contains **accumulated task history** that the user wants to preserve
- It is **not documentation** about the project - it is a **working log of tasks**
- Other team members (including the user) will update this section manually when needed
### What you MUST do:
✅ **Leave the "AI Prompt" section completely unchanged**
✅ **Keep the exact content** that is currently there (do not revert, do not update, do not clean up)
✅ **Only edit the technical documentation sections** that come BEFORE the "AI Prompt" section
✅ **Ensure "AI Prompt" stays at the end** of the file (do not move it)
### What you MUST NOT do:
❌ **Do NOT delete** the "AI Prompt" section
❌ **Do NOT modify** any words, sentences, or formatting inside "AI Prompt"
❌ **Do NOT revert** it to an older version
❌ **Do NOT clean up** or reorganize its content
❌ **Do NOT add** new task results to it (the user does this manually)
❌ **Do NOT remove** task history from it
### Simple rule to remember:
> **Everything before `# AI Prompt` = Your responsibility to update**
> **Everything from `# AI Prompt` to the end = User's responsibility, DO NOT TOUCH**
---
Thank you so much for helping keep the documents accurate and the task history preserved — it makes a big difference to the whole team!
BACKUP before you touch the above mentioned documents:
- you make a doc. backup folder `Design_Doc/bck/` if not exist, then before any your touch of documents, such as: `Design_Doc/Tech_Desc.md`, you copy that current version into eg. `Design_Doc/bck/Design_Doc/Tech_Desc_YYYYMMDDHHMMSS.md`. The other document files as same approach by keeping the path and file name with Date and Time at file name into backup folder.

View File

@ -34,6 +34,12 @@ INSTANCE_NAME="traefik" # Default container name, can be overridden in .env.rvs
DOCKER_NETWORK="${DOCKER_NETWORK}" # Default Docker network name, can be overridden in .env.rvs-prxy
DASHBOARD_PORT="18080" # Default dashboard port, can be overridden in .env.rvs-prxy
# Extra entrypoints beyond default web (:80/tcp) and websecure (:443/tcp)
# Format: comma-separated list of name=address
# Example: "quic=:443/udp, custom-tcp=:8888"
# Address format: :port or :port/protocol (default protocol: tcp)
EXTRA_ENTRYPOINTS=""
# Target server variables (for Traefik mode - now running locally)
LOCAL_SERVICE_NAME=""
LOCAL_SERVICE_PORT=""
@ -411,14 +417,16 @@ interactive_setup_wizard() {
fi
done
# Get service port
echo -e "Enter your application's internal port:" >&2
# Get service port(s)
echo -e "Enter your application's internal port(s):" >&2
echo -e " Single port: 80" >&2
echo -e " Multiple: 80,4443/udp (comma-separated, protocol defaults to tcp)" >&2
while [ -z "$LOCAL_SERVICE_PORT" ]; do
LOCAL_SERVICE_PORT=$(read_terminal "Service port (default: 3000): " "3000")
if [ -z "$LOCAL_SERVICE_PORT" ]; then
LOCAL_SERVICE_PORT="3000"
elif ! [[ "$LOCAL_SERVICE_PORT" =~ ^[0-9]+$ ]] || [ "$LOCAL_SERVICE_PORT" -lt 1 ] || [ "$LOCAL_SERVICE_PORT" -gt 65535 ]; then
echo -e "${RED}Invalid port. Please enter a number between 1 and 65535.${NC}" >&2
elif ! [[ "$LOCAL_SERVICE_PORT" =~ ^[0-9]+(/tcp|/udp)?(,[0-9]+(/tcp|/udp)?)*$ ]]; then
echo -e "${RED}Invalid format. Use comma-separated port/protocol, e.g. 80 or 80,4443/udp${NC}" >&2
LOCAL_SERVICE_PORT=""
fi
done
@ -578,6 +586,16 @@ interactive_setup_wizard() {
save_to_env "LETSENCRYPT_EMAIL" "$LETSENCRYPT_EMAIL" ".env.rvs-prxy"
echo "" >&2
# Extra entrypoints (e.g., QUIC)
echo -e "Extra Traefik entrypoints (optional):" >&2
echo -e " Format: name=:port/protocol (comma-separated)" >&2
echo -e " Example: quic=:443/udp enables HTTP/3 QUIC on port 443/udp" >&2
EXTRA_ENTRYPOINTS=$(read_terminal "Extra entrypoints (leave empty for none): " "")
if [ -n "$EXTRA_ENTRYPOINTS" ]; then
save_to_env "EXTRA_ENTRYPOINTS" "$EXTRA_ENTRYPOINTS" ".env.rvs-prxy"
fi
echo "" >&2
# Optional: Debug mode
DEBUG_MODE=$(read_terminal "Enable debug mode? (y/N): " "n")
if [[ "$DEBUG_MODE" =~ ^[Yy]$ ]]; then
@ -1433,6 +1451,63 @@ local_volume_copy() {
docker run --rm -v "$volume:$dest" -v "$(dirname "$source"):/tmp/source:ro" alpine:latest cp -r "/tmp/source/$(basename "$source")" "$dest/"
}
# Function to parse EXTRA_ENTRYPOINTS variable and produce entrypoint args and port mappings
# Globals read: EXTRA_ENTRYPOINTS
# Globals set: EXTRA_ENTRYPOINT_ARGS (array of --entrypoints.NAME.address=ADDRESS)
# EXTRA_PORT_MAPPINGS (array of port mappings for docker-compose/docker-run)
# EXTRA_COMPOSE_ENTRYPOINTS (string of YAML command entries for compose)
# EXTRA_COMPOSE_PORTS (string of YAML port entries for compose)
parse_extra_entrypoints() {
EXTRA_ENTRYPOINT_ARGS=()
EXTRA_PORT_MAPPINGS=()
EXTRA_COMPOSE_ENTRYPOINTS=""
EXTRA_COMPOSE_PORTS=""
if [ -z "$EXTRA_ENTRYPOINTS" ]; then
return
fi
local saved_ifs="$IFS"
IFS=','
for entry in $EXTRA_ENTRYPOINTS; do
IFS="$saved_ifs"
entry=$(echo "$entry" | xargs)
local name="${entry%%=*}"
local address="${entry#*=}"
address=$(echo "$address" | xargs)
# Determine port and protocol from address
local port="${address#:}"
local protocol="tcp"
if [[ "$port" == *"/"* ]]; then
protocol="${port#*/}"
port="${port%%/*}"
fi
# Build entrypoint arg
EXTRA_ENTRYPOINT_ARGS+=("--entrypoints.${name}.address=${address}")
# Build port mapping
if [ "$protocol" = "udp" ]; then
EXTRA_PORT_MAPPINGS+=("${port}:${port}/udp")
else
EXTRA_PORT_MAPPINGS+=("${port}:${port}")
fi
# Build compose YAML strings (with real newlines for heredoc expansion)
EXTRA_COMPOSE_ENTRYPOINTS="${EXTRA_COMPOSE_ENTRYPOINTS} - '--entrypoints.${name}.address=${address}'
"
if [ "$protocol" = "udp" ]; then
EXTRA_COMPOSE_PORTS="${EXTRA_COMPOSE_PORTS} - '${port}:${port}/udp'
"
else
EXTRA_COMPOSE_PORTS="${EXTRA_COMPOSE_PORTS} - '${port}:${port}'
"
fi
done
IFS="$saved_ifs"
}
# Function to check if Traefik container exists locally (Traefik Mode)
check_traefik_container() {
log_info "Checking if Traefik container exists locally..."
@ -1464,8 +1539,33 @@ create_traefik_container_local() {
if [ -n "$container_exists" ] && [ "$container_exists" = "$TRAEFIK_CONTAINER_NAME" ]; then
log_info "Traefik container '$TRAEFIK_CONTAINER_NAME' already exists locally"
# Only update the config files in the volume, don't recreate the container
log_info "Updating configuration in existing Traefik container..."
# Parse extra entrypoints and check if container needs recreation
parse_extra_entrypoints
if [ ${#EXTRA_ENTRYPOINT_ARGS[@]} -gt 0 ]; then
# Check if entrypoints are already in the container
local current_cmd
current_cmd=$(docker inspect traefik --format='{{json .Config.Cmd}}' 2>/dev/null || echo "[]")
local needs_recreate=false
for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
if ! echo "$current_cmd" | grep -qF -- "${ep_arg}"; then
needs_recreate=true
break
fi
done
if [ "$needs_recreate" = true ]; then
log_info "Extra entrypoints changed, recreating Traefik container to apply..."
# Re-enter the creation path by removing the container reference
docker stop traefik 2>/dev/null || true
sleep 2
docker rm traefik 2>/dev/null || true
container_exists=""
fi
fi
fi
if [ -n "$container_exists" ] && [ "$container_exists" = "$TRAEFIK_CONTAINER_NAME" ]; then
log_info "Traefik container '$TRAEFIK_CONTAINER_NAME' exists, updating configuration..."
# Use specified Traefik version or default to v3.6.9
local TRAEFIK_VER="${TRAEFIK_VER:-v3.6.9}"
@ -1575,6 +1675,9 @@ EOF_HEREDOC
docker run --rm -v "$TRAEFIK_VOLUME_NAME:/etc/traefik" -v "/tmp:/tmp" alpine:latest sh -c 'mkdir -p /etc/traefik/config /etc/traefik/acme && cp /tmp/traefik.yml /etc/traefik/config/traefik.yml && touch /etc/traefik/acme/acme.json && chmod 600 /etc/traefik/acme/acme.json'
rm -f "$temp_config"
# Parse extra entrypoints for dynamic entrypoint/port configuration
parse_extra_entrypoints
# Determine Docker version to decide if we need the version field
local compose_version_field
docker_version=$(docker --version | sed 's/Docker version //' | sed 's/,.*//')
@ -1607,7 +1710,7 @@ services:
- '--providers.docker.exposedbydefault=false'
- '--entrypoints.web.address=:80'
- '--entrypoints.websecure.address=:443'
- '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
${EXTRA_COMPOSE_ENTRYPOINTS} - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
- '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
- '--certificatesresolvers.letsencrypt.acme.email=$LETSENCRYPT_EMAIL'
- '--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json'
@ -1616,7 +1719,7 @@ services:
ports:
- '80:80'
- '443:443'
- "127.0.0.1:${DASHBOARD_PORT}:8080" # Bind dashboard to localhost only for security
${EXTRA_COMPOSE_PORTS} - "127.0.0.1:${DASHBOARD_PORT}:8080" # Bind dashboard to localhost only for security
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- $TRAEFIK_VOLUME_NAME:/etc/traefik
@ -1650,7 +1753,7 @@ services:
- '--providers.docker.exposedbydefault=false'
- '--entrypoints.web.address=:80'
- '--entrypoints.websecure.address=:443'
- '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
${EXTRA_COMPOSE_ENTRYPOINTS} - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'
- '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
- '--certificatesresolvers.letsencrypt.acme.email=$LETSENCRYPT_EMAIL'
- '--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json'
@ -1659,7 +1762,7 @@ services:
ports:
- '80:80'
- '443:443'
- "127.0.0.1:${DASHBOARD_PORT}:8080" # Bind dashboard to localhost only for security
${EXTRA_COMPOSE_PORTS} - "127.0.0.1:${DASHBOARD_PORT}:8080" # Bind dashboard to localhost only for security
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- $TRAEFIK_VOLUME_NAME:/etc/traefik
@ -1693,9 +1796,12 @@ EOF
# Start Traefik container using appropriate command
$compose_cmd -f "$temp_compose" up -d
cp "$temp_compose" /tmp/docker-compose.yml
rm -f "$temp_compose"
log_info "Traefik container '$TRAEFIK_CONTAINER_NAME' is running locally using image: $TRAEFIK_IMAGE with volume: $TRAEFIK_VOLUME_NAME"
log_info "Docker compose file saved to /tmp/docker-compose.yml for reference"
log_info "Traefik dashboard: http://localhost:${DASHBOARD_PORT}/dashboard/"
# Wait a bit for the container to start
sleep 3
@ -1707,6 +1813,38 @@ EOF
configure_traefik_local() {
log_info "Configuring Traefik locally to connect to Application Service..."
# Parse LOCAL_SERVICE_PORT for multi-port support
# Format: comma-separated port/protocol entries (protocol defaults to tcp)
# Example: "80,4443/udp" → two ports: 80/tcp and 4443/udp
local service_ports=()
local service_protocols=()
local first_tcp_port=""
local saved_ifs="$IFS"
local entry port protocol
IFS=','
for entry in $LOCAL_SERVICE_PORT; do
IFS="$saved_ifs"
entry=$(echo "$entry" | xargs)
port="$entry"
protocol="tcp"
if [[ "$port" == *"/"* ]]; then
protocol="${port#*/}"
port="${port%%/*}"
fi
service_ports+=("$port")
service_protocols+=("$protocol")
if [ -z "$first_tcp_port" ] && [ "$protocol" = "tcp" ]; then
first_tcp_port="$port"
fi
done
IFS="$saved_ifs"
if [ -z "$first_tcp_port" ]; then
log_error "No TCP port found in LOCAL_SERVICE_PORT ($LOCAL_SERVICE_PORT)"
log_error "At least one TCP port is required for HTTP routing"
exit 1
fi
# Check if the target service is running
local service_running
service_running=$(docker ps --filter name="$LOCAL_SERVICE_NAME" --format '{{.Names}}' 2>/dev/null || true)
@ -1785,7 +1923,7 @@ http:
${LOCAL_SERVICE_NAME}-service:
loadBalancer:
servers:
- url: 'http://${LOCAL_SERVICE_NAME}:${LOCAL_SERVICE_PORT}'
- url: 'http://${LOCAL_SERVICE_NAME}:${first_tcp_port}'
passHostHeader: true
EOF
else
@ -1811,11 +1949,71 @@ http:
${LOCAL_SERVICE_NAME}-service:
loadBalancer:
servers:
- url: 'http://${LOCAL_SERVICE_NAME}:${LOCAL_SERVICE_PORT}'
- url: 'http://${LOCAL_SERVICE_NAME}:${first_tcp_port}'
passHostHeader: true
EOF
fi
# Generate UDP/TCP router config for non-HTTP service ports (e.g., UDP relay ports)
local udp_routers_added=false
if [ ${#service_ports[@]} -gt 1 ]; then
parse_extra_entrypoints
local udp_ep_ports=()
local udp_ep_names=()
for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
local ep_name="${ep_arg#--entrypoints.}"
ep_name="${ep_name%%.address=*}"
local ep_addr="${ep_arg#*=}"
if [[ "$ep_addr" == *"/udp" ]]; then
local ep_port="${ep_addr#:}"
ep_port="${ep_port%/udp}"
udp_ep_ports+=("$ep_port")
udp_ep_names+=("$ep_name")
fi
done
for i in "${!service_ports[@]}"; do
if [ "${service_protocols[$i]}" = "udp" ]; then
local container_port="${service_ports[$i]}"
local matching_ep=""
for j in "${!udp_ep_ports[@]}"; do
if [ "${udp_ep_ports[$j]}" = "$container_port" ]; then
matching_ep="${udp_ep_names[$j]}"
break
fi
done
if [ -z "$matching_ep" ] && [ ${#udp_ep_names[@]} -gt 0 ]; then
matching_ep="${udp_ep_names[0]}"
fi
if [ -n "$matching_ep" ]; then
cat >> "$temp_config" << EOF
udp:
routers:
${LOCAL_SERVICE_NAME}-${matching_ep}:
entryPoints:
- ${matching_ep}
service: ${LOCAL_SERVICE_NAME}-udp
services:
${LOCAL_SERVICE_NAME}-udp:
loadBalancer:
servers:
- address: "${LOCAL_SERVICE_NAME}:${container_port}"
EOF
udp_routers_added=true
log_info "Added UDP routing: entrypoint '${matching_ep}' → ${LOCAL_SERVICE_NAME}:${container_port}"
else
log_warn "UDP port ${container_port} in LOCAL_SERVICE_PORT has no matching EXTRA_ENTRYPOINTS entry"
log_warn "Add e.g. EXTRA_ENTRYPOINTS=\"myudp=:${container_port}/udp\" to your .env file"
fi
fi
done
fi
# Generate a unique configuration file name based on the domain and service name to avoid conflicts
# Use the domain (without path) and service name to create a unique identifier
local domain_base=$(echo "$DOMAIN_NAME" | cut -d'/' -f1)
@ -2517,6 +2715,19 @@ add_traefik_entry_point() {
log_info "Updating Traefik container with new entry point '${entry_point_name}'..."
log_info "Using Let's Encrypt email: ${letsencrypt_email}"
# Parse extra entrypoints from config to include them in the recreation
parse_extra_entrypoints
# Build extra entrypoint args and port mappings
local extra_ep_args=""
local extra_port_maps=""
for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
extra_ep_args="${extra_ep_args} ${ep_arg}"
done
for pm in "${EXTRA_PORT_MAPPINGS[@]}"; do
extra_port_maps="${extra_port_maps} -p ${pm}"
done
# Stop current container
log_info "Stopping Traefik container..."
docker stop traefik 2>/dev/null || true
@ -2525,7 +2736,7 @@ add_traefik_entry_point() {
# Remove old container (keep volume)
docker rm traefik 2>/dev/null || true
# Recreate container with all standard entry points plus the new one
# Recreate container with all standard entry points plus the new one and extras
docker run -d \
--name ${INSTANCE_NAME} \
--restart=unless-stopped \
@ -2534,6 +2745,7 @@ add_traefik_entry_point() {
-p 443:443 \
-p "127.0.0.1:${DASHBOARD_PORT}:8080" \
-p "${port}:${port}" \
${extra_port_maps} \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v traefik_data:/etc/traefik \
"${current_image}" \
@ -2543,6 +2755,7 @@ add_traefik_entry_point() {
--entrypoints.web.address=:80 \
--entrypoints.websecure.address=:443 \
"--entrypoints.${entry_point_name}.address=:${port}" \
${extra_ep_args} \
--certificatesresolvers.letsencrypt.acme.httpchallenge=true \
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \
"--certificatesresolvers.letsencrypt.acme.email=${letsencrypt_email}" \
@ -2575,6 +2788,29 @@ setup_new_domain() {
exit 1
fi
# Check if extra entrypoints need to be applied to the Traefik container
if [ -n "$EXTRA_ENTRYPOINTS" ]; then
parse_extra_entrypoints
if [ ${#EXTRA_ENTRYPOINT_ARGS[@]} -gt 0 ]; then
local current_cmd
current_cmd=$(docker inspect traefik --format='{{json .Config.Cmd}}' 2>/dev/null || echo "[]")
local needs_recreate=false
for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
if ! echo "$current_cmd" | grep -qF -- "${ep_arg}"; then
needs_recreate=true
break
fi
done
if [ "$needs_recreate" = true ]; then
log_info "Extra entrypoints changed, recreating Traefik container to apply..."
docker stop traefik 2>/dev/null || true
sleep 2
docker rm traefik 2>/dev/null || true
create_traefik_container_local
fi
fi
fi
# Check if Application Service exists locally
if ! check_app_service_local; then
log_error "Application Service not found locally. Please ensure the service '$LOCAL_SERVICE_NAME' is running."
@ -2847,7 +3083,7 @@ Environment Variables (in .env.rvs-prxy file):
Traefik Mode Variables (FRP=false):
LOCAL_SERVICE_NAME Name of the local Docker application container
LOCAL_SERVICE_PORT Port number of the local application service
LOCAL_SERVICE_PORT Port(s) of the local app service, e.g. 80 or 80,4443/udp
DOMAIN_NAME Domain name for reverse proxy routing
TRAEFIK_VER Traefik version (default: v3.6.9)
LETSENCRYPT_EMAIL Email for Let's Encrypt certificates
@ -2983,10 +3219,10 @@ manage_configurations() {
if [ -n "$domain_name" ] && [ -n "$service_name" ]; then
log_info "Removing domain config from Traefik: ${domain_name}"
# Remove the dynamic config file from Traefik volume
# Use specific pattern: app-service-{domain}-{service}.yml
# This avoids accidentally removing port config files that share the same service name
# Must use same domain_base logic as configure_traefik_local():
# cut -d'/' -f1 (not dots-to-dashes) to match actual filename
local domain_base
domain_base=$(echo "$domain_name" | sed 's/\./-/g')
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-service-${domain_base}-${service_name}.yml 2>/dev/null" || true
fi
@ -3040,17 +3276,39 @@ manage_configurations() {
local existing_entrypoints
existing_entrypoints=$(echo "$current_cmd" | grep -o 'entrypoints\.[a-zA-Z0-9_-]*\.address=[^"]*' | tr -d '"' || echo "")
# Build new entry point list excluding removed ports
local new_entrypoint_args="--entrypoints.web.address=:80 --entrypoints.websecure.address=:443"
local new_port_mappings="-p 80:80 -p 443:443 -p 127.0.0.1:${DASHBOARD_PORT}:8080"
# Also include extra entrypoints from EXTRA_ENTRYPOINTS config
parse_extra_entrypoints
local extra_ep_args=""
local extra_port_maps=""
for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
extra_ep_args="${extra_ep_args} ${ep_arg}"
done
for pm in "${EXTRA_PORT_MAPPINGS[@]}"; do
extra_port_maps="${extra_port_maps} -p ${pm}"
done
# Parse existing entry points
# Build new entry point list excluding removed ports
local new_entrypoint_args="--entrypoints.web.address=:80 --entrypoints.websecure.address=:443 ${extra_ep_args}"
local new_port_mappings="-p 80:80 -p 443:443 -p 127.0.0.1:${DASHBOARD_PORT}:8080 ${extra_port_maps}"
# Parse existing entry points (skip web/websecure and extra entries already covered)
while IFS= read -r ep; do
if [ -n "$ep" ]; then
local ep_name
ep_name=$(echo "$ep" | sed 's/entrypoints\.\(.*\)\.address=.*/\1/')
local ep_address
ep_address=$(echo "$ep" | sed 's/entrypoints\.[a-zA-Z0-9_-]*\.address=//')
local ep_port
ep_port=$(echo "$ep" | grep -o ':[0-9]*' | sed 's/://')
ep_port=$(echo "$ep_address" | sed 's/^://' | sed 's/\/.*//')
# Check if this is already covered by extra entrypoints
local already_covered=false
for ep_arg in "${EXTRA_ENTRYPOINT_ARGS[@]}"; do
if echo "$ep_arg" | grep -q "entrypoints.${ep_name}.address"; then
already_covered=true
break
fi
done
# Check if this port is being removed
local should_skip=false
@ -3061,11 +3319,16 @@ manage_configurations() {
fi
done
if [ "$should_skip" = false ] && [ "$ep_name" != "web" ] && [ "$ep_name" != "websecure" ]; then
new_entrypoint_args="${new_entrypoint_args} --entrypoints.${ep_name}.address=:${ep_port}"
if [ "$should_skip" = false ] && [ "$ep_name" != "web" ] && [ "$ep_name" != "websecure" ] && [ "$already_covered" = false ]; then
new_entrypoint_args="${new_entrypoint_args} --entrypoints.${ep_name}.address=${ep_address}"
# Handle protocol suffix in port mapping
if [[ "$ep_address" == *"/udp" ]]; then
new_port_mappings="${new_port_mappings} -p ${ep_port}:${ep_port}/udp"
else
new_port_mappings="${new_port_mappings} -p ${ep_port}:${ep_port}"
fi
fi
fi
done <<< "$existing_entrypoints"
# Stop and remove current container