Initial skill library: 9 skills for delegate briefs
WordPress plugin rebrand/conventions/remote-CLI patterns, Gitea release workflow, bastille jail provisioning, remote shell quoting safety, server fleet map, delegate brief writing, and verification discipline -- all derived from real incidents this session, plus two skills adapted (MIT license, attributed) from obra/superpowers and andrej-karpathy-skills.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: bastille-jail-provisioning
|
||||
description: Use when creating a new FreeBSD bastille jail (a new site/service on a shared-LAN jail host) via clone-from-known-good-base. Covers the IP-conflict gotcha that has caused real, time-costly incidents twice.
|
||||
---
|
||||
|
||||
# Bastille Jail Provisioning
|
||||
|
||||
Pattern for standing up a new jail on a FreeBSD host running Bastille,
|
||||
by cloning a known-working base jail (already has nginx+php-fpm+packages
|
||||
configured) rather than building from scratch.
|
||||
|
||||
## CRITICAL FIRST STEP: arp-scan before picking an IP
|
||||
|
||||
**Confirmed real incident, twice, on two different hosts on the same
|
||||
shared LAN**: an IP address that looked free by every host-local check
|
||||
(`bastille list all`, `ifconfig` aliases, a manually-tracked "already
|
||||
used" list) turned out to already belong to a **different physical
|
||||
device, or a jail on a different host sharing the same LAN segment**.
|
||||
Symptom: the new jail's DNS resolution and outbound TCP connections
|
||||
silently fail or time out with no useful error — this looks exactly like
|
||||
a jail networking/pf config bug and can cost real time chasing the wrong
|
||||
theory before the actual cause (an IP conflict) is found.
|
||||
|
||||
**Before assigning ANY candidate IP, always:**
|
||||
```bash
|
||||
pkg install -y arp-scan # if not already present
|
||||
arp-scan --interface=vtnet0 <candidate-ip>
|
||||
```
|
||||
Zero responses = genuinely free. **Any** response (even from a device
|
||||
with no recognizable vendor match) means it's taken — pick a different
|
||||
IP and re-check. Do not rely on ping (misses devices with ICMP
|
||||
disabled/filtered — a real false-negative that happened once already) or
|
||||
on a per-host "what's already in use" list (each host only knows about
|
||||
its own jails, not other physical devices or other hosts' jails on the
|
||||
same shared subnet).
|
||||
|
||||
If you inherit a jail already provisioned on a conflicting IP: stop the
|
||||
jail, remove the bad alias, update `jail.conf`'s `ip4.addr`, start the
|
||||
jail again (this re-adds the alias on the new IP), then propagate the IP
|
||||
change everywhere else it's referenced (DB user grants scoped by IP,
|
||||
host-level nginx `proxy_pass` target, etc.) — an IP conflict fixed in
|
||||
`jail.conf` alone but not in the DB grant/nginx config leaves the site
|
||||
broken even though the jail itself is now healthy.
|
||||
|
||||
## Provisioning steps (once the IP is confirmed free)
|
||||
|
||||
1. **Clone from a known-good base jail** rather than building from
|
||||
scratch — inherits working nginx/php-fpm/package configuration:
|
||||
```bash
|
||||
bastille clone -a <known-good-base-jail> <new-jail-name> <new-ip>
|
||||
```
|
||||
2. **Wipe cloned content, do a fresh install** if this is a brand-new
|
||||
site (not a migration) — the cloned base's site files are a *template*
|
||||
for the stack config, not content you want to keep.
|
||||
3. **Create the database** on the host's local MariaDB, with the grant
|
||||
scoped to the **jail's own IP**, not the host's IP:
|
||||
```sql
|
||||
CREATE DATABASE `dbname` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER 'dbuser'@'<jail-ip>' IDENTIFIED BY '<random-password>';
|
||||
GRANT ALL PRIVILEGES ON `dbname`.* TO 'dbuser'@'<jail-ip>';
|
||||
```
|
||||
The application's `DB_HOST` config value is the **host's** own LAN IP
|
||||
(where MariaDB actually listens), NOT the jail's IP — the jail IP is
|
||||
only used for the GRANT's scope. Confirmed gotcha across multiple
|
||||
real migrations: mixing these two up is an easy, non-obvious mistake.
|
||||
4. **Wire the host-level nginx reverse-proxy vhost**, `proxy_pass`ing to
|
||||
the jail's IP.
|
||||
5. **Do NOT touch DNS or the reverse-proxy manager's public routing** as
|
||||
part of automated provisioning — that's a deliberate, manual cutover
|
||||
step for a human to do once the site is verified working over a
|
||||
direct IP/Host-header test. Build and verify fully "dark" first.
|
||||
6. **Smoke test** both directly to the jail IP and via the host nginx
|
||||
proxy (with the right `Host` header and, if the site checks for HTTPS
|
||||
via `X-Forwarded-Proto`, that header too) before considering
|
||||
provisioning done.
|
||||
|
||||
## Persist credentials immediately
|
||||
|
||||
Write the generated DB password (and any other generated secrets) to a
|
||||
durable location the moment they're generated — if a later provisioning
|
||||
step fails, an unrecorded generated password is otherwise lost with no
|
||||
way to recover it short of resetting it.
|
||||
Reference in New Issue
Block a user