158 lines
12 KiB
Markdown
158 lines
12 KiB
Markdown
# First-server bootstrap runbook
|
|
|
|
- Status: Draft operational procedure; commands/artifacts not yet implemented
|
|
- Target: `zarinpal.amiirkhl.ir`
|
|
- Owner: Platform operator
|
|
- Last updated: 2026-08-20
|
|
|
|
This is the safe order for preparing a new production host. It is intentionally a human-reviewed checklist, not a copy-paste bootstrap script. Stop on any failed validation and preserve the existing server/DNS state until the failure is understood.
|
|
|
|
## Preconditions and recorded inputs
|
|
|
|
Before connecting to the host, record in the change ticket/release record:
|
|
|
|
- server provider, instance id, public IPv4/IPv6, region, CPU, RAM, disk type/size, and console/recovery access
|
|
- approved OS image and checksum/source; initial target is a supported Ubuntu LTS minimal image
|
|
- operator names, maintenance window, rollback/abandonment condition, and emergency contact
|
|
- DNS zone owner and current `A`/`AAAA`/CAA records for `zarinpal.amiirkhl.ir`
|
|
- SSH source allowlist and at least two tested operator public keys
|
|
- dedicated deployment username that will receive Docker-group access
|
|
- approved deployment, backend, and frontend repository URLs and expected nested paths
|
|
- object-storage bucket/endpoint, retention capability, backup credentials, and separately escrowed encryption key
|
|
- target `main` branches and an empty location for the first three-hash release record
|
|
- secret inventory and owner; never put values in the ticket, shell history, or this repository
|
|
|
|
Do not change DNS before the host passes local and direct-IP validation. Do not reuse Qlockify credentials, certificates, volumes, backup paths, or deploy keys.
|
|
|
|
## Phase 1: establish recoverable access
|
|
|
|
1. Open the provider console/recovery channel and confirm it works independently of SSH.
|
|
2. Log in using the provider's initial method. Create named operator accounts and install only approved public keys.
|
|
3. Confirm a second operator can log in in a new session before changing SSH configuration.
|
|
4. Disable direct root SSH and password authentication only after key access and recovery console are proven.
|
|
5. Restrict SSH to the operator source allowlist at the provider firewall and host firewall. Keep the current session open while testing a new one.
|
|
6. Configure time synchronization and verify UTC plus the documented `Asia/Tehran` application assumption. The host itself should log in UTC.
|
|
7. Apply all supported security updates and reboot if required. Re-test console and SSH access after reboot.
|
|
|
|
Evidence: OS release/kernel, update result, time-sync state, two successful key logins, failed password/root-login test, and firewall rules. Do not record public key private material or session output containing secrets.
|
|
|
|
## Phase 2: harden and size the host
|
|
|
|
1. Set a unique hostname and record the provider instance id alongside it.
|
|
2. Configure default-deny inbound firewall rules. Initially allow SSH from the operator allowlist and TCP 80/443; verify from both an allowed and disallowed external source.
|
|
3. Enable unattended security updates with a documented reboot policy and alert path.
|
|
4. Configure disk/inode monitoring, time sync, audit/system logs, log rotation, and OOM visibility before application deployment.
|
|
5. Create dedicated paths with explicit ownership:
|
|
|
|
```text
|
|
/srv/zarinpal-analytics/ deployment control plane and release records
|
|
/etc/zarinpal-analytics/config/ protected non-secret environment configuration
|
|
/etc/zarinpal-analytics/secrets/ runtime secret files, mode 0600 or stricter
|
|
/var/lib/zarinpal-analytics/ host-managed operational state if required
|
|
/var/backups/zarinpal-analytics/ encrypted local backup retention only
|
|
```
|
|
|
|
6. Verify every resolved absolute path is on the intended filesystem, not a symlink to a broad/unexpected target, before applying ownership or any future recursive operation.
|
|
7. Reserve enough free space for PostgreSQL growth, one temporary logical dump, three encrypted local backups, container images, and logs while retaining the platform alert floor of at least 20% and 10 GiB free.
|
|
|
|
## Phase 3: install and constrain Docker
|
|
|
|
1. Install Docker Engine and the Compose plugin only from Docker's approved repository for the selected OS; pin/record installed versions.
|
|
2. Add only the dedicated trusted deploy user to the `docker` group. Record that this access is effectively root-equivalent and restrict the account accordingly.
|
|
3. Configure the daemon's bounded logging policy before starting application containers.
|
|
4. Ensure Docker starts after reboot and that no unauthenticated TCP daemon socket is enabled.
|
|
5. Pull a harmless pinned public test image, verify its tag/digest, run it without published ports, then remove the test container.
|
|
6. Confirm the deploy user can run Docker Compose without using a broader shared administrator account.
|
|
|
|
Evidence: Engine/Compose versions, daemon security configuration, socket/group ownership, deploy-user membership, log driver/options, and test-image result.
|
|
|
|
## Phase 4: install the deployment control plane
|
|
|
|
1. Create one server Git identity/key with read-only access to all three repositories and no write/push permission.
|
|
2. Clone the deployment repository into the approved deployment root, then clone backend and frontend into its exact ignored nested paths.
|
|
3. Validate ownership, remote URL, `main` branch, current SHA, clean status, and independent `.git` directory for every repository.
|
|
4. Materialize protected non-secret configuration and secret files outside the checkout. Set owner/mode before writing secret values; verify consumers receive only required files.
|
|
5. Fetch each repository and confirm it can fast-forward cleanly to `origin/main`; do not reset, build, or start yet.
|
|
6. Record the three initial commit hashes and selected infrastructure image tags in a non-secret draft release record.
|
|
7. Render/validate Compose configuration without printing secret values. Confirm only Nginx publishes 80/443.
|
|
|
|
Do not run `docker compose up`, migrations, import, credential generation, restore, or DNS cutover during this phase.
|
|
|
|
## Phase 5: DNS and TLS staging
|
|
|
|
1. Lower DNS TTL in advance if an existing record is moving. Preserve the previous values and rollback time.
|
|
2. Point `A`/`AAAA` only after direct-IP Nginx/host validation succeeds. Remove an `AAAA` record if IPv6 is not actually routed and firewalled correctly.
|
|
3. Verify public authoritative DNS from multiple resolvers and confirm the address matches the current server.
|
|
4. Start only the minimum edge services required for ACME HTTP-01. Verify the challenge path from an external network.
|
|
5. Obtain a staging certificate first and exercise renewal. Then request the production certificate.
|
|
6. Validate certificate names, chain, expiry, TLS 1.2/1.3, canonical redirects, unknown-host rejection, and renewal permissions.
|
|
7. Enable HSTS only after successful production issuance and renewal evidence; preload remains a separate approval.
|
|
|
|
If issuance fails, stop repeated production requests, inspect DNS/CAA/firewall/challenge routing, and continue serving no plaintext application content.
|
|
|
|
## Phase 6: first database and application release
|
|
|
|
1. Acquire the production deployment lock and record the operator/release id.
|
|
2. Start PostgreSQL and Redis only. Verify named-volume targets, authentication, resource limits, and health.
|
|
3. Run the one-off migration from the recorded backend checkout/build. Confirm exit zero and the exact expected migration version.
|
|
4. Start backend and worker. Verify liveness/readiness, worker heartbeat, Asynq `v0.26.0` compatibility, queue limits, and absence of unintended published ports.
|
|
5. Start frontend and verify the static sentinel plus the emitted Iran Yekan font resource. The source font/license remain frontend-owned; no deployment-side injection or copy occurs.
|
|
6. Start/reload Nginx and run public generic health, canonical redirect, security-header, login-page, and authenticated smoke checks.
|
|
7. Verify MVP operator checks: Compose health/restarts, structured logs, disk, certificate, queue/Redis state, and backup destination access.
|
|
8. Record the active three-hash release only after all postflight checks pass.
|
|
|
|
Failure before the active-release switch restores the prior/empty known state. Do not improvise a database down migration.
|
|
|
|
## Phase 7: one-time data and demo setup
|
|
|
|
This phase is deliberately separate from server/application bootstrap.
|
|
|
|
1. Copy or mount the approved challenge archive to an absolute operator-owned path outside Git. Validate path, owner, permissions, size, and SHA-256.
|
|
2. Confirm no migration, restore, backup, or import is active. Acquire the import lock.
|
|
3. Invoke the explicit one-off import service with a read-only mount of exactly that file.
|
|
4. Review import status, checksum, source/accepted/rejected counts, quality report, and reconciliation before declaring the dataset ready.
|
|
5. Run demo credential generation as a separate explicit administrative job. Select a secure one-time output destination outside the repository and Compose logs.
|
|
6. Test at least two merchant accounts and cross-tenant isolation. Remove or escrow plaintext credential output according to the approved operator procedure.
|
|
|
|
Never add the source archive or credentials to Git, a container image, backup package, or diagnostic bundle.
|
|
|
|
## Phase 8: backup and handoff gate
|
|
|
|
1. Run the first encrypted logical PostgreSQL backup.
|
|
2. Verify archive listing/checksum, encrypted local artifact, remote upload, remote checksum, manifest, and retention counts.
|
|
3. Restore that backup to a clean non-production destination and complete application/tenant/analytics validation within the two-hour RTO.
|
|
4. Perform and record the MVP manual certificate, disk, backup-age, availability, API error, SSE heartbeat, and queue-age checks.
|
|
5. Record current release, DNS, certificate, backup id, achieved RPO/RTO, remaining exceptions, and on-call ownership.
|
|
6. Remove temporary bootstrap credentials/files and close provider rules that are no longer needed.
|
|
|
|
The host is not considered MVP-ready until restore rehearsal and every manual operator check passes. Prometheus/Grafana and automated alert delivery are the required next phase before AI work.
|
|
|
|
## Stop/rollback conditions
|
|
|
|
Stop without progressing when:
|
|
|
|
- recovery console or second-operator SSH access is not proven
|
|
- resolved paths, host identity, repository remotes/branches, environment, or release record do not match
|
|
- a repository is dirty/diverged unexpectedly or a build/source dependency cannot be verified
|
|
- unexpected ports are externally reachable
|
|
- DNS points somewhere unexpected or certificate names/chain are invalid
|
|
- migration, readiness, authenticated smoke, tenant isolation, worker/SSE, manual operational checks, backup, or restore rehearsal fails
|
|
- secrets appear in output, files have broad permissions, or the deployment checkout is dirty unexpectedly
|
|
|
|
Rollback DNS to the recorded prior values when the new host cannot pass the cutover window. Preserve logs and manifests, revoke any exposed credential, and do not delete the failed host until evidence and required backups are secured.
|
|
|
|
## Completion evidence
|
|
|
|
- [ ] Provider/host identity and capacity recorded
|
|
- [ ] Console plus two key-only operator paths proven
|
|
- [ ] Firewall external tests show only approved ports
|
|
- [ ] Docker/Compose versions and privilege model recorded
|
|
- [ ] Three repository remotes/branches/SHAs and release record verified
|
|
- [ ] Secrets outside Git with restrictive ownership/mode
|
|
- [ ] TLS issuance and renewal tested
|
|
- [ ] Migration, API, worker, frontend/font, Nginx, SSE, and tenant smoke pass
|
|
- [ ] One-time import reconciles and demo credentials stay outside logs/Git
|
|
- [ ] MVP manual health/log/disk/certificate/backup/queue checks pass
|
|
- [ ] Encrypted off-host backup and clean restore rehearsal pass within RTO
|
|
- [ ] Active/previous release ids and operator handoff recorded
|