#!/bin/sh
# 米游签 M·Desk 启动脚本
# - port 模式：直接监听 MDESK_PORT
# - socket 模式：应用监听本机随机端口，另起 Unix socket 反向代理并剥掉路径前缀
# MPK 只有 bin/run 是 0755；内置 Python 先复制到可写的 var 目录再执行。
set -eu

ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)"

case "$(uname -m)" in
    x86_64|amd64)  ARCH=x86_64 ;;
    aarch64|arm64) ARCH=aarch64 ;;
    *) echo "unsupported arch: $(uname -m)" >&2; exit 1 ;;
esac

: "${MDESK_DATA_DIR:?MDESK_DATA_DIR is required}"
: "${MDESK_VAR_DIR:?MDESK_VAR_DIR is required}"

umask 077
export PYTHONUTF8=1
export PYTHONDONTWRITEBYTECODE=1

mkdir -p "$MDESK_DATA_DIR/data" "$MDESK_DATA_DIR/logs" 2>/dev/null || true
LOG_DIR="${MDESK_LOG_DIR:-$MDESK_DATA_DIR/logs}"
if ! mkdir -p "$LOG_DIR" 2>/dev/null || ! : >>"$LOG_DIR/launcher.log" 2>/dev/null; then
    LOG_DIR="$MDESK_DATA_DIR/logs"
    mkdir -p "$LOG_DIR" 2>/dev/null || true
fi
LOG="$LOG_DIR/launcher.log"
log() {
    printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$LOG" 2>/dev/null || true
}
log "start arch=$ARCH port=${MDESK_PORT:-} socket=${MDESK_SOCKET:-} base=${MDESK_BASE_PATH:-} app=$ROOT"

PY_SRC="$ROOT/runtime/$ARCH/python/bin/python3.11"
if [ ! -f "$PY_SRC" ]; then
    log "missing python runtime: $PY_SRC"
    echo "python runtime missing for $ARCH: $PY_SRC" >&2
    exit 1
fi

export PYTHONHOME="$ROOT/runtime/$ARCH/python"

# 优先使用 var 目录中的副本，不要求应用目录可写。
PY="$MDESK_VAR_DIR/python3.11-$ARCH"
if [ ! -x "$PY" ] || [ "$PY_SRC" -nt "$PY" ]; then
    log "copy python -> $PY"
    mkdir -p "$MDESK_VAR_DIR"
    cp "$PY_SRC" "$PY.tmp.$$"
    chmod +x "$PY.tmp.$$"
    mv "$PY.tmp.$$" "$PY"
fi

if ! "$PY" -c 'pass' >/dev/null 2>&1; then
    log "copied python failed, fallback to app root"
    PY="$PY_SRC"
    chmod +x "$PY" 2>/dev/null || true
fi
if [ ! -x "$PY" ]; then
    log "cannot execute python: $PY"
    echo "cannot execute python: $PY" >&2
    exit 1
fi
log "python=$PY"

# 依赖 wheel 解压到可重建的 var 目录，升级或缓存清空后自动重建。
SITE="$MDESK_VAR_DIR/pysite-$ARCH"
STAMP="$SITE/.stamp"
WANT="${MDESK_APP_VERSION:-dev}"
if [ ! -f "$STAMP" ] || [ "$(cat "$STAMP" 2>/dev/null)" != "$WANT" ]; then
    log "extract wheels -> $SITE"
    rm -rf "$SITE"
    mkdir -p "$SITE"
    "$PY" - "$ROOT/wheels/$ARCH" "$SITE" <<'PY'
import pathlib
import sys
import zipfile

src = pathlib.Path(sys.argv[1])
dst = pathlib.Path(sys.argv[2])
for wheel in sorted(src.glob("*.whl")):
    with zipfile.ZipFile(wheel) as archive:
        archive.extractall(dst)
PY
    printf '%s' "$WANT" > "$STAMP"
fi

log "prepare data dir $MDESK_DATA_DIR"
mkdir -p "$MDESK_DATA_DIR/data" "$MDESK_DATA_DIR/logs"
if [ ! -f "$MDESK_DATA_DIR/config.yaml" ]; then
    cp "$ROOT/app/config.example.yaml" "$MDESK_DATA_DIR/config.yaml"
fi
chmod 700 "$MDESK_DATA_DIR" "$MDESK_DATA_DIR/data" "$MDESK_DATA_DIR/logs" 2>/dev/null || true
chmod 600 "$MDESK_DATA_DIR/config.yaml" 2>/dev/null || true

export PYTHONPATH="$SITE"
cd "$ROOT"

if [ -n "${MDESK_PORT:-}" ]; then
    log "mode=port port=$MDESK_PORT"
    exec "$PY" "$ROOT/app/main.py" \
        --config "$MDESK_DATA_DIR/config.yaml" \
        serve --host 0.0.0.0 --port "$MDESK_PORT"
fi

if [ -n "${MDESK_SOCKET:-}" ]; then
    : "${MDESK_BASE_PATH:?MDESK_BASE_PATH is required in socket mode}"

    LOCAL_PORT="$("$PY" -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()')"
    log "mode=socket socket=$MDESK_SOCKET base=$MDESK_BASE_PATH local=$LOCAL_PORT"

    "$PY" "$ROOT/app/main.py" \
        --config "$MDESK_DATA_DIR/config.yaml" \
        serve --host 127.0.0.1 --port "$LOCAL_PORT" \
        >>"$LOG_DIR/app-stdout.log" 2>&1 &
    APP_PID=$!

    if ! "$PY" - "$LOCAL_PORT" <<'PY'
import sys
import time
import urllib.request

port = sys.argv[1]
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
for _ in range(60):
    try:
        opener.open(f"http://127.0.0.1:{port}/api/auth/status", timeout=1).read()
    except Exception:
        time.sleep(0.5)
    else:
        sys.exit(0)
sys.exit(1)
PY
    then
        log "app failed to become ready on 127.0.0.1:$LOCAL_PORT"
        kill "$APP_PID" 2>/dev/null || true
        exit 1
    fi
    log "app ready on 127.0.0.1:$LOCAL_PORT"

    export MDESK_SOCKET MDESK_BASE_PATH
    export MIYOUQIAN_LOCAL_PORT="$LOCAL_PORT"
    export MIYOUQIAN_PROXY_LOG="$LOG"

    "$PY" "$ROOT/bin/socket_proxy.py" &
    PROXY_PID=$!
    cleanup() {
        kill "$PROXY_PID" "$APP_PID" 2>/dev/null || true
    }
    trap 'cleanup; exit 0' INT TERM
    trap cleanup EXIT
    wait "$PROXY_PID"
    exit $?
fi

log "neither MDESK_PORT nor MDESK_SOCKET is set"
echo "neither MDESK_PORT nor MDESK_SOCKET is set" >&2
exit 1
