#!/usr/bin/env bash
set -euo pipefail

SERVICE_NAME="veloramesh-server"
EDGE_SERVICE_NAME="${VELORAMESH_EDGE_SERVICE_NAME:-veloramesh-edge}"
SERVICE_USER="${VELORAMESH_SERVICE_USER:-veloramesh}"
SERVICE_GROUP="${VELORAMESH_SERVICE_GROUP:-veloramesh}"
INSTALL_DIR="${VELORAMESH_INSTALL_DIR:-/opt/veloramesh}"
CONFIG_DIR="${VELORAMESH_CONFIG_DIR:-/etc/veloramesh}"
DATA_DIR="${VELORAMESH_DATA_DIR:-/var/lib/veloramesh}"
TLS_DIR="$CONFIG_DIR/tls"
ENV_FILE="$CONFIG_DIR/veloramesh.env"
TLS_CERT="$TLS_DIR/fullchain.pem"
TLS_KEY="$TLS_DIR/privkey.pem"
SYSTEMD_DIR="${VELORAMESH_SYSTEMD_DIR:-/etc/systemd/system}"
UNIT_FILE="$SYSTEMD_DIR/$SERVICE_NAME.service"
EDGE_UNIT_FILE="$SYSTEMD_DIR/$EDGE_SERVICE_NAME.service"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_BINARY="$SCRIPT_DIR/veloramesh-server"
EDGE_SOURCE_BINARY="$SCRIPT_DIR/veloramesh-edge"
START_SERVICE=1
ROLE="${VELORAMESH_SERVER_ROLE:-all}"
EDGE_TICKET_KEY_SOURCE="${VELORAMESH_EDGE_TICKET_KEY_SOURCE:-}"
DEFAULT_API_BIND_ADDR=":2053"
DEFAULT_EDGE_BIND_ADDR=":3478"
DEFAULT_VIRTUAL_IPV4_CIDR="${VELORAMESH_DEFAULT_VIRTUAL_IPV4_CIDR:-10.253.0.0/16}"
API_BIND_ADDR="${VELORAMESH_API_BIND_ADDR:-$DEFAULT_API_BIND_ADDR}"
EDGE_BIND_ADDR="${VELORAMESH_EDGE_BIND_ADDR:-$DEFAULT_EDGE_BIND_ADDR}"
API_BIND_ADDR_EXPLICIT=0
EDGE_BIND_ADDR_EXPLICIT=0
DEFAULT_VIRTUAL_IPV4_CIDR_EXPLICIT=0
TRANSACTION_DIR=""
TRANSACTION_ACTIVE=0
INSTALL_MUTATION_STARTED=0
STAGED_API_BINARY=""
STAGED_EDGE_BINARY=""
API_WAS_ACTIVE=0
EDGE_WAS_ACTIVE=0
API_WAS_ENABLED=0
EDGE_WAS_ENABLED=0
declare -A TRANSACTION_PATHS=()
declare -A TRANSACTION_EXISTED=()
TRANSACTION_KEYS=(api_binary edge_binary environment api_unit edge_unit edge_ticket_key)
[ -n "${VELORAMESH_API_BIND_ADDR:-}" ] && API_BIND_ADDR_EXPLICIT=1
[ -n "${VELORAMESH_EDGE_BIND_ADDR:-}" ] && EDGE_BIND_ADDR_EXPLICIT=1
[ -n "${VELORAMESH_DEFAULT_VIRTUAL_IPV4_CIDR:-}" ] && DEFAULT_VIRTUAL_IPV4_CIDR_EXPLICIT=1

usage() {
    cat <<USAGE
Usage: sudo bash install-veloramesh-server.sh [options]

Options:
  --role <all|api|edge>  Install all-in-one, API-only, or edge-only role.
  --api-bind <addr>      Normalized API bind value for automation, for example :2053 or 127.0.0.1:2053.
  --edge-bind <addr>     Normalized edge UDP bind value for automation, for example :3478 or 0.0.0.0:3478.
  --default-virtual-ipv4-cidr <cidr>
                        Default network virtual IPv4 CIDR for API/admin network creation.
  --binary <path>       Install the API server binary from this path.
  --edge-binary <path>  Install the edge discovery binary from this path.
  --edge-ticket-key <path>
                        Copy an existing edge discovery ticket key into place.
  --no-start            Install files and systemd units, but do not start services.
  -h, --help            Show this help text.

Environment overrides:
  VELORAMESH_SERVICE_USER
  VELORAMESH_SERVICE_GROUP
  VELORAMESH_INSTALL_DIR
  VELORAMESH_CONFIG_DIR
  VELORAMESH_DATA_DIR
  VELORAMESH_API_BIND_ADDR
  VELORAMESH_EDGE_BIND_ADDR
  VELORAMESH_DEFAULT_VIRTUAL_IPV4_CIDR
USAGE
}

log() {
    printf '[veloramesh] %s\n' "$1" >&2
}

fail() {
    printf '[veloramesh] error: %s\n' "$1" >&2
    exit 1
}

need_command() {
    command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1"
}

snapshot_managed_path() {
    local key="$1"
    local path="$2"
    TRANSACTION_PATHS["$key"]="$path"
    if [ -e "$path" ] || [ -L "$path" ]; then
        cp -a -- "$path" "$TRANSACTION_DIR/$key"
        TRANSACTION_EXISTED["$key"]=1
    else
        TRANSACTION_EXISTED["$key"]=0
    fi
}

