# WolfPanel Agent — Deployment Guide

Covers the downloads-server layout, channels, and the commands to run after
the `agent/` project itself is deployed to the downloads host. For key
generation and the release pipeline itself, see
[RELEASE_GUIDE.md](RELEASE_GUIDE.md). For rollback, see
[ROLLBACK_GUIDE.md](ROLLBACK_GUIDE.md).

## Downloads server directory layout

`deploy/release.conf` points `PUBLIC_ROOT` at this tree
(`/var/www/downloads.wolfpanel.net/public_html/agent` by default):

```text
PUBLIC_ROOT/
├── install.sh
├── uninstall.sh
├── stable/
│   ├── latest.json
│   ├── latest.tar.gz               # alias -> wolfpanel-agent-<version>.tar.gz
│   ├── wolfpanel-agent-<version>.tar.gz
│   ├── wolfpanel-agent-<version>.tar.gz.sha256
│   └── checksums.txt
├── beta/
│   └── ... (same structure as stable)
├── dev/
│   └── ... (same structure as stable)
└── archive/
    ├── stable/
    ├── beta/
    └── dev/
```

`publish-release.sh` never deletes an older versioned tarball from a channel
directory — only `latest.json`/`latest.tar.gz`/`checksums.txt` are
overwritten to point at the new release. `archive/<channel>/` is where older
versioned tarballs can be moved once a channel directory grows large; this
repo does not automate that move (there is no fixed retention policy for
public downloads), but the layout is reserved so an operator or a future cron
job can relocate files there without changing any client-facing paths.

## Channels

- **stable** — production-grade releases only.
- **beta** — pre-release candidates (`-rc.N`, `-beta.N` versions).
- **dev** — active development builds.

All three are signed with the **same** production key (see
[RELEASE_GUIDE.md](RELEASE_GUIDE.md) §1) — the installer and the agent embed
one fixed public key regardless of channel, they just fetch a different
channel's `latest.json`.

## Commands to run after the agent project is deployed

Run these on the downloads/release host, from the deployed `agent/` checkout:

```bash
cd /var/www/wolf/agent
git status
cat VERSION

# Point at the real production key (never the test key, never inline in
# your shell history if you can avoid it -- use a secrets manager where
# possible):
export RELEASE_PRIVATE_KEY_FILE="/secure/path/release_private_key.pem"

bash scripts/release.sh stable

# Spot-check what actually got published:
curl -fsSL https://downloads.wolfpanel.net/agent/stable/latest.json
curl -fsSL https://downloads.wolfpanel.net/agent/install.sh | head -40
curl -fsSL https://downloads.wolfpanel.net/agent/stable/latest.tar.gz -o /tmp/latest.tar.gz
sha256sum /tmp/latest.tar.gz
tar -tzf /tmp/latest.tar.gz | head -50
```

Then, on a **test** server (never on a machine you care about, until you've
done this once against a disposable VM):

```bash
curl -fsSL https://downloads.wolfpanel.net/agent/install.sh | sudo bash -s -- \
  --token TOKEN \
  --api-url https://api.wolfpanel.net \
  --channel stable
```

## Client install command (canonical)

```bash
curl -fsSL https://downloads.wolfpanel.net/agent/install.sh | sudo bash -s -- \
  --token TOKEN \
  --api-url https://api.wolfpanel.net \
  --channel stable
```

`--channel` accepts `stable` (default), `beta`, or `dev`. The installer:

1. resolves `latest.json` for the requested channel,
2. validates `manifest_version`, `channel`, `file`, `sha256`, `signature`,
   and `size_bytes` are all present and that `channel` matches what was
   requested,
3. downloads the tarball (retrying transient network failures),
4. verifies its size, then its SHA-256, then its cryptographic signature —
   in that order, refusing to extract anything until all three pass,
5. cross-checks the extracted `VERSION` file against the manifest's
   `version` before activating it,
6. installs the systemd service and registers with the Central API (if a
   token was given).

## Verifying a live deployment

```bash
bash scripts/verify-release.sh stable   # or beta / dev
```

This is safe to run repeatedly and does not modify anything; treat a
non-zero exit as "do not tell anyone to install from this channel yet."
