Labels every command with who runs it and where
Four contexts got tangled together across the doc as it grew: pi-main as root, pi-main as gitea-workflow, the separate proxy Pi, and Gitea's own web UI. Added a legend up front and tagged every single command block so it's no longer something the reader has to infer.
This commit is contained in:
+55
-12
@@ -10,6 +10,15 @@ software itself. SlpModularCms can host multiple customers; `slpsoftware` is thi
|
||||
and the owner's own site. A future second customer on the same Pi would get its own instance name
|
||||
throughout, following the same pattern.
|
||||
|
||||
**Who runs what, where** — three hosts and two accounts appear below; every command block is
|
||||
labelled with one of these:
|
||||
- 🖥️ **pi-main / root** — as yourself with `sudo`, on the Pi this application runs on
|
||||
- 👤 **pi-main / `gitea-workflow`** — as the deploy account, in a **real login shell** (SSH in
|
||||
directly as `gitea-workflow`, not `su -`/`sudo -i -u` from your own session — § 1.3 explains why
|
||||
that distinction matters)
|
||||
- 🌐 **proxy Pi / root** — as yourself with `sudo`, on the *separate* Pi that terminates TLS (§ 1.7)
|
||||
- ☁️ **Gitea web UI** — not a host command at all; done in this repository's Actions settings
|
||||
|
||||
---
|
||||
|
||||
## 1. One-Time Host Setup
|
||||
@@ -37,6 +46,8 @@ named after this project, since it may end up deploying other projects on this P
|
||||
`SlpSoftware` project's own pipeline may already have exactly this kind of account; check its
|
||||
`PI_MAIN_USERNAME` secret first and reuse it directly if it already has SSH shell access, rather than
|
||||
creating a second one). Example name: `gitea-workflow`.
|
||||
|
||||
🖥️ **pi-main / root:**
|
||||
```bash
|
||||
sudo useradd -m -s /bin/bash gitea-workflow
|
||||
sudo passwd gitea-workflow
|
||||
@@ -56,6 +67,8 @@ The rest of this document uses `gitea-workflow` as the example account name —
|
||||
if you pick something else.
|
||||
|
||||
### 1.3 Enable Lingering (INFRA-U6-01 — do this first, easy to forget)
|
||||
|
||||
🖥️ **pi-main / root:**
|
||||
```bash
|
||||
sudo loginctl enable-linger gitea-workflow
|
||||
```
|
||||
@@ -69,6 +82,8 @@ running independent of any login session.
|
||||
setting the rest of this up manually (before the deploy workflow ever runs): enabling lingering
|
||||
does not retroactively start the user manager — that happens on the next real login, reboot, or
|
||||
manually:
|
||||
|
||||
🖥️ **pi-main / root:**
|
||||
```bash
|
||||
sudo systemctl start user@$(id -u gitea-workflow).service
|
||||
```
|
||||
@@ -84,6 +99,8 @@ Two fixes, in order of preference:
|
||||
- **SSH in directly as `gitea-workflow`** instead of logging in as yourself and switching user —
|
||||
a real SSH login does go through `sshd`'s PAM stack and sets `XDG_RUNTIME_DIR` correctly
|
||||
- Or, after `su -`/`sudo -i -u`, just set it by hand once per shell:
|
||||
|
||||
👤 **pi-main / `gitea-workflow`:**
|
||||
```bash
|
||||
export XDG_RUNTIME_DIR=/run/user/$(id -u)
|
||||
```
|
||||
@@ -102,6 +119,7 @@ Deliberately placed under `gitea-workflow`'s **own home directory**, not under
|
||||
for the CMS's own release/current/shared structure to live anywhere near the other websites at all,
|
||||
which directly avoids interfering with them (as you asked in Q2):
|
||||
|
||||
👤 **pi-main / `gitea-workflow`:**
|
||||
```bash
|
||||
mkdir -p ~/apps/slpsoftware/<env>/releases
|
||||
```
|
||||
@@ -113,6 +131,7 @@ uploads *this* customer's website via FileZilla, e.g. `/mnt/storage1/www/html/sl
|
||||
(adjust the exact folder name to whatever convention the other sites under `html/` already use, if
|
||||
one exists). Rather than a plain directory, make it a symlink across accounts:
|
||||
|
||||
👤 **pi-main / `gitea-workflow`:**
|
||||
```bash
|
||||
mkdir -p ~/apps/slpsoftware/<env>
|
||||
ln -s /mnt/storage1/www/html/slpsoftware/<env> ~/apps/slpsoftware/<env>/shared/wwwroot-web
|
||||
@@ -122,12 +141,17 @@ ln -s /mnt/storage1/www/html/slpsoftware/<env> ~/apps/slpsoftware/<env>/shared/w
|
||||
needs no changes for this — it only ever resolves the symlink chain, it doesn't care how many hops
|
||||
that chain has. What **does** need attention: `gitea-workflow` needs read + traverse permission on
|
||||
`/mnt/storage1/www/html/slpsoftware/<env>/` and its parent directories, which `webadmin` owns.
|
||||
Simplest fix: put both accounts in a shared group (e.g. `webshared`), `chgrp -R webshared` that
|
||||
folder, and make sure webadmin's FTP server creates new uploads group-readable (`g+rx`, not just
|
||||
Simplest fix: put both accounts in a shared group (e.g. `webshared`), then, 🖥️ **pi-main / root**:
|
||||
```bash
|
||||
sudo chgrp -R webshared /mnt/storage1/www/html/slpsoftware/<env>
|
||||
```
|
||||
and make sure webadmin's FTP server creates new uploads group-readable (`g+rx`, not just
|
||||
owner-readable) — a one-time permission setup, not something either pipeline touches per deploy.
|
||||
|
||||
### 1.5 Runtime Configuration File
|
||||
One file per environment, **outside** the release directory so it survives every switch:
|
||||
|
||||
👤 **pi-main / `gitea-workflow`:**
|
||||
```bash
|
||||
touch ~/apps/slpsoftware/<env>/shared/env
|
||||
chmod 600 ~/apps/slpsoftware/<env>/shared/env
|
||||
@@ -187,7 +211,8 @@ gate only catches drift between the frontend build and that Gitea variable; it c
|
||||
so keeping the two in sync is a manual discipline, not something enforced automatically.
|
||||
|
||||
### 1.6 systemd User Units
|
||||
Create `~/.config/systemd/user/slpsoftware-test.service`:
|
||||
|
||||
👤 **pi-main / `gitea-workflow`** — create `~/.config/systemd/user/slpsoftware-test.service`:
|
||||
```ini
|
||||
[Unit]
|
||||
Description=SlpModularCms API (test)
|
||||
@@ -209,6 +234,8 @@ And `~/.config/systemd/user/slpsoftware-production.service` — identical, with
|
||||
`production` throughout (including the port inside `shared/env`).
|
||||
|
||||
Enable both (does not start them yet — nothing is deployed there until the first CI run):
|
||||
|
||||
👤 **pi-main / `gitea-workflow`** (real login shell — § 1.3):
|
||||
```bash
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable slpsoftware-test.service
|
||||
@@ -251,6 +278,7 @@ or container on the LAN — pi-main and its two ports, in this deployment's case
|
||||
server block for the domain to attach to and to answer the HTTP-01 validation challenge; asking
|
||||
for a certificate before this exists will fail.
|
||||
|
||||
🌐 **proxy Pi / root:**
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
@@ -275,12 +303,15 @@ is answering on port 80, which is the proxy Pi.
|
||||
**Step 2 — request and install the certificate.** The nginx plugin edits the file from step 1 in
|
||||
place: it adds the `listen 443 ssl` block, the certificate/key paths, and (by default) an
|
||||
HTTP→HTTPS redirect for the port 80 block:
|
||||
|
||||
🌐 **proxy Pi / root:**
|
||||
```bash
|
||||
sudo certbot --nginx -d <new-domain>
|
||||
```
|
||||
Certbot's own systemd timer handles renewal automatically — nothing further to set up for that.
|
||||
|
||||
**Step 3 — verify.** After certbot finishes:
|
||||
**Step 3 — verify.** Can be run from anywhere with network access to the domain (your own machine
|
||||
is fine — this isn't tied to either Pi). After certbot finishes:
|
||||
```bash
|
||||
curl -I https://<new-domain>/health # or whichever path this new site/API actually serves
|
||||
```
|
||||
@@ -292,8 +323,9 @@ site — nginx and certbot don't know or care what's actually listening on the b
|
||||
|
||||
**Step 4 — restrict the backend port on pi-main (do this once the domain works end to end).**
|
||||
Since Kestrel now listens on `0.0.0.0:<backend-port>` (§ 1.5), anything on the LAN can reach it
|
||||
directly, bypassing the proxy Pi's TLS entirely, unless pi-main's firewall says otherwise. On
|
||||
**pi-main**:
|
||||
directly, bypassing the proxy Pi's TLS entirely, unless pi-main's firewall says otherwise.
|
||||
|
||||
🖥️ **pi-main / root:**
|
||||
```bash
|
||||
sudo ufw allow from <proxy-pi-lan-ip> to any port <backend-port> proto tcp
|
||||
sudo ufw deny <backend-port>/tcp
|
||||
@@ -331,6 +363,8 @@ server {
|
||||
```
|
||||
|
||||
### 1.8 Database Backup Credentials (§ 4 depends on this)
|
||||
|
||||
👤 **pi-main / `gitea-workflow`:**
|
||||
```bash
|
||||
touch ~/.config/slpsoftware-db-backup.env
|
||||
chmod 600 ~/.config/slpsoftware-db-backup.env
|
||||
@@ -347,7 +381,9 @@ credential, ideally scoped to just read access (`SELECT`, `LOCK TABLES` — ever
|
||||
needs) rather than the application's own data-access login.
|
||||
|
||||
### 1.9 Gitea Actions Variables and Secrets
|
||||
Set once, in this repository's Gitea Actions settings. This is exactly where the real path lives —
|
||||
|
||||
☁️ **Gitea web UI** — not a host command, no SSH involved. Set once, in this repository's Actions
|
||||
settings. This is exactly where the real path lives —
|
||||
`deploy-scp.yaml` never hardcodes it, it only reads `${{ vars.DEPLOY_PATH_TEST }}` /
|
||||
`${{ vars.DEPLOY_PATH_PRODUCTION }}`, so changing the path later is a variable edit, not a workflow
|
||||
change:
|
||||
@@ -389,13 +425,15 @@ Already built (U5/U6) — this is the read-only walkthrough for whoever operates
|
||||
- `wwwroot/web/` will be empty until a website workspace deploys into it — `/` serves the built-in
|
||||
placeholder until then (this is expected, not a failure)
|
||||
- The very first run has no "previous release" to keep — pruning naturally has nothing to prune
|
||||
- Verify manually after the first run: `curl https://test.slpsoftware.nl/health` and
|
||||
`curl https://slpsoftware.nl/health` (after the first production run) both return `200`
|
||||
- Verify manually after the first run (from anywhere — not tied to either Pi):
|
||||
`curl https://test.slpsoftware.nl/health` and `curl https://slpsoftware.nl/health` (after the
|
||||
first production run) both return `200`
|
||||
|
||||
## 4. Database Backup Script
|
||||
|
||||
Create `~/scripts/backup-slpmodularcms-db.sh` on the Pi (this script is host-side by design — never
|
||||
part of this repository, so no DB credential ever reaches Gitea):
|
||||
👤 **pi-main / `gitea-workflow`** — create `~/scripts/backup-slpmodularcms-db.sh` on the Pi (this
|
||||
script is host-side by design — never part of this repository, so no DB credential ever reaches
|
||||
Gitea):
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
@@ -433,11 +471,13 @@ ls -1t "$BACKUP_DIR"/*.sql.gz 2>/dev/null | tail -n +8 | xargs -r rm -f
|
||||
`mariadb-dump` is MariaDB's own name for the tool (present since MariaDB 10.4-ish); if the host only
|
||||
has the older `mysqldump` name, substitute it — same tool, same flags.
|
||||
|
||||
👤 **pi-main / `gitea-workflow`:**
|
||||
```bash
|
||||
chmod +x ~/scripts/backup-slpmodularcms-db.sh
|
||||
```
|
||||
|
||||
**Verify once, manually**, before relying on it in a real deploy:
|
||||
**Verify once, manually**, before relying on it in a real deploy — 👤 **pi-main / `gitea-workflow`**
|
||||
(this matches exactly how `deploy-scp.yaml` itself invokes the script over SSH):
|
||||
```bash
|
||||
~/scripts/backup-slpmodularcms-db.sh production
|
||||
```
|
||||
@@ -445,6 +485,9 @@ Confirm a `.sql.gz` file appears under `~/backups/slpsoftware/production/` and t
|
||||
silently fail (the script uses `set -euo pipefail`, so a real error does propagate as a non-zero
|
||||
exit — which fails the calling `deploy-scp.yaml` step, correctly blocking the deploy). Worth a
|
||||
one-time restore rehearsal too — an untested backup is not a verified one:
|
||||
|
||||
👤 **pi-main / `gitea-workflow`** (or wherever you have a MariaDB client and network access to
|
||||
`127.0.0.1`'s database — this one isn't identity-sensitive, just needs DB access):
|
||||
```bash
|
||||
gunzip -c ~/backups/slpsoftware/production/<file>.sql.gz | mariadb -h 127.0.0.1 -u root -p <a-scratch-database>
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user