record_service_state() {
    if systemctl is-active --quiet "$SERVICE_NAME"; then
        API_WAS_ACTIVE=1
    fi
    if systemctl is-active --quiet "$EDGE_SERVICE_NAME"; then
        EDGE_WAS_ACTIVE=1
    fi
    if systemctl is-enabled --quiet "$SERVICE_NAME"; then
        API_WAS_ENABLED=1
    fi
    if systemctl is-enabled --quiet "$EDGE_SERVICE_NAME"; then
        EDGE_WAS_ENABLED=1
    fi
}

restore_service_state() {
    local failed=0
    if [ "$API_WAS_ENABLED" -eq 1 ]; then
        systemctl enable "$SERVICE_NAME" >/dev/null 2>&1 || failed=1
    else
        systemctl disable "$SERVICE_NAME" >/dev/null 2>&1 || true
    fi
    if [ "$EDGE_WAS_ENABLED" -eq 1 ]; then
        systemctl enable "$EDGE_SERVICE_NAME" >/dev/null 2>&1 || failed=1
    else
        systemctl disable "$EDGE_SERVICE_NAME" >/dev/null 2>&1 || true
    fi
    if [ "$API_WAS_ACTIVE" -eq 1 ]; then
        systemctl start "$SERVICE_NAME" >/dev/null 2>&1 || failed=1
    fi
    if [ "$EDGE_WAS_ACTIVE" -eq 1 ]; then
        systemctl start "$EDGE_SERVICE_NAME" >/dev/null 2>&1 || failed=1
    fi
    return "$failed"
}

rollback_install_transaction() {
    local status="$?"
    local key
    local path
    local service
    local staged
    local failed=0
    trap - EXIT
    set +e
    if [ "$TRANSACTION_ACTIVE" -eq 1 ]; then
        if [ "$INSTALL_MUTATION_STARTED" -eq 1 ]; then
            log "install failed; restoring the previous server installation"
            for service in "$SERVICE_NAME" "$EDGE_SERVICE_NAME"; do
                if systemctl is-active --quiet "$service"; then
                    systemctl stop "$service" >/dev/null 2>&1 || failed=1
                fi
            done
            if [ "$failed" -eq 0 ]; then
                for key in "${TRANSACTION_KEYS[@]}"; do
                    path="${TRANSACTION_PATHS[$key]}"
                    staged="$path.restore.$$"
                    if [ "${TRANSACTION_EXISTED[$key]}" -eq 1 ]; then
                        if ! cp -a -- "$TRANSACTION_DIR/$key" "$staged" || ! mv -f -- "$staged" "$path"; then
                            log "rollback could not restore $path"
                            failed=1
                        fi
                        rm -f -- "$staged" || failed=1
                    else
                        rm -f -- "$path" || failed=1
                    fi
                done
            fi
            if [ "$failed" -eq 0 ]; then
                systemctl daemon-reload >/dev/null 2>&1 || failed=1
            fi
            if [ "$failed" -eq 0 ]; then
                restore_service_state || failed=1
            fi
        fi
        rm -f -- "$STAGED_API_BINARY" "$STAGED_EDGE_BINARY"
        if [ "$failed" -eq 0 ]; then
            rm -rf -- "$TRANSACTION_DIR"
        else
            log "rollback incomplete; recovery files retained at $TRANSACTION_DIR"
            status=1
        fi
    fi
    exit "$status"
}

begin_install_transaction() {
    TRANSACTION_DIR="$(mktemp -d)"
    chmod 0700 "$TRANSACTION_DIR"
    snapshot_managed_path api_binary "$INSTALL_DIR/veloramesh-server"
    snapshot_managed_path edge_binary "$INSTALL_DIR/veloramesh-edge"
    snapshot_managed_path environment "$ENV_FILE"
    snapshot_managed_path api_unit "$UNIT_FILE"
    snapshot_managed_path edge_unit "$EDGE_UNIT_FILE"
    snapshot_managed_path edge_ticket_key "$DATA_DIR/edge-ticket.key"
    record_service_state
    TRANSACTION_ACTIVE=1
    trap rollback_install_transaction EXIT
}

commit_install_transaction() {
    rm -f -- "$STAGED_API_BINARY" "$STAGED_EDGE_BINARY"
    rm -rf -- "$TRANSACTION_DIR"
    TRANSACTION_ACTIVE=0
    trap - EXIT
}

stage_install_binaries() {
    if [ "$INSTALL_API" -eq 1 ]; then
        STAGED_API_BINARY="$INSTALL_DIR/.veloramesh-server.new.$$"
        install -o root -g root -m 0755 "$SOURCE_BINARY" "$STAGED_API_BINARY"
        "$STAGED_API_BINARY" version >/dev/null
    fi
    if [ "$INSTALL_EDGE" -eq 1 ]; then
        STAGED_EDGE_BINARY="$INSTALL_DIR/.veloramesh-edge.new.$$"
        install -o root -g root -m 0755 "$EDGE_SOURCE_BINARY" "$STAGED_EDGE_BINARY"
        "$STAGED_EDGE_BINARY" version >/dev/null
    fi
}

