#!/bin/bash
# =============================================================================
# officer-setup — the pm2 ecosystem file, and starting the processes
# =============================================================================
#
# Definitions only.
#
# ── The ecosystem file is GENERATED, and is not in git ──
#
# There used to be four of them — ecosystem.config.cjs, .light., .mac.light. and
# a .profile. that the others derived from. A profile deriving from a full list
# means the full list has to exist, which means every plugin's process is
# described in the repository whether or not anybody installed it, and a test had
# to assert that the two files still agreed with each other.
#
# One generated file removes all of that. It describes exactly the processes this
# install runs, it is written once at setup, and nothing in git can drift from
# it. A plugin adds its own entry when it is installed.
#
# ── Why .cjs and not .js ──
#
# PM2's own convention is ecosystem.config.js, and it would be wrong here:
# package.json declares "type": "module", so a .js file in this directory is ESM
# and `module.exports` throws "module is not defined in ES module scope". PM2
# require()s the config, so the extension has to say CommonJS out loud.

[[ -n "${OFFICER_SETUP_SERVICES_LOADED:-}" ]] && return 0
OFFICER_SETUP_SERVICES_LOADED=1

ecosystem_file() { echo "$(platform_dir)/ecosystem.config.cjs"; }

# The processes a core install runs. Everything else is a plugin.
#
# `officer-pty` is node rather than bun, and that is not an oversight: it loads
# node-pty, a native module built against Node's ABI. Everything else is bun.
#
# `officer-claude-code` was `officer-agent` until 2026-08-13. The old name said
# nothing about what it runs, and it sits beside officer-anthropic-proxy — which
# is a different process doing a different job — so "the agent" was ambiguous
# exactly where it mattered. It spawns `claude`; the name says so now.
CORE_PROCESSES=(
  "officer|bun|start"
  "officer-anthropic-proxy|bun|run src/servers/sidecar/claude/index.ts"
  "officer-claude-code|bun|run src/servers/sidecar/claude/user-instance.ts"
  "officer-opencode|bun|run src/servers/sidecar/opencode/index.ts"
  "officer-pty|node|src/servers/sidecar/pty/index.mjs"
)

write_ecosystem() {
  local dest entry name script args
  dest="$(ecosystem_file)"

  {
    cat <<'HEADER'
// Generated by officer-setup. Not in git, and not meant to be — it describes THIS
// install, and the next machine generates its own.
//
// `cwd` is pinned on every app for two reasons. Bun auto-loads .env from the
// working directory (and the pty sidecar does `import 'dotenv/config'`), so
// without it a process started from anywhere else comes up with no POSTGRES_URL.
// And src/servers/data-path.ts derives the install root as the PARENT of the
// working directory, so a wrong cwd does not fail — it relocates data/,
// capabilities/ and dockers/ somewhere else entirely. `assertInstallLayout`
// refuses to boot when that happens.
//
// To add a plugin later, add its entry here. Nothing derives this file from
// anything, so there is no second list to keep it agreeing with.

module.exports = {
  apps: [
HEADER
    for entry in "${CORE_PROCESSES[@]}"; do
      IFS='|' read -r name script args <<<"$entry"
      printf "    { name: '%s', script: '%s', args: '%s', cwd: '%s', watch: false },\n" \
        "$name" "$script" "$args" "$(platform_dir)"
    done
    cat <<'FOOTER'
  ],
};
FOOTER
  } >"$dest"

  chown "${USERNAME}:$(user_group)" "$dest"
  return 0
}

pm2_start() {
  sudo -u "$USERNAME" bash -c "cd '$(platform_dir)' && pm2 startOrRestart '$(ecosystem_file)' --update-env" 2>&1
}

pm2_save() { sudo -u "$USERNAME" pm2 save 2>&1; }

# Survive a reboot. `pm2 startup` PRINTS a command for root to run rather than
# doing it — so this runs what it prints, which is the whole point of already
# being root here.
pm2_enable_startup() {
  local cmd
  cmd="$(sudo -u "$USERNAME" bash -c "cd '$(platform_dir)' && pm2 startup systemd -u '$USERNAME' --hp '$USER_HOME'" 2>/dev/null | grep -E '^sudo ' | tail -1)"
  [[ -z "$cmd" ]] && return 1
  eval "${cmd#sudo }"
}

# One line per process: name, status, restarts.
pm2_status_lines() {
  sudo -u "$USERNAME" pm2 jlist 2>/dev/null |
    node -e '
      let s = ""; process.stdin.on("data", (d) => (s += d)).on("end", () => {
        let apps = []; try { apps = JSON.parse(s); } catch { }
        for (const a of apps) {
          const st = a.pm2_env?.status ?? "?";
          console.log(`${a.name}|${st}|${a.pm2_env?.restart_time ?? 0}`);
        }
      });'
}
