Merge pull request #403 from outis1one/claude/wolf-pair-port-conflict-7nz8qg
Claude/wolf pair port conflict 7nz8qg
This commit is contained in:
@@ -529,6 +529,8 @@ install_gitea() {
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
echo "[DRY-RUN] Would create $DIR with docker-compose.yml (gitea/gitea:latest)"
|
||||
echo "[DRY-RUN] Would scan for free host ports (web + SSH) to avoid collisions"
|
||||
echo "[DRY-RUN] Would open the SSH clone port in UFW (web port too, or scoped to caddy_net"
|
||||
echo "[DRY-RUN] if Caddy ends up fronting it locally)"
|
||||
echo "[DRY-RUN] Would prompt for a Gitea admin username/password, then create that account"
|
||||
echo "[DRY-RUN] and an API token once the container is ready (no manual web wizard)"
|
||||
echo "[DRY-RUN] Would prompt for a GitHub token and copy in gitea-github-sync.sh"
|
||||
@@ -738,6 +740,25 @@ ENV
|
||||
# replacement requiring Caddy involvement. ─────────────────────────────
|
||||
configure_caddy_for_service "Gitea" "host.docker.internal:${WEB_PORT}" "git"
|
||||
|
||||
# ── Firewall ─────────────────────────────────────────────────────────────
|
||||
# SSH clone (SSH_PORT->22) is a different protocol than the web UI — Caddy
|
||||
# can't front it no matter what CADDY_SERVICE_MODE came back as, so it
|
||||
# always needs its own direct rule or `git clone ssh://...` hangs forever
|
||||
# (a dropped SYN with UFW active, not a fast connection-refused).
|
||||
if command -v ufw &>/dev/null; then
|
||||
if [[ "$CADDY_SERVICE_CONFIGURED" == true && "$CADDY_SERVICE_MODE" == "local" ]]; then
|
||||
ufw delete allow "${WEB_PORT}/tcp" 2>/dev/null || true
|
||||
ufw_allow_from_caddy_net "${WEB_PORT}"
|
||||
else
|
||||
ufw allow "${WEB_PORT}/tcp" comment "Gitea web UI" >/dev/null 2>&1 || true
|
||||
fi
|
||||
ufw allow "${SSH_PORT}/tcp" comment "Gitea SSH clone" >/dev/null 2>&1 || true
|
||||
ensure_ufw_enabled
|
||||
log_success "UFW: opened SSH clone port ${SSH_PORT}/tcp"
|
||||
else
|
||||
log_warning "ufw not installed — if you use a firewall, open TCP ${SSH_PORT} for SSH clones."
|
||||
fi
|
||||
|
||||
_gitea_offer_authelia_sso "$DIR"
|
||||
_gitea_offer_reverse_proxy_auth "$DIR"
|
||||
_gitea_offer_actions_runner "$DIR"
|
||||
|
||||
+52
-3
@@ -174,6 +174,19 @@ CBLOCK
|
||||
[ -f "$_file" ] || return 0
|
||||
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
|
||||
}
|
||||
port_in_use() {
|
||||
local _port="$1" _proto="${2:-tcp}"
|
||||
local _flag="-tlnH"
|
||||
[ "$_proto" = "udp" ] && _flag="-ulnH"
|
||||
ss "$_flag" "sport = :${_port}" 2>/dev/null | grep -q .
|
||||
}
|
||||
find_free_port() {
|
||||
local _varname="$1" _port="$2" _proto="${3:-tcp}"
|
||||
while port_in_use "$_port" "$_proto"; do
|
||||
_port=$((_port + 1))
|
||||
done
|
||||
eval "$_varname='$_port'"
|
||||
}
|
||||
fi
|
||||
|
||||
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
|
||||
@@ -206,11 +219,29 @@ install_wolf-pair() {
|
||||
echo " - Build the wolf-pair image (python:3.12-alpine + docker-cli)"
|
||||
echo " - Run the container with network_mode: host (for localhost:47989 access)"
|
||||
echo " - Mount /var/run/docker.sock:ro (for docker logs wolf)"
|
||||
echo " - Open port $WOLFPAIR_PORT in UFW"
|
||||
echo " - Open port $WOLFPAIR_PORT in UFW (auto-scanned for a free host port —"
|
||||
echo " other services, e.g. wordpress/ntfy/beszel, default to 8090 too)"
|
||||
echo " - Optionally configure a Caddy reverse proxy"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# network_mode: host means there's no HOST:CONTAINER ports: mapping to scan
|
||||
# around a collision on — server.py binds 0.0.0.0 directly on the host, so a
|
||||
# taken 8090 (wordpress/ntfy/beszel all default here too) fails at container
|
||||
# start with "address already in use" and nothing in docker-compose.yml to
|
||||
# point at. Scan once and persist in .env; on a rerun, keep the port already
|
||||
# in use rather than silently moving it out from under an existing Caddy
|
||||
# site block / bookmarked URL.
|
||||
if [ -f "$WOLFPAIR_DIR/.env" ]; then
|
||||
local _existing_port
|
||||
_existing_port="$(grep '^WOLFPAIR_PORT=' "$WOLFPAIR_DIR/.env" 2>/dev/null | cut -d= -f2-)"
|
||||
[ -n "$_existing_port" ] && WOLFPAIR_PORT="$_existing_port"
|
||||
else
|
||||
find_free_port WOLFPAIR_PORT "$WOLFPAIR_PORT"
|
||||
fi
|
||||
[ "$WOLFPAIR_PORT" != "8090" ] && \
|
||||
log_info "Port 8090 already in use — wolf-pair will use $WOLFPAIR_PORT instead."
|
||||
|
||||
mkdir -p "$WOLFPAIR_DIR"
|
||||
ensure_docker_dir_ownership "$WOLFPAIR_DIR"
|
||||
cd "$WOLFPAIR_DIR" || return 1
|
||||
@@ -234,10 +265,11 @@ submitted — otherwise the user resubmits a dead secret and Wolf returns
|
||||
"key not found". We track submitted secrets and fall back to the waiting page
|
||||
until Moonlight initiates a brand-new pairing (which mints a new secret).
|
||||
"""
|
||||
import json, subprocess, re, urllib.request, urllib.error
|
||||
import json, os, subprocess, re, urllib.request, urllib.error
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
WOLF_HTTP = "http://localhost:47989"
|
||||
LISTEN_PORT = int(os.environ.get("WOLFPAIR_PORT", "8090"))
|
||||
|
||||
# Secrets already submitted to Wolf. Wolf erases a secret on first submit, so a
|
||||
# secret in here is dead — show the waiting page instead of re-offering it.
|
||||
@@ -390,7 +422,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
if __name__ == '__main__':
|
||||
HTTPServer.allow_reuse_address = True
|
||||
HTTPServer(('0.0.0.0', 8090), Handler).serve_forever()
|
||||
HTTPServer(('0.0.0.0', LISTEN_PORT), Handler).serve_forever()
|
||||
PYEOF
|
||||
log_success "server.py written"
|
||||
|
||||
@@ -420,12 +452,27 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
container_name: wolf-pair
|
||||
network_mode: host
|
||||
environment:
|
||||
- WOLFPAIR_PORT=${WOLFPAIR_PORT:-8090}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
restart: unless-stopped
|
||||
COMPOSE
|
||||
log_success "docker-compose.yml written"
|
||||
|
||||
# host networking means server.py binds this port directly — .env feeds it
|
||||
# to the container's WOLFPAIR_PORT (above) via docker compose's own .env
|
||||
# auto-load, same pattern as WOLF_STATE_DIR in services/wolf.sh.
|
||||
backup_if_exists "$WOLFPAIR_DIR/.env"
|
||||
cat > "$WOLFPAIR_DIR/.env" << EOF
|
||||
# Port wolf-pair's pairing UI listens on (host networking — no port mapping
|
||||
# to edit). Auto-scanned at install time to avoid clashing with other
|
||||
# services that also default to 8090 (wordpress, ntfy, beszel).
|
||||
WOLFPAIR_PORT=${WOLFPAIR_PORT}
|
||||
EOF
|
||||
chmod 600 "$WOLFPAIR_DIR/.env"
|
||||
chown "$ACTUAL_USER:$ACTUAL_USER" "$WOLFPAIR_DIR/.env"
|
||||
|
||||
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$WOLFPAIR_DIR"
|
||||
|
||||
# ── 4. Caddy (optional) ───────────────────────────────────────────────────
|
||||
@@ -491,6 +538,8 @@ docker compose logs -f # follow logs
|
||||
- If you set up a Caddy subdomain (e.g. `wolf-pair.yourdomain.com`), that
|
||||
subdomain is for the PIN form only.
|
||||
MD
|
||||
[ "$WOLFPAIR_PORT" != "8090" ] && \
|
||||
sed -i "s/localhost:8090/localhost:${WOLFPAIR_PORT}/g" "$WOLFPAIR_DIR/README.md"
|
||||
|
||||
# ── 7. Build & start ──────────────────────────────────────────────────────
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user