stop_services_for_install() {
    if [ "$API_WAS_ACTIVE" -eq 1 ]; then
        log "stopping active API service for install/update"
        systemctl stop "$SERVICE_NAME"
    fi
    if [ "$EDGE_WAS_ACTIVE" -eq 1 ]; then
        log "stopping active edge service for install/update"
        systemctl stop "$EDGE_SERVICE_NAME"
    fi
}

restart_previously_active_selected_services() {
    local restarted=0
    if [ "$INSTALL_API" -eq 1 ] && [ "$API_WAS_ACTIVE" -eq 1 ]; then
        systemctl start "$SERVICE_NAME"
        restarted=1
    fi
    if [ "$INSTALL_EDGE" -eq 1 ] && [ "$EDGE_WAS_ACTIVE" -eq 1 ]; then
        systemctl start "$EDGE_SERVICE_NAME"
        restarted=1
    fi
    if [ "$restarted" -eq 1 ]; then
        log "restored previously active services after install/update"
    fi
}

normalize_role() {
    case "$1" in
        all|all-in-one|api-edge|api+edge)
            printf 'all'
            ;;
        api|api-only)
            printf 'api'
            ;;
        edge|edge-only)
            printf 'edge'
            ;;
        *)
            fail "unknown role: $1"
            ;;
    esac
}

systemd_quote() {
    local value="$1"
    value="${value//\\/\\\\}"
    value="${value//\"/\\\"}"
    value="${value//%/%%}"
    value="${value//$'\n'/\\n}"
    value="${value//$'\r'/\\r}"
    value="${value//$'\t'/\\t}"
    printf '"%s"' "${value}"
}

role_label() {
    case "$ROLE" in
        all) printf 'API and edge' ;;
        api) printf 'API only' ;;
        edge) printf 'edge only' ;;
    esac
}

api_role_state_present() {
    local path
    for path in \
        "$INSTALL_DIR/veloramesh-server" \
        "$UNIT_FILE" \
        "$TLS_CERT" \
        "$TLS_KEY" \
        "$DATA_DIR/veloramesh.db" \
        "$DATA_DIR/veloramesh.db-wal" \
        "$DATA_DIR/veloramesh.db-shm" \
        "$DATA_DIR/membership-ticket.key"; do
        if [ -e "$path" ] || [ -L "$path" ]; then
            return 0
        fi
    done
    if [ -f "$ENV_FILE" ] && grep -Eq '^VELORAMESH_(API_BIND_ADDR|SERVER_TLS_CERT|SERVER_TLS_KEY|STORE_BACKEND|STORE_PATH|TICKET_KEY_FILE)=' "$ENV_FILE"; then
        return 0
    fi
    return 1
}

require_clean_edge_role_target() {
    [ "$ROLE" = "edge" ] || return 0
    if api_role_state_present; then
        fail "edge-only installation refuses a host that still contains API state; use Fresh Edge-only reset or a clean host"
    fi
}

ensure_safe_data_dir() {
    case "$DATA_DIR" in
        ""|"/"|"/etc"|"/opt"|"/var"|"/var/lib"|"/usr"|"/usr/local")
            fail "refusing unsafe data directory: $DATA_DIR"
            ;;
    esac
    case "$DATA_DIR" in
        *veloramesh*|*VeloraMesh*) ;;
        *) fail "refusing non-VeloraMesh data directory: $DATA_DIR" ;;
    esac
}

repair_data_permissions() {
    ensure_safe_data_dir
    chown -R "$SERVICE_USER:$SERVICE_GROUP" "$DATA_DIR"
    chmod 0750 "$DATA_DIR"
    find "$DATA_DIR" -type d -exec chmod 0750 {} +
    find "$DATA_DIR" -maxdepth 1 -type f \( -name 'veloramesh.db' -o -name 'veloramesh.db-wal' -o -name 'veloramesh.db-shm' -o -name 'membership-ticket.key' -o -name 'edge-ticket.key' \) -exec chmod 0640 {} +
}

run_edge_init_key() {
    local runner
    local remove_runner=0
    local status

    if [ "$INSTALL_EDGE" -eq 1 ]; then
        runner="$INSTALL_DIR/veloramesh-edge"
    else
        runner="$INSTALL_DIR/.veloramesh-edge-init-key"
        install -o root -g root -m 0755 "$EDGE_SOURCE_BINARY" "$runner"
        remove_runner=1
    fi

    if VELORAMESH_EDGE_BIND_ADDR="$EDGE_BIND_ADDR" \
        VELORAMESH_EDGE_TICKET_KEY_FILE="$DATA_DIR/edge-ticket.key" \
        "$runner" init-key >/dev/null; then
        status=0
    else
        status=$?
    fi

    if [ "$remove_runner" -eq 1 ]; then
        rm -f -- "$runner"
    fi
    return "$status"
}

set_env_value() {
    local key="$1"
    local value="$2"
    if grep -q "^${key}=" "$ENV_FILE"; then
        sed -i "s|^${key}=.*|${key}=${value}|" "$ENV_FILE"
    else
        printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE"
    fi
}

remove_env_values() {
    [ -f "$ENV_FILE" ] || return 0
    local key
    for key in "$@"; do
        sed -i "/^${key}=/d" "$ENV_FILE"
    done
}

read_env_value() {
    local wanted="$1"
    local key
    local value
    [ -f "$ENV_FILE" ] || return 0
    while IFS='=' read -r key value; do
        case "$key" in
            ""|\#*) continue ;;
        esac
        if [ "$key" = "$wanted" ]; then
            printf '%s' "$value"
            return 0
        fi
    done < "$ENV_FILE"
    return 0
}

