#!/bin/bash set -euo pipefail # ─── Kordant Scheduler — Pan Server Setup ───────────────────── # Usage: # bash scripts/setup-pan.sh # interactively # bash scripts/setup-pan.sh user@pan # interactively, remote # bash scripts/setup-pan.sh -u -d ... # # Flags: # -u, --gitea-url URL Gitea clone URL (skip prompt) # -d, --db-url URL Turso database URL (skip prompt) # -t, --db-token TOKEN Turso auth token (skip prompt) # -k, --hooks-dir DIR Gitea hooks directory (skip prompt; empty=skip hook) # --host HOST SSH host (default: pan) # -y, --non-interactive Skip all prompts (requires -u, -d, -t) # -h, --help Show this message # ─── Defaults ─────────────────────────────────────────────────── PAN_HOST="" GITEA_URL="" DB_URL="" DB_TOKEN="" HOOKS_DIR="" NON_INTERACTIVE=false REPO_DIR="/opt/kordant" # ─── Help ─────────────────────────────────────────────────────── usage() { sed -n 's/^# //p; s/^#$//p' "$0" exit 0 } # ─── Parse flags ──────────────────────────────────────────────── while [ $# -gt 0 ]; do case "$1" in -u|--gitea-url) GITEA_URL="$2"; shift 2 ;; -d|--db-url) DB_URL="$2"; shift 2 ;; -t|--db-token) DB_TOKEN="$2"; shift 2 ;; -k|--hooks-dir) HOOKS_DIR="$2"; shift 2 ;; --host) PAN_HOST="$2"; shift 2 ;; -y|--non-interactive) NON_INTERACTIVE=true; shift ;; -h|--help) usage ;; -*) echo "Unknown option: $1" usage ;; *) PAN_HOST="${PAN_HOST:-$1}"; shift ;; esac done PAN_HOST="${PAN_HOST:-pan}" # ─── Remote detection ─────────────────────────────────────────── if [ "$(hostname)" != "pan" ] && [ "$(hostname -s 2>/dev/null)" != "pan" ]; then if [ -n "${SSH_CONNECTION:-}" ]; then echo "Already connected to pan via SSH." else echo "Not on pan. Connecting to $PAN_HOST via SSH..." # Rebuild the flag string to pass through FLAGS="" [ -n "$GITEA_URL" ] && FLAGS="$FLAGS -u '$GITEA_URL'" [ -n "$DB_URL" ] && FLAGS="$FLAGS -d '$DB_URL'" [ -n "$DB_TOKEN" ] && FLAGS="$FLAGS -t '$DB_TOKEN'" [ -n "$HOOKS_DIR" ] && FLAGS="$FLAGS -k '$HOOKS_DIR'" $NON_INTERACTIVE && FLAGS="$FLAGS -y" scp "$0" "${PAN_HOST}:/tmp/kordant-setup.sh" ssh -t "$PAN_HOST" "sudo bash /tmp/kordant-setup.sh $FLAGS && rm /tmp/kordant-setup.sh" exit $? fi fi echo "=== Kordant Scheduler Setup (running on pan) ===" # ─── Step 1: Install prerequisites ───────────────────────────── echo "--- Step 1: Checking prerequisites ---" if ! command -v docker &>/dev/null; then echo "Installing Docker..." curl -fsSL https://get.docker.com | bash systemctl enable --now docker else echo "Docker already installed." fi if ! docker compose version &>/dev/null; then echo "Installing Docker Compose plugin..." apt-get update && apt-get install -y docker-compose-plugin else echo "Docker Compose plugin already installed." fi if ! command -v git &>/dev/null; then echo "Installing git..." apt-get update && apt-get install -y git else echo "Git already installed." fi # ─── Step 2: Clone or pull the repo ──────────────────────────── echo "--- Step 2: Setting up repo at $REPO_DIR ---" if [ ! -d "$REPO_DIR/.git" ]; then if [ -z "$GITEA_URL" ]; then if $NON_INTERACTIVE; then echo "❌ --gitea-url is required in non-interactive mode" exit 1 fi while [ -z "$GITEA_URL" ]; do read -rp "Gitea clone URL (e.g. http://localhost:3000/kordant/kordant.git): " GITEA_URL done fi echo "Cloning $GITEA_URL ..." git clone "$GITEA_URL" "$REPO_DIR" cd "$REPO_DIR" else echo "Repo exists. Pulling latest..." cd "$REPO_DIR" git pull fi # ─── Step 3: Create .env with credentials ────────────────────── echo "--- Step 3: Environment file ---" if [ ! -f "$REPO_DIR/.env" ]; then if [ -z "$DB_URL" ]; then if $NON_INTERACTIVE; then echo "❌ --db-url is required in non-interactive mode" exit 1 fi read -rp " DATABASE_URL (e.g. libsql://kordant.turso.io): " DB_URL fi if [ -z "$DB_TOKEN" ]; then if $NON_INTERACTIVE; then echo "❌ --db-token is required in non-interactive mode" exit 1 fi read -rsp " DATABASE_AUTH_TOKEN: " DB_TOKEN echo "" fi cat > "$REPO_DIR/.env" << ENVEOF # ─── Kordant Scheduler Environment ───────────────────────────── # Turso database DATABASE_URL="${DB_URL}" DATABASE_AUTH_TOKEN="${DB_TOKEN}" # Job queue (local Redis container) REDIS_URL="redis://redis:6379" # Node environment NODE_ENV=production JOB_WORKER=true JOB_PRIMARY=true ENVEOF chmod 600 "$REPO_DIR/.env" echo "✅ Created $REPO_DIR/.env" else echo "$REPO_DIR/.env already exists, keeping it." fi # ─── Step 4: Gitea post-receive hook ──────────────────────────── echo "--- Step 4: Gitea post-receive hook ---" if [ -z "$HOOKS_DIR" ] && ! $NON_INTERACTIVE; then read -rp "Gitea repo hooks directory (or leave blank to skip): " HOOKS_DIR fi if [ -n "$HOOKS_DIR" ]; then if [ ! -d "$HOOKS_DIR" ]; then echo " Directory not found: $HOOKS_DIR" if $NON_INTERACTIVE; then echo "❌ hooks-dir does not exist" exit 1 fi else HOOK_FILE="$HOOKS_DIR/post-receive" cat > "$HOOK_FILE" << 'HOOKEOF' #!/bin/bash cd /opt/kordant git pull origin main docker compose -f scheduler/docker-compose.yml up -d --build HOOKEOF chmod +x "$HOOK_FILE" echo "✅ Post-receive hook installed at $HOOK_FILE" fi else echo "Skipping post-receive hook." fi # ─── Step 5: Create systemd service ──────────────────────────── echo "--- Step 5: Systemd service ---" SERVICE_NAME="kordant-scheduler" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" if [ ! -f "$SERVICE_FILE" ]; then echo "Creating $SERVICE_FILE..." cat > "$SERVICE_FILE" << SERVICEEOF [Unit] Description=Kordant Background Job Scheduler After=docker.service network-online.target Wants=docker.service network-online.target [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=$REPO_DIR ExecStart=docker compose -f scheduler/docker-compose.yml up -d --build ExecStop=docker compose -f scheduler/docker-compose.yml down ExecReload=docker compose -f scheduler/docker-compose.yml pull && docker compose -f scheduler/docker-compose.yml up -d --build StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target SERVICEEOF systemctl daemon-reload else echo "$SERVICE_FILE already exists." fi # ─── Step 6: Start / restart the scheduler ───────────────────── echo "--- Step 6: Starting scheduler ---" systemctl enable "$SERVICE_NAME" 2>/dev/null || true cd "$REPO_DIR" docker compose -f scheduler/docker-compose.yml pull 2>/dev/null || true docker compose -f scheduler/docker-compose.yml up -d --build echo "" echo "=== Scheduler status ===" sleep 3 docker compose -f scheduler/docker-compose.yml ps echo "" echo "=== Kordant scheduler setup complete ===" echo " Repo: $REPO_DIR" echo " Logs: journalctl -u kordant-scheduler -f" echo " Shell: cd $REPO_DIR && docker compose -f scheduler/docker-compose.yml logs -f" echo " .env: $REPO_DIR/.env"