diff --git a/apps/cistech-tunnel/docker-compose.json b/apps/cistech-tunnel/docker-compose.json index 01d9a8b..3e4cd59 100755 --- a/apps/cistech-tunnel/docker-compose.json +++ b/apps/cistech-tunnel/docker-compose.json @@ -16,7 +16,8 @@ "NOVNC_PORT": "6902" }, "volumes": [ - { "hostPath": "${APP_DATA_DIR}/data", "containerPath": "/root" } + { "hostPath": "${APP_DATA_DIR}/data", "containerPath": "/root" }, + { "hostPath": "${APP_DATA_DIR}", "containerPath": "/runtime" } ] } ] diff --git a/apps/cistech-tunnel/shared/host-routing.sh b/apps/cistech-tunnel/shared/host-routing.sh new file mode 100644 index 0000000..c796301 --- /dev/null +++ b/apps/cistech-tunnel/shared/host-routing.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +# +# Host routing script for cistech-tunnel +# Routes target subnets through the VPN container +# +set -euo pipefail + +ACTION="${1:-start}" + +# Fixed configuration +CONTAINER_IP="172.30.0.10" +BRIDGE_NAME="br-vpn-static" +TARGET_SUBNETS="10.3.1.0/24 10.255.255.0/24" +LAN_SUBNET="192.168.0.0/23" +LAN_INTERFACES="eth0 eth1 wlan0" +LOG_FILE="/var/log/cistech-routing.log" + +log() { + local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [cistech-routing] $*" + echo "$msg" | tee -a "$LOG_FILE" >&2 +} + +get_lan_interface() { + ip route show default | awk '/default/ {for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1 +} + +remove_routes() { + log "Removing stale routes..." + for subnet in $TARGET_SUBNETS; do + ip route del "$subnet" 2>/dev/null || true + done + log "Stale routes removed" +} + +apply_routes() { + local lan_if + lan_if="$(get_lan_interface)" + + log "Applying host routing rules..." + log " Container IP: $CONTAINER_IP" + log " Bridge: $BRIDGE_NAME" + log " Target subnets: $TARGET_SUBNETS" + log " LAN interface: ${lan_if:-unknown}" + + # Enable IP forwarding + echo 1 > /proc/sys/net/ipv4/ip_forward + log "IP forwarding enabled" + + # Add routes to target subnets via container + for subnet in $TARGET_SUBNETS; do + ip route replace "$subnet" via "$CONTAINER_IP" dev "$BRIDGE_NAME" + log "Route added: $subnet via $CONTAINER_IP dev $BRIDGE_NAME" + done + + # Allow forwarding in DOCKER-USER chain for all LAN interfaces + for lan_if in $LAN_INTERFACES; do + if ip link show "$lan_if" &>/dev/null; then + # Allow traffic from LAN to container bridge + iptables -C DOCKER-USER -i "$lan_if" -o "$BRIDGE_NAME" -j ACCEPT 2>/dev/null || \ + iptables -I DOCKER-USER 1 -i "$lan_if" -o "$BRIDGE_NAME" -j ACCEPT + + # Allow return traffic + iptables -C DOCKER-USER -i "$BRIDGE_NAME" -o "$lan_if" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ + iptables -I DOCKER-USER 1 -i "$BRIDGE_NAME" -o "$lan_if" -m state --state RELATED,ESTABLISHED -j ACCEPT + + log "DOCKER-USER iptables rules added for $lan_if <-> $BRIDGE_NAME" + fi + done + + # Masquerade traffic from LAN subnet to VPN bridge (using nft) + if ! nft list chain ip nat POSTROUTING 2>/dev/null | grep -q "saddr $LAN_SUBNET.*oifname.*$BRIDGE_NAME.*masquerade"; then + nft add rule ip nat POSTROUTING ip saddr "$LAN_SUBNET" oifname "$BRIDGE_NAME" counter masquerade + log "NAT masquerade rule added for $LAN_SUBNET -> $BRIDGE_NAME" + else + log "NAT masquerade rule already exists for $LAN_SUBNET -> $BRIDGE_NAME" + fi + + log "OK: Host routing applied" +} + +remove_all() { + log "Removing all routing rules..." + + # Remove routes + for subnet in $TARGET_SUBNETS; do + ip route del "$subnet" via "$CONTAINER_IP" dev "$BRIDGE_NAME" 2>/dev/null || true + done + + # Remove iptables rules for all LAN interfaces + for lan_if in $LAN_INTERFACES; do + iptables -D DOCKER-USER -i "$lan_if" -o "$BRIDGE_NAME" -j ACCEPT 2>/dev/null || true + iptables -D DOCKER-USER -i "$BRIDGE_NAME" -o "$lan_if" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true + done + + # Remove masquerade rule (using nft) + local handle + handle=$(nft -a list chain ip nat POSTROUTING 2>/dev/null | grep "saddr $LAN_SUBNET.*oifname.*$BRIDGE_NAME.*masquerade" | grep -oP 'handle \K\d+' | head -1) + if [ -n "$handle" ]; then + nft delete rule ip nat POSTROUTING handle "$handle" 2>/dev/null || true + fi + + log "All routing rules removed" +} + +case "$ACTION" in + start) + remove_routes + apply_routes + ;; + stop) + remove_all + ;; + restart) + remove_all + sleep 1 + remove_routes + apply_routes + ;; + *) + echo "Usage: $0 {start|stop|restart}" >&2 + exit 2 + ;; +esac diff --git a/apps/cistech-tunnel/shared/install-host-services.sh b/apps/cistech-tunnel/shared/install-host-services.sh new file mode 100644 index 0000000..8ad7fc7 --- /dev/null +++ b/apps/cistech-tunnel/shared/install-host-services.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# +# Install host-side systemd services for cistech-tunnel +# Run this ONCE on the host after app install +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +APP_DATA_DIR="/etc/runtipi/app-data/runtipi/cistech-tunnel" + +echo "Installing cistech-tunnel host services..." + +# Create app-data directory for trigger file +sudo mkdir -p "$APP_DATA_DIR" + +# Create the path unit (watches for trigger file) +sudo tee /etc/systemd/system/cistech-routing-watcher.path > /dev/null << EOF +[Unit] +Description=Watch for cistech-tunnel routing trigger + +[Path] +PathExists=$APP_DATA_DIR/restart-routing +Unit=cistech-routing-watcher.service + +[Install] +WantedBy=multi-user.target +EOF + +# Create the service unit (applies routes when triggered) +sudo tee /etc/systemd/system/cistech-routing-watcher.service > /dev/null << EOF +[Unit] +Description=Apply cistech-tunnel routing rules +After=docker.service + +[Service] +Type=oneshot +ExecStart=$SCRIPT_DIR/host-routing.sh restart +ExecStartPost=/bin/rm -f $APP_DATA_DIR/restart-routing +ExecStartPost=/bin/bash -c 'echo "trigger cleared at \$(date)" >> $APP_DATA_DIR/watcher.log' +EOF + +# Make host-routing.sh executable +chmod +x "$SCRIPT_DIR/host-routing.sh" + +# Reload systemd and enable the watcher +sudo systemctl daemon-reload +sudo systemctl enable cistech-routing-watcher.path +sudo systemctl start cistech-routing-watcher.path + +# Disable the old boot-only service if it exists +if systemctl is-enabled cistech-routing.service &>/dev/null; then + echo "Disabling old cistech-routing.service (replaced by watcher)..." + sudo systemctl stop cistech-routing.service 2>/dev/null || true + sudo systemctl disable cistech-routing.service 2>/dev/null || true +fi + +# Apply routes now +echo "Applying initial routes..." +sudo "$SCRIPT_DIR/host-routing.sh" start + +echo "" +echo "Done! Watcher installed and routes applied." +echo "" +echo "To trigger route refresh from container:" +echo " touch /runtime/restart-routing" +echo "" +echo "To check watcher status:" +echo " systemctl status cistech-routing-watcher.path" diff --git a/apps/cistech-tunnel/shared/uninstall-host-services.sh b/apps/cistech-tunnel/shared/uninstall-host-services.sh new file mode 100644 index 0000000..743b62b --- /dev/null +++ b/apps/cistech-tunnel/shared/uninstall-host-services.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# +# Uninstall host-side systemd services for cistech-tunnel +# +set -euo pipefail + +echo "Removing cistech-tunnel host services..." + +# Stop and disable the watcher +sudo systemctl stop cistech-routing-watcher.path 2>/dev/null || true +sudo systemctl disable cistech-routing-watcher.path 2>/dev/null || true + +# Remove routing rules +/etc/runtipi/repos/runtipi/apps/cistech-tunnel/shared/host-routing.sh stop 2>/dev/null || true + +# Remove systemd units +sudo rm -f /etc/systemd/system/cistech-routing-watcher.path +sudo rm -f /etc/systemd/system/cistech-routing-watcher.service + +# Reload systemd +sudo systemctl daemon-reload + +echo "" +echo "Done! Host services removed." diff --git a/apps/cistech-tunnel/source/entrypoint.sh b/apps/cistech-tunnel/source/entrypoint.sh index 30540fb..130c423 100755 --- a/apps/cistech-tunnel/source/entrypoint.sh +++ b/apps/cistech-tunnel/source/entrypoint.sh @@ -160,6 +160,12 @@ setup_nat() { 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