resolve_existing_listen_addrs() {
    local existing
    [ -f "$ENV_FILE" ] || return 0
    if [ "$API_BIND_ADDR_EXPLICIT" -eq 0 ]; then
        existing="$(read_env_value VELORAMESH_API_BIND_ADDR)"
        [ -n "$existing" ] && API_BIND_ADDR="$existing"
    fi
    if [ "$EDGE_BIND_ADDR_EXPLICIT" -eq 0 ]; then
        existing="$(read_env_value VELORAMESH_EDGE_BIND_ADDR)"
        [ -n "$existing" ] && EDGE_BIND_ADDR="$existing"
    fi
    if [ "$DEFAULT_VIRTUAL_IPV4_CIDR_EXPLICIT" -eq 0 ]; then
        existing="$(read_env_value VELORAMESH_DEFAULT_VIRTUAL_IPV4_CIDR)"
        [ -n "$existing" ] && DEFAULT_VIRTUAL_IPV4_CIDR="$existing"
    fi
    return 0
}

is_ipv4_literal() {
    local value="$1"
    local a
    local b
    local c
    local d
    local extra
    IFS=. read -r a b c d extra <<< "$value"
    [ -z "${extra:-}" ] || return 1
    for octet in "$a" "$b" "$c" "$d"; do
        case "$octet" in
            ""|*[!0-9]*) return 1 ;;
        esac
        [ "$octet" -ge 0 ] && [ "$octet" -le 255 ] || return 1
    done
    return 0
}

validate_listen_addr() {
    local label="$1"
    local value="$2"
    local host
    local port
    [ -n "$value" ] || fail "$label listen address cannot be empty"
    case "$value" in
        *[[:space:]]*) fail "$label listen address cannot contain whitespace" ;;
        *://*|*/*|*\?*|*\#*) fail "$label listen address must be a local bind address, not a URL" ;;
        *:*) ;;
        *) fail "$label listen address must include a port, for example :2053 or 127.0.0.1:2053" ;;
    esac
    port="${value##*:}"
    case "$port" in
        ""|*[!0-9]*) fail "$label listen address has an invalid port: $value" ;;
    esac
    if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
        fail "$label listen port must be between 1 and 65535"
    fi
    host="${value%:*}"
    case "$host" in
        ""|"localhost") return 0 ;;
        \[*\])
            host="${host#\[}"
            host="${host%\]}"
            case "$host" in
                *[!0-9A-Fa-f:.]*) fail "$label listen address must bind to a local IP, localhost, or all interfaces; public DNS names belong in client URLs" ;;
            esac
            return 0
            ;;
        *:*) fail "$label IPv6 bind address must be wrapped in brackets, for example [::]:$port" ;;
        *[!0-9.]*) fail "$label listen address must bind to a local IP, localhost, or all interfaces; public DNS names belong in client URLs" ;;
    esac
    is_ipv4_literal "$host" || fail "$label listen address must use a valid IPv4 address, localhost, bracketed IPv6, or all interfaces"
}

validate_virtual_ipv4_cidr() {
    local value="$1"
    local ip
    local bits
    local a
    local b
    local c
    local d
    local extra
    case "$value" in
        */*) ;;
        *) fail "default virtual IPv4 CIDR must include a prefix, for example 10.253.0.0/16" ;;
    esac
    ip="${value%/*}"
    bits="${value##*/}"
    case "$bits" in
        ""|*[!0-9]*) fail "default virtual IPv4 CIDR prefix is invalid: $value" ;;
    esac
    [ "$bits" -ge 16 ] && [ "$bits" -le 24 ] || fail "default virtual IPv4 CIDR prefix must be between /16 and /24"
    IFS=. read -r a b c d extra <<< "$ip"
    [ -z "${extra:-}" ] || fail "default virtual IPv4 CIDR has an invalid IPv4 address: $value"
    for octet in "$a" "$b" "$c" "$d"; do
        case "$octet" in
            ""|*[!0-9]*) fail "default virtual IPv4 CIDR has an invalid IPv4 address: $value" ;;
        esac
        [ "$octet" -ge 0 ] && [ "$octet" -le 255 ] || fail "default virtual IPv4 CIDR has an invalid IPv4 address: $value"
    done
    if [ "$a" -eq 10 ]; then
        return 0
    fi
    if [ "$a" -eq 172 ] && [ "$b" -ge 16 ] && [ "$b" -le 31 ]; then
        return 0
    fi
    fail "default virtual IPv4 CIDR must be inside 10.0.0.0/8 or 172.16.0.0/12; do not use CGNAT 100.64.0.0/10, home LAN 192.168.0.0/16, or public ranges"
}

