From 685488c7d45ece787ca29b86f87e6be104d365b8 Mon Sep 17 00:00:00 2001 From: alexz Date: Sat, 17 Jan 2026 10:22:01 +0000 Subject: [PATCH] cistech-tunnel: Mount entrypoint.sh from shared folder No more image rebuild needed for script changes. Co-Authored-By: Claude Opus 4.5 --- apps/cistech-tunnel/docker-compose.json | 4 +- apps/cistech-tunnel/shared/entrypoint.sh | 224 +++++++++++++++++++++++ 2 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 apps/cistech-tunnel/shared/entrypoint.sh diff --git a/apps/cistech-tunnel/docker-compose.json b/apps/cistech-tunnel/docker-compose.json index 78dd46b..1247e8a 100755 --- a/apps/cistech-tunnel/docker-compose.json +++ b/apps/cistech-tunnel/docker-compose.json @@ -19,7 +19,9 @@ ], "volumes": [ { "hostPath": "${APP_DATA_DIR}/data", "containerPath": "/root" }, - { "hostPath": "${APP_DATA_DIR}", "containerPath": "/runtime" } + { "hostPath": "${APP_DATA_DIR}", "containerPath": "/runtime" }, + { "hostPath": "/etc/runtipi/repos/runtipi/apps/cistech-tunnel/shared", "containerPath": "/shared" }, + { "hostPath": "/etc/runtipi/repos/runtipi/apps/cistech-tunnel/shared/entrypoint.sh", "containerPath": "/entrypoint.sh", "readOnly": true } ] } ] diff --git a/apps/cistech-tunnel/shared/entrypoint.sh b/apps/cistech-tunnel/shared/entrypoint.sh new file mode 100644 index 0000000..6eac090 --- /dev/null +++ b/apps/cistech-tunnel/shared/entrypoint.sh @@ -0,0 +1,224 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${OC_URL:?OC_URL required}" + +# Auto-fetch server certificate pin if not provided +get_server_cert_pin() { + local url="$1" + local host=$(echo "$url" | sed -E 's|https?://([^/:]+).*|\1|') + local port=443 + + echo "Fetching certificate pin from $host:$port..." >&2 + + # Get certificate and compute pin-sha256 + local pin=$(echo | openssl s_client -connect "$host:$port" -servername "$host" 2>/dev/null | \ + openssl x509 -pubkey -noout 2>/dev/null | \ + openssl pkey -pubin -outform DER 2>/dev/null | \ + openssl dgst -sha256 -binary | \ + base64) + + if [[ -n "$pin" ]]; then + echo "pin-sha256:$pin" + else + echo "ERROR: Failed to fetch certificate from $host" >&2 + return 1 + fi +} + +# Get or fetch OC_SERVERCERT +if [[ -z "${OC_SERVERCERT:-}" ]]; then + OC_SERVERCERT=$(get_server_cert_pin "$OC_URL") + echo "Auto-detected server cert: $OC_SERVERCERT" +fi + +NOVNC_PORT="${NOVNC_PORT:-6901}" +VNC_PASSWORD="${VNC_PASSWORD:-changeme}" +DISPLAY_ADDR="${DISPLAY:-:1}" +OC_INTERFACE="${OC_INTERFACE:-tun0}" +OC_USER="${OC_USER:-}" +OC_PASSWORD="${OC_PASSWORD:-}" +OC_TOTP_SECRET="${OC_TOTP_SECRET:-}" + +# Default to hidden browser if OC_USER is set +if [[ -n "$OC_USER" ]]; then + OC_SSO_ARGS_DEFAULT="--browser-display-mode hidden -u $OC_USER" +else + OC_SSO_ARGS_DEFAULT="--browser-display-mode shown" +fi + +CLOUDFLARED_MODE="${CLOUDFLARED_MODE:-off}" # off|token|config +CLOUDFLARED_TOKEN="${CLOUDFLARED_TOKEN:-}" +SSH_TUNNEL_ENABLE="${SSH_TUNNEL_ENABLE:-0}" +SSH_DEST="${SSH_DEST:-zawa@10.3.1.201}" +SSH_FORWARDS="${SSH_FORWARDS:-0.0.0.0:8090:localhost:8090}" + +pids=() + +# Setup keyring with TOTP secret if provided +setup_keyring() { + if [[ -n "$OC_TOTP_SECRET" && -n "$OC_USER" ]]; then + python3 -c " +import keyring +keyring.set_password('openconnect-sso', 'totp/$OC_USER', '$OC_TOTP_SECRET'.upper()) +print('TOTP secret stored in keyring for $OC_USER') +" + fi +} + +# Create vpn_connect command in PATH and save environment +create_vpn_command() { + # Save environment variables to a file + cat > /etc/vpn.env << ENVFILE +export OC_URL="$OC_URL" +export OC_SERVERCERT="$OC_SERVERCERT" +export OC_INTERFACE="$OC_INTERFACE" +export OC_USER="$OC_USER" +export OC_PASSWORD="$OC_PASSWORD" +export OC_SSO_ARGS_DEFAULT="$OC_SSO_ARGS_DEFAULT" +export OC_SSO_ARGS="${OC_SSO_ARGS:-$OC_SSO_ARGS_DEFAULT}" +export OC_AUTHGROUP="${OC_AUTHGROUP:-}" +export OC_USERAGENT="${OC_USERAGENT:-}" +export OC_EXTRA_ARGS="${OC_EXTRA_ARGS:-}" +export OC_TOTP_SECRET="$OC_TOTP_SECRET" +export DISPLAY=":1" +ENVFILE + + # Build openconnect command + OPENCONNECT_CMD="/usr/sbin/openconnect --protocol=anyconnect --servercert $OC_SERVERCERT --interface $OC_INTERFACE --script /usr/share/vpnc-scripts/vpnc-script" + [[ -n "${OC_AUTHGROUP:-}" ]] && OPENCONNECT_CMD+=" --authgroup $OC_AUTHGROUP" + [[ -n "${OC_USERAGENT:-}" ]] && OPENCONNECT_CMD+=" --useragent $OC_USERAGENT" + [[ -n "${OC_EXTRA_ARGS:-}" ]] && OPENCONNECT_CMD+=" ${OC_EXTRA_ARGS}" + echo "export OPENCONNECT_CMD=\"$OPENCONNECT_CMD\"" >> /etc/vpn.env + + cat > /usr/local/bin/vpn_connect << 'VPNCMD' +#!/usr/bin/env bash +source /etc/vpn.env +echo "[$(date)] Starting VPN connection..." + +# Set password for openconnect +export OPENCONNECT_PASSWORD="$OC_PASSWORD" + +# openconnect-sso reads TOTP from keyring automatically +# Pass password via stdin for SSO form if needed +if [[ -n "$OC_USER" && -n "$OC_PASSWORD" ]]; then + echo "$OC_PASSWORD" | openconnect-sso -s "$OC_URL" ${OC_SSO_ARGS:-$OC_SSO_ARGS_DEFAULT} -- $OPENCONNECT_CMD +elif [[ -n "$OC_USER" ]]; then + echo "" | openconnect-sso -s "$OC_URL" ${OC_SSO_ARGS:-$OC_SSO_ARGS_DEFAULT} -- $OPENCONNECT_CMD +else + openconnect-sso -s "$OC_URL" ${OC_SSO_ARGS:-$OC_SSO_ARGS_DEFAULT} -- $OPENCONNECT_CMD +fi +VPNCMD + chmod +x /usr/local/bin/vpn_connect +} + +# Create VPN runner script that keeps shell open +create_vpn_script() { + cat > /tmp/vpn-runner.sh << 'VPNSCRIPT' +#!/usr/bin/env bash +cd /root + +echo "============================================" +echo " Cistech VPN Container" +echo "============================================" +echo "" +echo "Commands:" +echo " vpn_connect - Start/restart VPN connection" +echo " Ctrl+C - Stop auto-reconnect and drop to shell" +echo "" +echo "Starting VPN with auto-reconnect..." +echo "" + +while true; do + vpn_connect + echo "" + echo "[$(date)] VPN disconnected. Reconnecting in 10 seconds..." + echo "(Press Ctrl+C to stop auto-reconnect)" + sleep 10 +done +VPNSCRIPT + chmod +x /tmp/vpn-runner.sh +} + +start_gui() { + mkdir -p /root/.vnc + x11vnc -storepasswd "$VNC_PASSWORD" /root/.vnc/pass >/dev/null 2>&1 || true + rm -f /tmp/.X1-lock /tmp/.X11-unix/X1 2>/dev/null || true + Xvfb "$DISPLAY_ADDR" -screen 0 ${XVFB_WxHxD:-1280x800x24} +extension RANDR & + pids+=($!) + sleep 0.5 + export DISPLAY="$DISPLAY_ADDR" + fluxbox >/tmp/fluxbox.log 2>&1 & + pids+=($!) + x11vnc -display "$DISPLAY_ADDR" -rfbauth /root/.vnc/pass -forever -shared -rfbport 5900 -quiet & + pids+=($!) + websockify --web=/usr/share/novnc/ 0.0.0.0:"$NOVNC_PORT" localhost:5900 >/tmp/websockify.log 2>&1 & + pids+=($!) +} + +start_vpn_terminal() { + # Start xterm with VPN script + sleep 1 + xterm -fa 'Monospace' -fs 11 -bg black -fg white -geometry 120x35+50+50 \ + -T "Cistech VPN" -e /tmp/vpn-runner.sh & + pids+=($!) +} + +start_cloudflared() { + case "$CLOUDFLARED_MODE" in + token) + [ -n "$CLOUDFLARED_TOKEN" ] && cloudflared tunnel run --token "$CLOUDFLARED_TOKEN" >/tmp/cloudflared.log 2>&1 & + pids+=($!) + ;; + config) + cloudflared --no-autoupdate --config /etc/cloudflared/config.yml tunnel run >/tmp/cloudflared.log 2>&1 & + pids+=($!) + ;; + off|*) + ;; + esac +} + +start_ssh_tunnel() { + [ "$SSH_TUNNEL_ENABLE" = "1" ] || return 0 + IFS=',' read -ra LINES <<< "$SSH_FORWARDS" + args=(-N -o StrictHostKeyChecking=no -o ServerAliveInterval=60) + for m in "${LINES[@]}"; do args+=(-L "$m"); done + ssh "${args[@]}" "$SSH_DEST" & + pids+=($!) +} + +setup_nat() { + ( + for i in {1..60}; do + if ip link show "$OC_INTERFACE" >/dev/null 2>&1; then + sysctl -w net.ipv4.ip_forward=1 >/dev/null + iptables -t nat -C POSTROUTING -o "$OC_INTERFACE" -j MASQUERADE 2>/dev/null || \ + iptables -t nat -A POSTROUTING -o "$OC_INTERFACE" -j MASQUERADE + echo "NAT enabled on $OC_INTERFACE" + + # Trigger host routing service + if [ -d /runtime ]; then + touch /runtime/restart-routing + echo "Host routing trigger sent" + fi + break + fi + sleep 2 + done + ) & +} + +trap 'kill 0' INT TERM + +# Always start GUI now +setup_keyring +create_vpn_command +create_vpn_script +start_gui +start_vpn_terminal +setup_nat +start_cloudflared +start_ssh_tunnel + +wait