#!/usr/bin/env bash
# fork-fleet.sh — bootstrap a fresh fleet-management repo from this one.
#
# Mirrors the reusable tooling (scripts/, generic playbook templates,
# conventions section of CLAUDE.md, .gitignore) into a new directory
# and strips everything fleet-specific (servers/, stacks/, configs/,
# fleet-named playbooks, runbooks, status). The output is an empty
# but conventionally-organized workspace that's ready to populate
# with a different fleet.
#
# Deliberately:
#   * does NOT create a remote / push — that's a manual step so the
#     name + namespace are explicit
#   * does NOT carry git history — the new fleet starts with a clean
#     "Initial commit" so the source repo's commit log doesn't bleed
#     across
#   * refuses to overwrite an existing directory
#
# Usage:
#   scripts/fork-fleet.sh <destination-dir> [<fleet-name>]
#
# Examples:
#   scripts/fork-fleet.sh ~/development/acme-prod-management
#   scripts/fork-fleet.sh /tmp/test-fork test-fleet
#
# Default fleet-name is the destination dir's basename.

set -euo pipefail

DEST="${1:?usage: $0 <destination-dir> [<fleet-name>]}"
FLEET_NAME="${2:-$(basename "$DEST" | sed 's/-management$//')}"
SRC="$(cd "$(dirname "$0")/.." && pwd)"

bold() { printf '\033[1m%s\033[0m\n' "$*"; }
info() { printf '  %s\n' "$*"; }

if [ -e "$DEST" ]; then
    echo "FATAL: $DEST already exists — refusing to overwrite" >&2
    exit 1
fi

bold "→ forking $SRC"
info "  destination : $DEST"
info "  fleet name  : $FLEET_NAME"
echo

# ── 1. Mirror the reusable tooling ──────────────────────────────────
# Excludes are explicit so adding a new fleet-specific dir to the
# source doesn't accidentally leak into forks.
mkdir -p "$DEST"
rsync -a \
    --exclude='.git/' \
    --exclude='.claude/' \
    --exclude='stacks-mirror/' \
    --exclude='servers/*' \
    --exclude='stacks/*' \
    --exclude='configs/' \
    --exclude='docs/orientation.md' \
    --exclude='docs/runbooks/' \
    --exclude='docs/pfi/' \
    --exclude='MEMORY.md' \
    --exclude='STATUS.md' \
    --exclude='README.md' \
    --exclude='CLAUDE.md' \
    --exclude='playbooks/deploy-*' \
    --exclude='playbooks/decouple-*' \
    --exclude='scripts/upgrade-irv-ml1-cuda.sh' \
    "$SRC/" "$DEST/"

info "tooling copied"

# ── 2. Replace gitkeeps in the now-empty fleet dirs ─────────────────
mkdir -p "$DEST/servers" "$DEST/stacks" "$DEST/stacks-mirror" \
         "$DEST/configs" "$DEST/docs/runbooks"
touch "$DEST/servers/.gitkeep" "$DEST/stacks/.gitkeep" \
      "$DEST/configs/.gitkeep" "$DEST/docs/runbooks/.gitkeep"
# stacks-mirror is gitignored — no .gitkeep needed.

# ── 3. Skeleton CLAUDE.md ───────────────────────────────────────────
cat > "$DEST/CLAUDE.md" <<EOF
# CLAUDE.md

This workspace manages **${FLEET_NAME}** — servers, Docker stacks,
configs. Forked from a shared infra-management template; the
conventions below carry over, the server table + placement rules are
yours to fill in.