write_full_env_file() {
    local tmp_env
    tmp_env="$(mktemp "$TRANSACTION_DIR/environment.XXXXXX")"
    cat > "$tmp_env" <<ENV
VELORAMESH_API_BIND_ADDR=$API_BIND_ADDR
VELORAMESH_EDGE_BIND_ADDR=$EDGE_BIND_ADDR
VELORAMESH_SERVER_TLS_CERT=$TLS_CERT
VELORAMESH_SERVER_TLS_KEY=$TLS_KEY
VELORAMESH_STORE_BACKEND=sqlite
VELORAMESH_STORE_PATH=$DATA_DIR/veloramesh.db
VELORAMESH_DEFAULT_VIRTUAL_IPV4_CIDR=$DEFAULT_VIRTUAL_IPV4_CIDR
VELORAMESH_TICKET_KEY_FILE=$DATA_DIR/membership-ticket.key
VELORAMESH_EDGE_TICKET_KEY_FILE=$DATA_DIR/edge-ticket.key
VELORAMESH_MEMBERSHIP_TICKET_TTL=10m
VELORAMESH_MAX_HEADER_BYTES=16384
VELORAMESH_MAX_BODY_BYTES=65536
VELORAMESH_API_MAX_CONNECTIONS=1024
VELORAMESH_API_MAX_IN_FLIGHT_REQUESTS=256
VELORAMESH_API_HTTP2_MAX_CONCURRENT_STREAMS=32
VELORAMESH_SESSION_TTL=15m
VELORAMESH_SHUTDOWN_TIMEOUT=10s
VELORAMESH_READ_HEADER_TIMEOUT=5s
VELORAMESH_READ_TIMEOUT=15s
VELORAMESH_WRITE_TIMEOUT=15s
VELORAMESH_IDLE_TIMEOUT=60s
VELORAMESH_API_INGRESS_RATE_LIMIT_WINDOW=1m
VELORAMESH_API_INGRESS_RATE_LIMIT_MAX=12000
VELORAMESH_API_DEVICE_RATE_LIMIT_WINDOW=1m
VELORAMESH_API_DEVICE_RATE_LIMIT_MAX=120
VELORAMESH_API_AUTH_RATE_LIMIT_WINDOW=1m
VELORAMESH_API_AUTH_RATE_LIMIT_MAX=20
VELORAMESH_EDGE_INGRESS_RATE_LIMIT_WINDOW=1m
VELORAMESH_EDGE_INGRESS_RATE_LIMIT_MAX=12000
VELORAMESH_EDGE_CLIENT_RATE_LIMIT_WINDOW=1m
VELORAMESH_EDGE_CLIENT_RATE_LIMIT_MAX=120
VELORAMESH_TRUST_PROXY_HEADERS=0
VELORAMESH_TRUSTED_PROXY_CIDRS=
ENV
    install -o root -g "$SERVICE_GROUP" -m 0640 "$tmp_env" "$ENV_FILE"
    rm -f "$tmp_env"
}

write_edge_env_file() {
    local tmp_env
    tmp_env="$(mktemp "$TRANSACTION_DIR/environment.XXXXXX")"
    cat > "$tmp_env" <<ENV
VELORAMESH_EDGE_BIND_ADDR=$EDGE_BIND_ADDR
VELORAMESH_EDGE_TICKET_KEY_FILE=$DATA_DIR/edge-ticket.key
VELORAMESH_SHUTDOWN_TIMEOUT=10s
VELORAMESH_EDGE_INGRESS_RATE_LIMIT_WINDOW=1m
VELORAMESH_EDGE_INGRESS_RATE_LIMIT_MAX=12000
VELORAMESH_EDGE_CLIENT_RATE_LIMIT_WINDOW=1m
VELORAMESH_EDGE_CLIENT_RATE_LIMIT_MAX=120
ENV
    install -o root -g "$SERVICE_GROUP" -m 0640 "$tmp_env" "$ENV_FILE"
    rm -f "$tmp_env"
}

while [ "$#" -gt 0 ]; do
    case "$1" in
        --role)
            [ "$#" -ge 2 ] || fail "--role requires all, api, or edge"
            ROLE="$(normalize_role "$2")"
            shift 2
            ;;
        --api-bind)
            [ "$#" -ge 2 ] || fail "--api-bind requires a listen address"
            API_BIND_ADDR="$2"
            API_BIND_ADDR_EXPLICIT=1
            shift 2
            ;;
        --edge-bind)
            [ "$#" -ge 2 ] || fail "--edge-bind requires a listen address"
            EDGE_BIND_ADDR="$2"
            EDGE_BIND_ADDR_EXPLICIT=1
            shift 2
            ;;
        --default-virtual-ipv4-cidr)
            [ "$#" -ge 2 ] || fail "--default-virtual-ipv4-cidr requires a CIDR"
            DEFAULT_VIRTUAL_IPV4_CIDR="$2"
            DEFAULT_VIRTUAL_IPV4_CIDR_EXPLICIT=1
            shift 2
            ;;
        --binary)
            [ "$#" -ge 2 ] || fail "--binary requires a path"
            SOURCE_BINARY="$2"
            shift 2
            ;;
        --edge-binary)
            [ "$#" -ge 2 ] || fail "--edge-binary requires a path"
            EDGE_SOURCE_BINARY="$2"
            shift 2
            ;;
        --edge-ticket-key)
            [ "$#" -ge 2 ] || fail "--edge-ticket-key requires a path"
            EDGE_TICKET_KEY_SOURCE="$2"
            shift 2
            ;;
        --no-start)
            START_SERVICE=0
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            fail "unknown option: $1"
            ;;
    esac
done

ROLE="$(normalize_role "$ROLE")"
INSTALL_API=0
INSTALL_EDGE=0
CREATE_EDGE_KEY=0
case "$ROLE" in
    all)
        INSTALL_API=1
        INSTALL_EDGE=1
        CREATE_EDGE_KEY=1
        ;;
    api)
        INSTALL_API=1
        CREATE_EDGE_KEY=1
        ;;
    edge)
        INSTALL_EDGE=1
        ;;
