#!/usr/bin/env bash
# wml-firstboot — multi-WAN / multi-LAN auto-detection
set -e
exec > >(tee -a /var/log/wml/firstboot.log) 2>&1
echo "===== $(date -Is) wml-firstboot ====="

[[ -f /var/lib/wml/firstboot-done ]] && { echo "already done"; exit 0; }

# CRITICAL: Drop a "started" marker IMMEDIATELY so that even if the service
# is killed/restarted mid-execution, the unit's ConditionPathExists check
# prevents a second invocation overlapping with us.
mkdir -p /var/lib/wml
echo "started $(date -Is)" > /var/lib/wml/firstboot-started


source /etc/wml/lan-default.conf 2>/dev/null || true
LAN_BASE_IP="${LAN_IP:-172.30.30.1}"
LAN_PREFIX="${LAN_PREFIX:-24}"
LAN_BASE_NET="${LAN_NETWORK:-172.30.30.0/24}"
LAN_BASE_OCTET=$(echo "$LAN_BASE_IP" | cut -d. -f3)
LAN_FAMILY=$(echo "$LAN_BASE_IP" | cut -d. -f1-2)
MAX_WANS=4

mapfile -t IFACES < <(
    for d in /sys/class/net/*; do
        n=$(basename "$d")
        case "$n" in lo|docker*|virbr*|veth*|tap*|wg*|br-*|tun*) continue ;; esac
        [[ -e "$d/device" ]] || continue
        echo "$n"
    done | sort
)
echo "Detected NICs: ${IFACES[*]:-(none)}"

echo "Waiting for carrier (up to 8s)..."
DL=$(( $(date +%s) + 8 ))
while [[ $(date +%s) -lt $DL ]]; do
    any=false
    for i in "${IFACES[@]}"; do
        ip link set "$i" up 2>/dev/null
        [[ "$(cat /sys/class/net/$i/carrier 2>/dev/null)" == "1" ]] && any=true
    done
    $any && break
    sleep 1
done
sleep 1

declare -a WANS=()
declare -a LANS=()

# ---------------------------------------------------------------------------
# FIXED interface roles — NO automatic detection (admin requirement).
#   eth0 = WAN (internet uplink)   eth1 = LAN (internal network)
# We no longer probe carrier/DHCP to classify ports; the mapping is static so
# port roles never change between boots or based on what is plugged in. If a
# named NIC is absent, it is simply skipped. Extra NICs (eth2+) default to LAN.
# ---------------------------------------------------------------------------
WAN_FIXED="eth0"
LAN_FIXED="eth1"
for i in "${IFACES[@]}"; do
    case "$i" in
        "$WAN_FIXED") WANS+=("$i"); echo "  $i -> WAN (fixed)";;
        "$LAN_FIXED") LANS+=("$i"); echo "  $i -> LAN (fixed)";;
        *)            LANS+=("$i"); echo "  $i -> LAN (default for extra NIC)";;
    esac
done

echo "Final assignment (FIXED, no auto-detect): WANs=[${WANS[*]}]  LANs=[${LANS[*]}]"

# --- Lock apt source to the WML mirror BEFORE marking firstboot done ---
# live-build leaves /etc/apt/sources.list pointing at deb.debian.org. We MUST
# enforce the mirror-only source here, and it MUST run before the done-marker
# below — otherwise, if firstboot exits early after the marker is set, the lock
# never runs and the box silently keeps using deb.debian.org, so it never sees
# wml-os updates (it was previously placed near the END of the script, after the
# marker, so an interrupted firstboot left apt pointed at debian.org forever).
if ! grep -q 'repo.rakshaq.com' /etc/apt/sources.list 2>/dev/null; then
    cat > /etc/apt/sources.list <<'APTEOF'
# WML OS - all updates via the WML mirror only. Do not add deb.debian.org.
deb https://repo.rakshaq.com/ trixie main
APTEOF
    rm -f /etc/apt/sources.list.d/*.list 2>/dev/null || true
    echo "apt source locked to repo.rakshaq.com"
fi

mkdir -p /var/lib/wml
date -Is > /var/lib/wml/firstboot-done

mkdir -p /etc/systemd/network
rm -f /etc/systemd/network/10-wml-*.network /etc/systemd/network/20-wml-*.network 2>/dev/null

metric=100
for w in "${WANS[@]}"; do
    f=/etc/systemd/network/10-wml-wan-${w}.network
    echo "[Match]" > "$f"
    echo "Name=${w}" >> "$f"
    echo "" >> "$f"
    echo "[Network]" >> "$f"
    echo "DHCP=ipv4" >> "$f"
    echo "IPv6AcceptRA=true" >> "$f"
    echo "" >> "$f"
    echo "[DHCP]" >> "$f"
    echo "UseDNS=false" >> "$f"
    echo "UseDomains=false" >> "$f"
    echo "UseHostname=false" >> "$f"
    echo "RouteMetric=${metric}" >> "$f"
    metric=$((metric+10))
done

lan_idx=0
declare -a LAN_IPS=()
declare -a LAN_NETS=()
for l in "${LANS[@]}"; do
    octet=$((LAN_BASE_OCTET + lan_idx))
    lan_ip="${LAN_FAMILY}.${octet}.1"
    lan_net="${LAN_FAMILY}.${octet}.0/${LAN_PREFIX}"
    LAN_IPS+=("$lan_ip")
    LAN_NETS+=("$lan_net")
    f=/etc/systemd/network/20-wml-lan-${l}.network
    echo "[Match]" > "$f"
    echo "Name=${l}" >> "$f"
    echo "" >> "$f"
    echo "[Network]" >> "$f"
    echo "Address=${lan_ip}/${LAN_PREFIX}" >> "$f"
    echo "DHCPServer=no" >> "$f"
    echo "IPMasquerade=no" >> "$f"
    lan_idx=$((lan_idx+1))
done

systemctl enable systemd-networkd
systemctl restart --no-block systemd-networkd

{
    echo "# Multi-WAN/LAN assignment"
    echo "WAN_IFS=\"${WANS[*]}\""
    echo "LAN_IFS=\"${LANS[*]}\""
    echo "WAN_IF=${WANS[0]:-}"
    echo "LAN_IF=${LANS[0]:-}"
    echo "LAN_IP=${LAN_IPS[0]:-$LAN_BASE_IP}"
    echo "LAN_PREFIX=$LAN_PREFIX"
    echo "LAN_NETWORK=${LAN_NETS[0]:-$LAN_BASE_NET}"
    i=0
    for l in "${LANS[@]}"; do
        echo "LAN_IF_${i}=${l}"
        echo "LAN_IP_${i}=${LAN_IPS[$i]}"
        echo "LAN_NET_${i}=${LAN_NETS[$i]}"
        i=$((i+1))
    done
} > /etc/wml/interfaces.conf

if [[ ${#LANS[@]} -gt 0 ]]; then
    KEA=/etc/kea/kea-dhcp4.conf
    echo '{' > $KEA
    echo '  "Dhcp4": {' >> $KEA
    printf '    "interfaces-config": { "interfaces": [' >> $KEA
    first=1
    for l in "${LANS[@]}"; do
        [[ $first -eq 1 ]] || printf ',' >> $KEA
        printf '"%s"' "$l" >> $KEA
        first=0
    done
    # Retry opening the DHCP socket if the LAN iface isn't ready yet (boot race).
    echo '], "service-sockets-max-retries": 5, "service-sockets-retry-wait-time": 5000 },' >> $KEA
    echo '    "valid-lifetime": 86400,' >> $KEA
    echo '    "lease-database": { "type": "memfile", "persist": true, "name": "/var/lib/kea/dhcp4.leases" },' >> $KEA
    echo '    "subnet4": [' >> $KEA
    i=0
    for l in "${LANS[@]}"; do
        [[ $i -gt 0 ]] && echo ',' >> $KEA
        lan_ip="${LAN_IPS[$i]}"
        lan_net="${LAN_NETS[$i]}"
        octet=$((LAN_BASE_OCTET + i))
        pool_start="${LAN_FAMILY}.${octet}.100"
        pool_end="${LAN_FAMILY}.${octet}.200"
        echo '      {' >> $KEA
        echo "        \"id\": $((i+1))," >> $KEA
        echo "        \"subnet\": \"${lan_net}\"," >> $KEA
        echo "        \"pools\": [{ \"pool\": \"${pool_start} - ${pool_end}\" }]," >> $KEA
        echo '        "option-data": [' >> $KEA
        echo "          { \"name\": \"routers\", \"data\": \"${lan_ip}\" }," >> $KEA
        echo "          { \"name\": \"domain-name-servers\", \"data\": \"${lan_ip}\" }" >> $KEA
        echo '        ]' >> $KEA
        echo '      }' >> $KEA
        i=$((i+1))
    done
    echo '    ]' >> $KEA
    echo '  }' >> $KEA
    echo '}' >> $KEA
    systemctl unmask kea-dhcp4-server 2>/dev/null || true
    systemctl enable kea-dhcp4-server 2>/dev/null || true
    systemctl restart --no-block kea-dhcp4-server || true
fi

mkdir -p /var/lib/unbound
if command -v unbound-anchor >/dev/null && [[ ! -f /var/lib/unbound/root.key ]]; then
    unbound-anchor -a /var/lib/unbound/root.key || true
fi
chown -R unbound:unbound /var/lib/unbound 2>/dev/null || true
rm -f /etc/unbound/unbound.conf.d/root-auto-trust-anchor-file.conf 2>/dev/null

UB=/etc/unbound/unbound.conf.d/wml.conf
echo 'server:' > $UB
echo '    interface: 127.0.0.1' >> $UB


# Point the box at its OWN unbound for DNS (so it can resolve the update mirror
# and DNS blocking applies to the box itself). Immutable so DHCP can't clobber it.
chattr -i /etc/resolv.conf 2>/dev/null || true
cat > /etc/resolv.conf <<'RESOLV'
# Managed by WML OS — the box resolves through its own unbound (127.0.0.1).
nameserver 127.0.0.1
nameserver 9.9.9.9
options timeout:2 attempts:2
RESOLV
chattr +i /etc/resolv.conf 2>/dev/null || true

# Unbound must listen on the LAN interface(s) so LAN CLIENTS can use the
# firewall for DNS (required for Content Filter / DNS blocking to work for
# them, not just the box itself). Loopback + IPv6 loopback + every LAN IP.
echo '    interface: ::1' >> $UB
for ip in "${LAN_IPS[@]}"; do echo "    interface: ${ip}" >> $UB; done
echo '    access-control: 127.0.0.0/8 allow' >> $UB
echo '    access-control: ::1 allow' >> $UB
for net in "${LAN_NETS[@]}"; do echo "    access-control: ${net} allow" >> $UB; done
echo '    hide-identity: yes' >> $UB
echo '    hide-version: yes' >> $UB
echo '    module-config: "iterator"' >> $UB
echo '    do-not-query-localhost: no' >> $UB
echo '' >> $UB
echo 'forward-zone:' >> $UB
echo '    name: "."' >> $UB
echo '    forward-addr: 1.1.1.1' >> $UB
echo '    forward-addr: 1.0.0.1' >> $UB
echo '    forward-addr: 8.8.8.8' >> $UB

systemctl unmask unbound 2>/dev/null || true

# Populate the GeoIP country database in the background so geo aliases work
# on first boot without a manual step. Enable the monthly refresh timer too.
systemctl enable wml-geoip-update.timer 2>/dev/null || true
( /opt/wml-os/scripts/wml-geoip-update.sh >/dev/null 2>&1 & ) || true
systemctl enable unbound 2>/dev/null || true
# Wait up to 10s for the LAN IP to actually be present before unbound starts.
# Otherwise unbound fails with "cannot bind" and systemd shows red errors.
if [[ -n "${LAN_IPS[0]:-}" ]]; then
    for _ in 1 2 3 4 5 6 7 8 9 10; do
        if ip -4 addr show 2>/dev/null | grep -q "${LAN_IPS[0]}/"; then
            break
        fi
        sleep 1
    done
fi
systemctl restart --no-block unbound || true

ssh-keygen -A 2>/dev/null || true
systemctl unmask ssh 2>/dev/null || true
systemctl enable ssh 2>/dev/null || true
systemctl restart --no-block ssh || true

sysctl -w net.ipv4.ip_forward=1 2>/dev/null || true
sysctl -w net.ipv6.conf.all.forwarding=1 2>/dev/null || true

systemctl unmask nftables 2>/dev/null || true
systemctl enable nftables 2>/dev/null || true

if [[ ${#WANS[@]} -gt 0 ]]; then
    nft flush ruleset 2>/dev/null || true
    NF=/etc/nftables.conf
    echo '#!/usr/sbin/nft -f' > $NF
    echo 'flush ruleset' >> $NF
    echo '' >> $NF
    echo 'table ip wml-nat {' >> $NF
    echo '    chain postrouting {' >> $NF
    echo '        type nat hook postrouting priority 100; policy accept;' >> $NF
    for w in "${WANS[@]}"; do
        echo "        oifname \"${w}\" masquerade" >> $NF
    done
    echo '    }' >> $NF
    echo '}' >> $NF
    echo '' >> $NF
    echo 'table inet wml-filter {' >> $NF
    echo '    chain forward {' >> $NF
    echo '        type filter hook forward priority 0; policy accept;' >> $NF
    if [[ ${#LANS[@]} -gt 1 ]]; then
        for l1 in "${LANS[@]}"; do
            for l2 in "${LANS[@]}"; do
                [[ "$l1" == "$l2" ]] && continue
                echo "        iifname \"${l1}\" oifname \"${l2}\" drop" >> $NF
            done
        done
    fi
    echo '    }' >> $NF
    echo '}' >> $NF
    systemctl restart --no-block nftables 2>/dev/null || true
    # Restore saved user firewall rules — the flush above wiped the wml_user
    # table; without this, firewall rules silently vanish after firstboot.
    [ -s /etc/nftables-wml-user.conf ] && nft -f /etc/nftables-wml-user.conf 2>/dev/null || true
    systemctl enable wml-nft-user 2>/dev/null || true
    echo "NAT enabled for ${#WANS[@]} WAN(s), inter-LAN isolation for ${#LANS[@]} LAN(s)"
fi

systemctl enable wml-wan-monitor.service 2>/dev/null || true
systemctl restart --no-block wml-wan-monitor.service 2>/dev/null || true

# Web proxy: enable Squid + HTTP redirect for content/category blocking.
[ -x /opt/wml-os/scripts/wml-proxy-apply.sh ] && /opt/wml-os/scripts/wml-proxy-apply.sh || true
# Enable HTTPS on the admin UI (uses the admin cert generated above).
[ -x /opt/wml-os/scripts/wml-https-enable.sh ] && /opt/wml-os/scripts/wml-https-enable.sh || true

# --- 11a. Generate admin-UI TLS (mini local CA + leaf) ---
SSL_DIR=/etc/wml/ssl
CA_DIR=/var/lib/wml/ssl-ca
mkdir -p "$SSL_DIR" "$CA_DIR" /var/www/wml-public
chmod 700 "$SSL_DIR" "$CA_DIR"

if [[ ! -f "$CA_DIR/wml-ca.crt" ]]; then
    echo "[ssl] generating local CA"
    openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 -nodes \
        -keyout "$CA_DIR/wml-ca.key" \
        -out "$CA_DIR/wml-ca.crt" \
        -subj "/CN=WML OS Root CA/O=WML OS" \
        -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
        -addext "keyUsage=critical,keyCertSign,cRLSign" 2>/dev/null
    chmod 600 "$CA_DIR/wml-ca.key"
    chmod 644 "$CA_DIR/wml-ca.crt"
fi

# Copy CA cert to a public-downloadable location so users can install it.
cp "$CA_DIR/wml-ca.crt" /var/www/wml-public/wml-ca.crt
chmod 644 /var/www/wml-public/wml-ca.crt

# Leaf cert for admin UI - regenerate every boot with current LAN IPs
SAN_ENTRIES="DNS:wml,DNS:wml.local,DNS:firewall,DNS:firewall.local,IP:127.0.0.1"
i=0
for ip in "${LAN_IPS[@]}"; do
    SAN_ENTRIES="${SAN_ENTRIES},IP:${ip}"
    i=$((i+1))
done

openssl req -newkey rsa:2048 -nodes \
    -keyout "$SSL_DIR/admin.key" \
    -out "$SSL_DIR/admin.csr" \
    -subj "/CN=${LAN_IPS[0]:-wml}/O=WML OS" 2>/dev/null

openssl x509 -req -in "$SSL_DIR/admin.csr" \
    -CA "$CA_DIR/wml-ca.crt" -CAkey "$CA_DIR/wml-ca.key" -CAcreateserial \
    -days 3650 -sha256 \
    -out "$SSL_DIR/admin.crt" \
    -extfile <(printf "subjectAltName=%s\nextendedKeyUsage=serverAuth\n" "$SAN_ENTRIES") 2>/dev/null

chmod 600 "$SSL_DIR/admin.key"
chmod 644 "$SSL_DIR/admin.crt"
rm -f "$SSL_DIR/admin.csr"

# --- 11b. nginx with HTTPS + HTTP-to-HTTPS redirect ---
cat > /etc/nginx/sites-available/wml-admin <<'NGINX_EOF'
# HTTP: serves only the CA cert download + redirects everything else to HTTPS
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    server_tokens off;

    # CA cert downloadable over plain HTTP so users can install it
    # before they trust the HTTPS cert.
    location = /wml-ca.crt {
        alias /var/www/wml-public/wml-ca.crt;
        default_type application/x-x509-ca-cert;
        add_header Content-Disposition 'attachment; filename="wml-ca.crt"';
    }

    # Everything else -> HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS: the actual admin UI + API
server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    http2 on;
    server_name _;
    server_tokens off;
    client_max_body_size 16M;

    ssl_certificate     /etc/wml/ssl/admin.crt;
    ssl_certificate_key /etc/wml/ssl/admin.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5:!3DES;
    ssl_prefer_server_ciphers on;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    root /opt/wml-os/ui/dist;
    index index.html;

    location /api/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
        proxy_connect_timeout 60s;
    }

    # CA cert also reachable over HTTPS for convenience
    location = /wml-ca.crt {
        alias /var/www/wml-public/wml-ca.crt;
        default_type application/x-x509-ca-cert;
        add_header Content-Disposition 'attachment; filename="wml-ca.crt"';
    }

    location / { try_files $uri $uri/ /index.html; }
}
NGINX_EOF
ln -sf /etc/nginx/sites-available/wml-admin /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
systemctl restart --no-block nginx || true

systemctl restart --no-block wml-api || true

# --- ClamAV: ensure DB present, then start the daemon.
# freshclam may finish downloading the DB AFTER this point, so we spawn a
# background waiter that polls for the DB (up to 5 min) and starts clamd as
# soon as it's ready. Avoids the "condition unmet / inactive dead" state.
(
    for _ in $(seq 1 60); do
        if ls /var/lib/clamav/main.{cvd,cld} /var/lib/clamav/daily.{cvd,cld} 2>/dev/null | head -1 >/dev/null; then
            systemctl restart clamav-daemon 2>/dev/null || true
            break
        fi
        # Kick freshclam if DB still missing
        systemctl start clamav-freshclam 2>/dev/null || true
        sleep 5
    done
) >/dev/null 2>&1 &

# --- Suricata: try to start now (in case the user/group fix in chroot hook
# means it can finally come up).
systemctl restart --no-block suricata 2>/dev/null || true


# (apt source is now locked earlier, before the firstboot-done marker.)

echo "===== wml-firstboot complete: ${#WANS[@]} WAN(s), ${#LANS[@]} LAN(s) ====="
