# Platform CI: push to `main` → production, via Jenkins

Deploying the **platform itself** (the `paas` CLI + panel in this repo) used
to be a manual rsync + `sudo ./bootstrap.sh` on the production VM. It is now a
Jenkins job that lints every push to `main` and rolls it out through one
narrow SSH entry point on the server.

```
git push origin main
   │  (LAN Jenkins polls every 2 min — it is not reachable from the
   │   internet, so there is no inbound GitHub webhook by design)
   ▼
Jenkins job `paas-platform`  ── ci/Jenkinsfile.platform
   1. checkout            2. lint (bash -n, shellcheck, node --check)
   3. tar czf - . | ssh deploy@192.168.13.10 deploy <sha> "<message>"
   ▼
production VM, deploy user, CI key with forced command  ── infra/paas-ci-ssh
   └─ sudo -n /usr/local/sbin/paas-self-deploy deploy <sha> …  ── infra/paas-self-deploy
        extract → lint again → copy checkout to *.prev → rsync new tree in
        → ./bootstrap.sh with the instance's own settings → verify
```

`bootstrap.sh` is idempotent and is re-run exactly as `paas-init` would run
it: panel domain from `/srv/paas/panel/.env`, flavour + PHP versions from
`/srv/paas/paas.conf` (a Laravel server stays Laravel, a Node server stays
Node), ACME e-mail from the Caddyfile. Apps, `paas.db`, the panel `.env`,
certificates and keys are never touched. The CI key cannot do anything
except `deploy`, `verify`, `status`, `rollback` (see `infra/paas-ci-ssh`).

## One-time setup

1. **Jenkins → GitHub read access.** The LAN Jenkins already has the GitHub
   token credential `younex1` (used by the other jobs); the job clones
   `https://github.com/younex1/deploy-younex-web.git` with it. Nothing to do
   unless that credential is removed (`GIT_CRED_ID` below).
2. **Create credential + job** from the workstation (needs a Jenkins API
   token: *your user → Security → API token*):

   ```bash
   JENKINS_USER=<login> JENKINS_TOKEN=<token> ./ci/jenkins-setup.sh
   ```

   This generates `~/.ssh/jenkins-platform`, stores the private half in
   Jenkins as credential `paas-platform-deploy`, and creates the job
   `paas-platform` from `ci/jenkins-platform-job.xml`. Re-running updates the
   job in place. (The first setup on 2026-09-16 was done as root on the
   Jenkins host with a one-shot `init.groovy.d` script instead; the keypair
   lives in `/root/jenkins-platform` there. Both paths produce the same
   credential id and job.)
3. **Authorize the key on the server** (the command is printed by step 2):

   ```bash
   ssh younes@192.168.13.10 'cd dotnews-paas && sudo ./infra/setup-ci-deploy.sh "<contents of ~/.ssh/jenkins-platform.pub>"'
   ```

   That installs `paas-self-deploy`, the forced-command wrapper and the
   sudoers line (all three are also installed by every `bootstrap.sh`, so
   they are part of the golden template — inert until a key is authorized).
4. Push to `main`, or *Build Now*. The first run also takes the server's
   host key (`StrictHostKeyChecking=accept-new`).

## Day to day

| | |
|---|---|
| Deploy | `git push origin main` — live within ~3 minutes; build log in Jenkins, server log in `/var/log/paas-self-deploy.log` |
| What is live | `ssh younes@192.168.13.10 'sudo paas-self-deploy status'` (commit, time, message; `.deployed` in the checkout) |
| Health | `sudo paas-self-deploy verify` — caddy/varnish active, pm2 `paas-panel` online, panel `/login` 200, installed panel == checkout |
| Rollback | `sudo paas-self-deploy rollback` — swaps `/home/younes/dotnews-paas` with `…prev` and re-runs bootstrap |
| Manual deploy | `tar czf - -C <repo> . \| ssh younes@… 'sudo paas-self-deploy deploy <sha> "msg"'` (same path, no Jenkins) |

Lint failures on the server abort before anything is changed. A failed
bootstrap or verify fails the Jenkins build and leaves the new tree in place
for inspection; `rollback` restores the previous one.

## Other instances

Every server from the golden template has the same entry point. To let the
same Jenkins deploy a second instance, add its host to a copy of the
`Deploy` stage (or a second job with `PAAS_HOST` changed) and run
`infra/setup-ci-deploy.sh` there with the same public key.