esac

[ "$(id -u)" -eq 0 ] || fail "run this installer as root"

for managed_path in "$INSTALL_DIR" "$CONFIG_DIR" "$DATA_DIR" "$SYSTEMD_DIR"; do
    [[ "$managed_path" == /* ]] || fail "installation directories must be absolute paths"
    [[ "$managed_path" != *$'\n'* && "$managed_path" != *$'\r'* ]] || fail "installation directories must not contain line breaks"
done

resolve_existing_listen_addrs
if [ "$INSTALL_API" -eq 1 ]; then
    validate_listen_addr "API" "$API_BIND_ADDR"
    validate_virtual_ipv4_cidr "$DEFAULT_VIRTUAL_IPV4_CIDR"
fi
if [ "$INSTALL_EDGE" -eq 1 ] || [ "$CREATE_EDGE_KEY" -eq 1 ]; then
    validate_listen_addr "Edge" "$EDGE_BIND_ADDR"
fi

need_command install
need_command getent
need_command id
need_command systemctl
need_command useradd
need_command groupadd
need_command mktemp
need_command find
need_command grep
need_command sed
need_command cp

if [ "$INSTALL_API" -eq 1 ]; then
    [ -f "$SOURCE_BINARY" ] || fail "server binary not found: $SOURCE_BINARY"
fi
if [ "$INSTALL_EDGE" -eq 1 ] || [ "$CREATE_EDGE_KEY" -eq 1 ]; then
    [ -f "$EDGE_SOURCE_BINARY" ] || fail "edge binary not found: $EDGE_SOURCE_BINARY"
fi
if [ -n "$EDGE_TICKET_KEY_SOURCE" ]; then
    [ -f "$EDGE_TICKET_KEY_SOURCE" ] || fail "edge ticket key not found: $EDGE_TICKET_KEY_SOURCE"
fi
require_clean_edge_role_target

if ! getent group "$SERVICE_GROUP" >/dev/null; then
    log "creating group $SERVICE_GROUP"
    groupadd --system "$SERVICE_GROUP"
fi

if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then
    log "creating service user $SERVICE_USER"
    useradd --system --gid "$SERVICE_GROUP" --home-dir "$DATA_DIR" --shell /usr/sbin/nologin "$SERVICE_USER"
fi

log "install role: $(role_label)"
log "creating directories"
install -d -o root -g root -m 0755 "$INSTALL_DIR"
install -d -o root -g root -m 0755 "$SYSTEMD_DIR"
install -d -o root -g "$SERVICE_GROUP" -m 0750 "$CONFIG_DIR"
if [ "$INSTALL_API" -eq 1 ]; then
    install -d -o root -g "$SERVICE_GROUP" -m 0750 "$TLS_DIR"
fi
install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 0750 "$DATA_DIR"
repair_data_permissions
REMOVE_EDGE_BINARY_AFTER_KEY=0
begin_install_transaction
stage_install_binaries
INSTALL_MUTATION_STARTED=1
stop_services_for_install

if [ "$INSTALL_API" -eq 1 ]; then
    log "installing API binary"
    mv "$STAGED_API_BINARY" "$INSTALL_DIR/veloramesh-server"
    STAGED_API_BINARY=""
else
    systemctl disable "$SERVICE_NAME" 2>/dev/null || true
    rm -f -- "$UNIT_FILE" "$INSTALL_DIR/veloramesh-server"
fi

if [ "$INSTALL_EDGE" -eq 1 ]; then
    log "installing edge binary"
    mv "$STAGED_EDGE_BINARY" "$INSTALL_DIR/veloramesh-edge"
    STAGED_EDGE_BINARY=""
else
    systemctl disable "$EDGE_SERVICE_NAME" 2>/dev/null || true
    rm -f -- "$EDGE_UNIT_FILE"
    REMOVE_EDGE_BINARY_AFTER_KEY=1
fi

if [ ! -f "$ENV_FILE" ]; then
    log "creating environment file"
    if [ "$ROLE" = "edge" ]; then
        write_edge_env_file
    else
        write_full_env_file
    fi
else
    log "keeping existing environment file"
fi

if [ "$ROLE" = "edge" ]; then
    remove_env_values \
        VELORAMESH_API_BIND_ADDR \
        VELORAMESH_SERVER_TLS_CERT \
        VELORAMESH_SERVER_TLS_KEY \
        VELORAMESH_STORE_BACKEND \
        VELORAMESH_STORE_PATH \
        VELORAMESH_TICKET_KEY_FILE \
        VELORAMESH_SERVER_ALLOW_INSECURE \
        VELORAMESH_MAX_HEADER_BYTES \
        VELORAMESH_MAX_BODY_BYTES \
        VELORAMESH_API_MAX_CONNECTIONS \
        VELORAMESH_API_MAX_IN_FLIGHT_REQUESTS \
        VELORAMESH_API_HTTP2_MAX_CONCURRENT_STREAMS \
        VELORAMESH_SESSION_TTL \
        VELORAMESH_MEMBERSHIP_TICKET_TTL \
        VELORAMESH_READ_HEADER_TIMEOUT \
        VELORAMESH_READ_TIMEOUT \
        VELORAMESH_WRITE_TIMEOUT \
        VELORAMESH_IDLE_TIMEOUT \
        VELORAMESH_API_INGRESS_RATE_LIMIT_WINDOW \
        VELORAMESH_API_INGRESS_RATE_LIMIT_MAX \
        VELORAMESH_API_DEVICE_RATE_LIMIT_WINDOW \
        VELORAMESH_API_DEVICE_RATE_LIMIT_MAX \
        VELORAMESH_API_AUTH_RATE_LIMIT_WINDOW \
        VELORAMESH_API_AUTH_RATE_LIMIT_MAX \
        VELORAMESH_DEFAULT_VIRTUAL_IPV4_CIDR \
        VELORAMESH_TRUST_PROXY_HEADERS \
        VELORAMESH_TRUSTED_PROXY_CIDRS
else
    set_env_value VELORAMESH_API_BIND_ADDR "$API_BIND_ADDR"
    set_env_value VELORAMESH_SERVER_TLS_CERT "$TLS_CERT"
    set_env_value VELORAMESH_SERVER_TLS_KEY "$TLS_KEY"
    set_env_value VELORAMESH_STORE_BACKEND "sqlite"
    set_env_value VELORAMESH_STORE_PATH "$DATA_DIR/veloramesh.db"
    set_env_value VELORAMESH_DEFAULT_VIRTUAL_IPV4_CIDR "$DEFAULT_VIRTUAL_IPV4_CIDR"
    set_env_value VELORAMESH_TICKET_KEY_FILE "$DATA_DIR/membership-ticket.key"
    set_env_value VELORAMESH_MEMBERSHIP_TICKET_TTL "10m"
    set_env_value VELORAMESH_MAX_HEADER_BYTES "16384"
    set_env_value VELORAMESH_MAX_BODY_BYTES "65536"
    set_env_value VELORAMESH_API_MAX_CONNECTIONS "1024"
    set_env_value VELORAMESH_API_MAX_IN_FLIGHT_REQUESTS "256"
    set_env_value VELORAMESH_API_HTTP2_MAX_CONCURRENT_STREAMS "32"
    set_env_value VELORAMESH_SESSION_TTL "15m"
    set_env_value VELORAMESH_READ_HEADER_TIMEOUT "5s"
    set_env_value VELORAMESH_READ_TIMEOUT "15s"
    set_env_value VELORAMESH_WRITE_TIMEOUT "15s"
    set_env_value VELORAMESH_IDLE_TIMEOUT "60s"
    set_env_value VELORAMESH_API_INGRESS_RATE_LIMIT_WINDOW "1m"
    set_env_value VELORAMESH_API_INGRESS_RATE_LIMIT_MAX "12000"
    set_env_value VELORAMESH_API_DEVICE_RATE_LIMIT_WINDOW "1m"
    set_env_value VELORAMESH_API_DEVICE_RATE_LIMIT_MAX "120"
    set_env_value VELORAMESH_API_AUTH_RATE_LIMIT_WINDOW "1m"
    set_env_value VELORAMESH_API_AUTH_RATE_LIMIT_MAX "20"
    set_env_value VELORAMESH_TRUST_PROXY_HEADERS "0"
    set_env_value VELORAMESH_TRUSTED_PROXY_CIDRS ""
fi
set_env_value VELORAMESH_EDGE_BIND_ADDR "$EDGE_BIND_ADDR"
set_env_value VELORAMESH_EDGE_TICKET_KEY_FILE "$DATA_DIR/edge-ticket.key"
set_env_value VELORAMESH_SHUTDOWN_TIMEOUT "10s"
set_env_value VELORAMESH_EDGE_INGRESS_RATE_LIMIT_WINDOW "1m"
set_env_value VELORAMESH_EDGE_INGRESS_RATE_LIMIT_MAX "12000"
set_env_value VELORAMESH_EDGE_CLIENT_RATE_LIMIT_WINDOW "1m"
set_env_value VELORAMESH_EDGE_CLIENT_RATE_LIMIT_MAX "120"
chown root:"$SERVICE_GROUP" "$ENV_FILE"
chmod 0640 "$ENV_FILE"

if [ -n "$EDGE_TICKET_KEY_SOURCE" ]; then
    log "installing edge ticket key"
    install -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 0640 "$EDGE_TICKET_KEY_SOURCE" "$DATA_DIR/edge-ticket.key"
elif [ "$CREATE_EDGE_KEY" -eq 1 ] && [ ! -f "$DATA_DIR/edge-ticket.key" ]; then
    log "creating edge ticket key"
    run_edge_init_key
elif [ "$INSTALL_EDGE" -eq 1 ] && [ ! -f "$DATA_DIR/edge-ticket.key" ]; then
    log "edge ticket key is missing"
    log "copy the API host key to $DATA_DIR/edge-ticket.key or rerun with --edge-ticket-key <path>"
fi
if [ -f "$DATA_DIR/edge-ticket.key" ]; then
    chown "$SERVICE_USER:$SERVICE_GROUP" "$DATA_DIR/edge-ticket.key"
    chmod 0640 "$DATA_DIR/edge-ticket.key"
fi
if [ "$REMOVE_EDGE_BINARY_AFTER_KEY" -eq 1 ]; then
    rm -f -- "$INSTALL_DIR/veloramesh-edge"
fi

if [ "$INSTALL_API" -eq 1 ] && [ -f "$TLS_CERT" ]; then
    chown root:"$SERVICE_GROUP" "$TLS_CERT"
    chmod 0644 "$TLS_CERT"
fi

if [ "$INSTALL_API" -eq 1 ] && [ -f "$TLS_KEY" ]; then
    chown root:"$SERVICE_GROUP" "$TLS_KEY"
    chmod 0640 "$TLS_KEY"
fi

if [ "$INSTALL_API" -eq 1 ]; then
    log "installing API systemd unit"
    tmp_unit="$TRANSACTION_DIR/api-unit.new"
    cat > "$tmp_unit" <<UNIT
[Unit]
Description=VeloraMesh control plane
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=$SERVICE_USER
Group=$SERVICE_GROUP
EnvironmentFile=${ENV_FILE//%/%%}
ExecStart=$(systemd_quote "$INSTALL_DIR/veloramesh-server")
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=$(systemd_quote "$DATA_DIR")

[Install]
WantedBy=multi-user.target
UNIT
    install -o root -g root -m 0644 "$tmp_unit" "$UNIT_FILE"
    rm -f "$tmp_unit"
fi

if [ "$INSTALL_EDGE" -eq 1 ]; then
    log "installing edge systemd unit"
    tmp_edge_unit="$TRANSACTION_DIR/edge-unit.new"
    cat > "$tmp_edge_unit" <<UNIT
[Unit]
Description=VeloraMesh UDP discovery edge
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=$SERVICE_USER
Group=$SERVICE_GROUP
EnvironmentFile=${ENV_FILE//%/%%}
ExecStart=$(systemd_quote "$INSTALL_DIR/veloramesh-edge")
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=$(systemd_quote "$DATA_DIR")

[Install]
WantedBy=multi-user.target
UNIT
    install -o root -g root -m 0644 "$tmp_edge_unit" "$EDGE_UNIT_FILE"
    rm -f "$tmp_edge_unit"
fi

systemctl daemon-reload

if [ "$START_SERVICE" -eq 0 ]; then
    if [ "$INSTALL_API" -eq 1 ] && [ "$API_WAS_ACTIVE" -eq 1 ] && { [ ! -f "$TLS_CERT" ] || [ ! -f "$TLS_KEY" ]; }; then
        fail "cannot restore the previously active API service because TLS files are missing"
    fi
    if [ "$INSTALL_EDGE" -eq 1 ] && [ "$EDGE_WAS_ACTIVE" -eq 1 ] && [ ! -f "$DATA_DIR/edge-ticket.key" ]; then
        fail "cannot restore the previously active edge service because edge-ticket.key is missing"
    fi
    restart_previously_active_selected_services
    log "installed $(role_label) without starting new services"
    if { [ "$INSTALL_API" -eq 1 ] && [ "$API_WAS_ACTIVE" -eq 1 ]; } || { [ "$INSTALL_EDGE" -eq 1 ] && [ "$EDGE_WAS_ACTIVE" -eq 1 ]; }; then
        if { [ "$INSTALL_API" -eq 1 ] && [ "$API_WAS_ACTIVE" -eq 0 ]; } || { [ "$INSTALL_EDGE" -eq 1 ] && [ "$EDGE_WAS_ACTIVE" -eq 0 ]; }; then
            log "selected services that were inactive before the update remain stopped"
        fi
    else
        case "$ROLE" in
            all) log "after TLS is installed, run: sudo systemctl enable --now $SERVICE_NAME $EDGE_SERVICE_NAME" ;;
            api) log "after TLS is installed, run: sudo systemctl enable --now $SERVICE_NAME" ;;
            edge) log "after the edge ticket key is installed, run: sudo systemctl enable --now $EDGE_SERVICE_NAME" ;;
        esac
    fi
    commit_install_transaction
    exit 0
fi

if [ "$INSTALL_API" -eq 1 ] && { [ ! -f "$TLS_CERT" ] || [ ! -f "$TLS_KEY" ]; }; then
    log "TLS files are missing; service was installed but not started"
    log "expected cert: $TLS_CERT"
    log "expected key: $TLS_KEY"
    if [ "$ROLE" = "all" ]; then
        log "after TLS is installed, run: sudo systemctl enable --now $SERVICE_NAME $EDGE_SERVICE_NAME"
    else
        log "after TLS is installed, run: sudo systemctl enable --now $SERVICE_NAME"
    fi
    commit_install_transaction
    exit 0
fi

if [ "$INSTALL_EDGE" -eq 1 ] && [ ! -f "$DATA_DIR/edge-ticket.key" ]; then
    log "edge ticket key is missing; edge service was installed but not started"
    log "copy the API host key to $DATA_DIR/edge-ticket.key or rerun with --edge-ticket-key <path>"
    commit_install_transaction
    exit 0
fi

case "$ROLE" in
    all)
        log "enabling and starting API and edge services"
        systemctl enable --now "$SERVICE_NAME" "$EDGE_SERVICE_NAME"
        systemctl --no-pager --full status "$SERVICE_NAME"
        systemctl --no-pager --full status "$EDGE_SERVICE_NAME"
        ;;
    api)
        log "enabling and starting API service"
        systemctl enable --now "$SERVICE_NAME"
        systemctl --no-pager --full status "$SERVICE_NAME"
        ;;
    edge)
        log "enabling and starting edge service"
        systemctl enable --now "$EDGE_SERVICE_NAME"
        systemctl --no-pager --full status "$EDGE_SERVICE_NAME"
        ;;
esac

commit_install_transaction