**New session starting here?** Once \`docs/orientation.md\` exists
read it first — fleet topology, backup architecture, governing
principles, and any host-specific gotchas.

**For SSH-driven work: use \`scripts/elway\`.** Write a playbook under
\`playbooks/<name>.yaml\` and run
\`scripts/elway <host> --playbook ...\` instead of chaining
\`ssh -t host 'sudo …'\` commands — handles sudo once lazily,
structured pass/change/fail reporting, idempotency via
\`creates:\` / \`when:\` / \`changed_when:\`. Template:
[\`playbooks/elway-smoke.yaml\`](playbooks/elway-smoke.yaml).

**Task visibility via task-board.** If the Claude Code session has the
\`task-board\` plugin enabled (installed from
\`git@gitea.phasefinal.com:vh/task-board.git\`), a card tracks work
in progress. Hooks flip the card on turn boundaries automatically;
call \`task_start\` / \`task_update\` / \`task_wait\` /
\`task_complete\` MCP tools to set the activity subheader and post
meaningful log entries.

When you launch a \`Bash\` tool with \`run_in_background: true\` (or
any long-running shell / monitor / poll loop), call
\`task_set_shells\` with one short description per active background
shell — and call it again with the updated list (or \`[]\`) when one
completes. The board flips a waiting card to **orange** while the
list is non-empty so the user can tell at a glance the session is
parked on background work, not stalled on them. Hooks have no way
to enumerate the bg-task list externally, so this is on the
assistant.

## Purpose

- Inventory of servers and their state
- Canonical copies of Docker Compose stacks deployed on those servers
- Scripts for inspecting and managing the infrastructure
- Conventions so all stacks look the same

This is a **reference workspace** — the authoritative copies of
compose files and configs live **on the servers** under
\`/opt/docker/compose/<stack>/\` and \`/opt/docker/conf/<stack>/\`.
This workspace mirrors them for version control, editing, and planning.

## Conventions (enforce for every new stack)

- **Compose location on server:** \`/opt/docker/compose/<stack>/compose.yaml\`
- **Config mounts on server:** \`/opt/docker/conf/<stack>/...\`
- **Networks:** external \`traefik-net\`, aliased as \`tnet\` in compose

  \`\`\`yaml
  networks:
    tnet:
      name: traefik-net
      external: true
  \`\`\`

- **GPU reservation:** prefer \`deploy.resources.reservations.devices\`
  with explicit \`device_ids\` for pinning

  \`\`\`yaml
  deploy:
    resources:
      reservations:
        devices:
          - driver: nvidia
            device_ids: ["1"]
            capabilities: [gpu]
  \`\`\`

- **Tunables:** \`.env\` in the same directory as \`compose.yaml\` —
  keep the compose file constant, edit the \`.env\`
- **Named volumes** for service state (pattern: \`<stack>_<name>\`)
- **Bind mounts** only for: model files, config files
  (\`/opt/docker/conf/...\`), docker socket where required
- **Restart policy:** \`restart: unless-stopped\` for daemons
- **Homepage labels** on user-facing services:

  \`\`\`yaml
  labels:
    - homepage.group=AI Systems
    - homepage.name=<ServiceName>
    - homepage.icon=mdi-<icon>
    - homepage.description=<short>
    - homepage.href=http://<host-ip>:<port>
  \`\`\`

- **Healthchecks** on services that expose HTTP

## Servers

| Name | IP | Role | Details |
|------|-----|------|---------|
| _(none yet — \`scripts/add-host.sh <name> <ip>\` to register)_ | | | |

## How to refresh a server's state

\`\`\`bash
scripts/refresh-server-info.sh                    # show help
scripts/refresh-server-info.sh all                # every host
scripts/refresh-server-info.sh <host>             # one host
scripts/refresh-server-info.sh --validate-only all
\`\`\`

To register a new server:

\`\`\`bash
scripts/add-host.sh <name> <ip-or-user@ip>
scripts/refresh-server-info.sh <name>
\`\`\`

## Stack tree convention (canonical vs mirror)

| tree | role | git | who writes | who reads |
|---|---|---|---|---|
| \`stacks/<stack>/\` | **canonical / intent** | tracked | you / Claude | \`deploy-stack.sh\` |
| \`stacks-mirror/<host>/<stack>/\` | **snapshot / reality** | gitignored | \`sync-stacks.sh\` | drift inspection |

\`\`\`bash
\$EDITOR stacks/<stack>/compose.yaml
scripts/deploy-stack.sh <host> <stack>             # diffs vs live, prompts y/N
scripts/sync-stacks.sh                             # drift check across all hosts
diff -ru stacks/<stack>/ stacks-mirror/<host>/<stack>/
\`\`\`

## Working rules

- **Copies, not symlinks.** Files here reflect what's on the server
  at the time of the last sync. Editing here doesn't change the
  server until you deploy.
- **Never commit secrets.** Use \`.env.example\` templates; real
  \`.env\` files live on the server (gitignored).
- **Surgical edits.** When fixing one stack, don't touch unrelated ones.
- **Sanity-check before deploying.** Run \`docker compose config\`
  (dry parse) before \`docker compose up -d\` on the server.
EOF

# ── 4. Skeleton README.md ───────────────────────────────────────────
cat > "$DEST/README.md" <<EOF
# ${FLEET_NAME}-management

Infrastructure-management workspace for the **${FLEET_NAME}** fleet —
inventory, canonical Docker compose stacks, deploy tooling.

Forked from the shared infra-management template; see
[\`CLAUDE.md\`](./CLAUDE.md) for conventions and tooling reference.

## Quick start

\`\`\`bash
# Register your first host
scripts/add-host.sh <name> <ip-or-user@ip>

# Pull a snapshot of its current state
scripts/refresh-server-info.sh <name>

# Smoke-test the elway runner against it
scripts/elway <name> --playbook playbooks/elway-smoke.yaml
\`\`\`

## Layout

\`\`\`
${FLEET_NAME}-management/
├── CLAUDE.md              # workspace conventions + assistant guidance
├── README.md              # this file
├── scripts/               # reusable tooling
│   ├── elway              # SSH-driven playbook runner (lazy sudo)
│   ├── server_inspect.sh
│   ├── refresh-server-info.sh
│   ├── add-host.sh
│   ├── deploy-stack.sh
│   ├── sync-stacks.sh
│   ├── add-digest-user.sh
│   └── fork-fleet.sh      # bootstrap another fleet from this one
├── playbooks/
│   └── elway-smoke.yaml   # template playbook
├── servers/<name>/        # one dir per registered host
└── stacks/<stack>/        # one dir per canonical stack
\`\`\`

## Onboarding the assistant

This workspace is intended to be opened in a Claude Code session
named after the fleet (e.g. \`${FLEET_NAME}-infra\`). The
\`CLAUDE.md\` file is auto-loaded and gives the assistant fleet
conventions and tool wiring.
EOF

# ── 5. Skeleton STATUS.md (optional rolling-status doc) ─────────────
cat > "$DEST/STATUS.md" <<EOF
# Status + Open Issues

Last updated: $(date '+%Y-%m-%d')

Snapshot of fleet state and open work. Refresh when something material
changes (host added, stack deployed, incident closed).

## Fleet summary

_(none yet — populate after first \`scripts/add-host.sh\`)_

## Open work

_(empty)_

## Known issues

_(empty)_
EOF

info "skeleton CLAUDE.md / README.md / STATUS.md written"

# ── 6. Initialize fresh git history ─────────────────────────────────
cd "$DEST"
git init -q -b main
git add .
# Quiet local commit — no remote, no signing fuss. The user's normal
# git config picks up author. If the source repo has a different user
# committing here would inherit it; explicit fallback to env keeps the
# script working in CI/headless contexts where git config isn't set.
if ! git -c user.useConfigOnly=true commit -q -m "Initial commit: forked from infra-management template

Stripped fleet-specific content; kept the reusable tooling
(scripts/, elway-smoke.yaml template, conventions section of CLAUDE.md,
.gitignore). Fleet name: ${FLEET_NAME}." 2>/dev/null; then
    git -c user.email=fork@local -c user.name=fork-fleet \
        commit -q -m "Initial commit: forked from infra-management template

Stripped fleet-specific content; kept the reusable tooling
(scripts/, elway-smoke.yaml template, conventions section of CLAUDE.md,
.gitignore). Fleet name: ${FLEET_NAME}."
fi

bold ""
bold "✓ forked into $DEST"
echo
echo "next steps:"
echo "  cd $DEST"
echo "  # 1. Edit CLAUDE.md — fill in the Servers table + any placement rules"
echo "  # 2. Add your first host"
echo "  scripts/add-host.sh <name> <ip-or-user@ip>"
echo "  scripts/refresh-server-info.sh <name>"
echo "  # 3. Wire up a remote when ready"
echo "  tea repo create vh/${FLEET_NAME}-management   # or whatever namespace"
echo "  git remote add origin git@gitea.phasefinal.com:vh/${FLEET_NAME}-management.git"
echo "  git push -u origin main"
