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,53 +11,20 @@ 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`
- `Design_Doc/Spec.md`
- `Design_Doc/Operations.md`
- `Design_Doc/Operations.md`
- `Design_Doc/Tech_Desc.md`
## How should you update them?
Just follow the style and purpose each file already has.
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
@ -577,6 +585,16 @@ interactive_setup_wizard() {
done
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")
@ -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,9 +3319,14 @@ 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}"
new_port_mappings="${new_port_mappings} -p ${ep_port}:${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"