Compare commits
No commits in common. "37cece9dbd4ece0195890fe43ea84586e736b941" and "bf9ce95e1e85d0d41b550d435a89b6f48b91c745" have entirely different histories.
37cece9dbd
...
bf9ce95e1e
11 changed files with 51 additions and 117 deletions
|
|
@ -12,7 +12,7 @@ platforms:
|
||||||
# Project-owned image built from .docker/molecule-debian13/Dockerfile
|
# Project-owned image built from .docker/molecule-debian13/Dockerfile
|
||||||
# and hosted in the Forgejo container registry.
|
# and hosted in the Forgejo container registry.
|
||||||
# Build/push with: make molecule-image / make molecule-image-push
|
# Build/push with: make molecule-image / make molecule-image-push
|
||||||
image: forgejo.nyumbani.baobab.band/sjat/molecule-debian13:latest
|
image: forgejo.nyumbani.baobab.band/<owner>/<repo>/molecule-debian13:latest
|
||||||
pre_build_image: true
|
pre_build_image: true
|
||||||
privileged: true # required for systemd
|
privileged: true # required for systemd
|
||||||
cgroupns_mode: host
|
cgroupns_mode: host
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,3 @@ only designed — much of the ADR-described design is not built yet.
|
||||||
- **Check `STATUS.md`** before assuming a role, provider, or pipeline exists.
|
- **Check `STATUS.md`** before assuming a role, provider, or pipeline exists.
|
||||||
- **Git**: `main` must always work; branch for sweeping changes. Commit your work in
|
- **Git**: `main` must always work; branch for sweeping changes. Commit your work in
|
||||||
logical units with imperative ≤72-char subjects and a `Co-Authored-By` trailer.
|
logical units with imperative ≤72-char subjects and a `Co-Authored-By` trailer.
|
||||||
- **Vault access**: before a task needing a Vaultwarden secret (`make
|
|
||||||
deploy/check/encrypt/decrypt`, or any `git commit` — the hook decrypts `vault.yml`),
|
|
||||||
run `rbw unlocked`; if locked, ask the user to `rbw unlock` first, don't fail partway.
|
|
||||||
|
|
|
||||||
|
|
@ -66,10 +66,6 @@ Full design rationale: `docs/decisions/`
|
||||||
`vault.grafana.admin_password`); reference as `{{ vault.grafana.admin_password }}`
|
`vault.grafana.admin_password`); reference as `{{ vault.grafana.admin_password }}`
|
||||||
- Vault password comes from Vaultwarden via `rbw` (`scripts/vault-pass-client.sh`,
|
- Vault password comes from Vaultwarden via `rbw` (`scripts/vault-pass-client.sh`,
|
||||||
wired as `vault_password_file`). Unlock once per session: `rbw unlock`
|
wired as `vault_password_file`). Unlock once per session: `rbw unlock`
|
||||||
- **Before any vault-dependent task** (`make deploy/check/encrypt/decrypt`, or **any
|
|
||||||
git commit** — the pre-commit ansible-lint hook decrypts `vault.yml`), run `rbw
|
|
||||||
unlocked`; if it exits non-zero, ask the user to `rbw unlock` and wait rather than
|
|
||||||
starting and failing partway. The agent stays unlocked 5h.
|
|
||||||
- To edit a vault file: `make decrypt FILE=<path>`, edit, `make encrypt FILE=<path>`
|
- To edit a vault file: `make decrypt FILE=<path>`, edit, `make encrypt FILE=<path>`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -169,7 +165,6 @@ Single-contributor, trunk-based (no merge requests / approval gates):
|
||||||
| Network topology | `docs/decisions/007-network.md` |
|
| Network topology | `docs/decisions/007-network.md` |
|
||||||
| Testing methodology | `docs/decisions/008-testing.md` |
|
| Testing methodology | `docs/decisions/008-testing.md` |
|
||||||
| TF ↔ Ansible handoff | `docs/decisions/009-provisioning-handoff.md` |
|
| TF ↔ Ansible handoff | `docs/decisions/009-provisioning-handoff.md` |
|
||||||
| Forgejo & CI | `docs/decisions/010-forgejo-ci.md` |
|
|
||||||
| Adding a new role | `docs/runbooks/new-role.md` |
|
| Adding a new role | `docs/runbooks/new-role.md` |
|
||||||
| Adding a new host | `docs/runbooks/new-host.md` |
|
| Adding a new host | `docs/runbooks/new-host.md` |
|
||||||
| Rotating vault secrets | `docs/runbooks/rotate-secrets.md` |
|
| Rotating vault secrets | `docs/runbooks/rotate-secrets.md` |
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -15,7 +15,7 @@ INVENTORY := -i inventories/production/hosts.yml
|
||||||
|
|
||||||
TF := terraform
|
TF := terraform
|
||||||
TF_ENV ?= staging
|
TF_ENV ?= staging
|
||||||
MOLECULE_IMAGE := forgejo.nyumbani.baobab.band/sjat/molecule-debian13:latest
|
MOLECULE_IMAGE := forgejo.nyumbani.baobab.band/<owner>/<repo>/molecule-debian13:latest
|
||||||
MOLECULE_DOCKERFILE := .docker/molecule-debian13/Dockerfile
|
MOLECULE_DOCKERFILE := .docker/molecule-debian13/Dockerfile
|
||||||
|
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
|
|
|
||||||
|
|
@ -44,18 +44,16 @@ Terraform manages its own provider dependencies via `required_providers` and
|
||||||
|
|
||||||
## State backend
|
## State backend
|
||||||
|
|
||||||
**Choice**: Local state on the control node.
|
**Choice**: Forgejo HTTP backend (self-hosted at forgejo.nyumbani.baobab.band)
|
||||||
|
|
||||||
Forgejo (Gitea-based) has no usable Terraform HTTP state backend — its API `/raw/`
|
Keeps all state on the same self-hosted stack without additional services.
|
||||||
endpoint is read-only, so state cannot be written there. State therefore lives
|
Authentication uses a Forgejo personal access token via `TF_HTTP_USERNAME` and
|
||||||
locally as `terraform.tfstate` (gitignored) on the control node, which is persistent
|
`TF_HTTP_PASSWORD` environment variables.
|
||||||
and backed up with the rest of the node.
|
|
||||||
|
|
||||||
At this scale (solo operator, a handful of VMs) local state is sufficient: no
|
**Note**: The backend URL in `backend.tf` is a placeholder — confirm the exact
|
||||||
concurrent applies, so no remote locking is needed. If a remote backend with locking
|
endpoint path against your running Forgejo instance's API documentation before
|
||||||
becomes worthwhile later, add a `backend` block to `backend.tf` pointing at a real
|
running `terraform init`. If Forgejo's HTTP state is unavailable, remove the
|
||||||
backend such as MinIO/S3 — Forgejo is not an option. See ADR-010 for the Forgejo
|
`backend` block from `backend.tf` to fall back to local state on the control node.
|
||||||
integration boundary.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ configuration issues invisible to Ansible check mode.
|
||||||
**Source**: `.docker/molecule-debian13/Dockerfile`
|
**Source**: `.docker/molecule-debian13/Dockerfile`
|
||||||
**Base**: `debian:trixie-slim` (official Debian 13, Docker Hub — only external
|
**Base**: `debian:trixie-slim` (official Debian 13, Docker Hub — only external
|
||||||
dependency permitted here, as the base OS image is not substitutable)
|
dependency permitted here, as the base OS image is not substitutable)
|
||||||
**Registry**: `forgejo.nyumbani.baobab.band/sjat/molecule-debian13:latest`
|
**Registry**: `forgejo.nyumbani.baobab.band/<owner>/<repo-name>/molecule-debian13:latest`
|
||||||
|
|
||||||
Build and push with:
|
Build and push with:
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
# ADR-010 — Forgejo integration and CI
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
boma's git host, container registry, and (planned) CI all run on a self-hosted
|
|
||||||
Forgejo instance at `forgejo.nyumbani.baobab.band` (SSH on 7577, HTTPS on 443). Both
|
|
||||||
humans and AI/automation interact with it. This ADR sets the principles so the
|
|
||||||
instance does not become undocumented click-ops drift, and so its credentials are
|
|
||||||
held to the same standard as the rest of the repo's secrets.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What Forgejo provides here
|
|
||||||
|
|
||||||
- **Git hosting** — `origin` (push/pull over SSH; key-based, not token).
|
|
||||||
- **Container registry** — hosts the Molecule test image
|
|
||||||
(`forgejo.nyumbani.baobab.band/sjat/molecule-debian13`); see ADR-008.
|
|
||||||
- **Actions CI** — planned, not yet enabled (`has_actions: false`); trunk-based
|
|
||||||
pipeline per ADR-003 / ADR-008.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Decisions
|
|
||||||
|
|
||||||
### 1. API tokens are managed secrets, least-privilege
|
|
||||||
|
|
||||||
A Forgejo API token (PAT) is a secret and follows ADR-002: stored in **Vaultwarden**,
|
|
||||||
fetched via `rbw`/env, **never** written to a file or pasted into chat. Tokens are
|
|
||||||
**least-privilege** — scoped to their purpose, never admin.
|
|
||||||
|
|
||||||
Note what does *not* need a token: git push/pull (SSH key), and Terraform state
|
|
||||||
(local — ADR-006). A token for CI / registry use needs only:
|
|
||||||
|
|
||||||
- `read:repository`
|
|
||||||
- `read:package`, `write:package` (pull/push the Molecule image)
|
|
||||||
|
|
||||||
### 2. Declarative-first, not click-ops
|
|
||||||
|
|
||||||
Forgejo configuration lives in the repo where possible:
|
|
||||||
|
|
||||||
- Actions workflows in `.forgejo/workflows/` (version-controlled).
|
|
||||||
- Repo/instance settings are codified or documented; changing them ad-hoc via the
|
|
||||||
API or UI is a **documented exception**, recorded when done — not the norm.
|
|
||||||
|
|
||||||
The point: the Forgejo instance must not become the kind of undocumented drift that
|
|
||||||
`/review-repo` exists to catch.
|
|
||||||
|
|
||||||
### 3. Automation boundary
|
|
||||||
|
|
||||||
Automation / AI **may**, via the API or CLI: read repo and CI state, manage the
|
|
||||||
container registry, run and inspect CI. It must **not**, without explicit human
|
|
||||||
direction: change instance/admin settings, delete repos or packages, or rotate
|
|
||||||
credentials.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CI pipeline (planned)
|
|
||||||
|
|
||||||
Trunk-based, matching ADR-003 / ADR-008:
|
|
||||||
|
|
||||||
```
|
|
||||||
push to main → lint + Molecule → deploy staging → [manual gate] → deploy production
|
|
||||||
```
|
|
||||||
|
|
||||||
Runner: `act_runner` on the control node or a dedicated runner VM. Actions is not
|
|
||||||
yet enabled — see STATUS.md.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What was ruled out
|
|
||||||
|
|
||||||
| Option | Reason |
|
|
||||||
|---|---|
|
|
||||||
| Terraform Forgejo HTTP state backend | Forgejo's `/raw/` API is read-only; state can't be written there. Local state instead (ADR-006). |
|
|
||||||
| Admin-scoped automation tokens | Unnecessary privilege; scope to `read:repository` + `read`/`write:package`. |
|
|
||||||
| Ad-hoc UI/API configuration as the norm | Becomes undocumented drift; codify or document instead. |
|
|
||||||
|
|
@ -1,9 +1,17 @@
|
||||||
# Terraform state: LOCAL, on the control node.
|
terraform {
|
||||||
|
backend "http" {
|
||||||
|
# Forgejo HTTP state backend.
|
||||||
|
# Replace <owner> and <repo> with your Forgejo organisation and repository name.
|
||||||
|
# Verify the exact path format against your running Forgejo instance's API docs.
|
||||||
|
# Authentication: set TF_HTTP_USERNAME (Forgejo username) and
|
||||||
|
# TF_HTTP_PASSWORD (Forgejo personal access token) as environment variables.
|
||||||
#
|
#
|
||||||
# Forgejo (Gitea-based) has no usable Terraform HTTP state backend — its API
|
# If Forgejo's HTTP state endpoint is unavailable, remove this block entirely
|
||||||
# `/raw/` endpoint is read-only, so state cannot be written there. State therefore
|
# to fall back to local state on the control node.
|
||||||
# lives locally as `terraform.tfstate` (gitignored); back it up with the control
|
address = "https://forgejo.nyumbani.baobab.band/api/v1/repos/<owner>/<repo>/raw/terraform/state/production.tfstate"
|
||||||
# node. See ADR-006.
|
lock_address = "https://forgejo.nyumbani.baobab.band/api/v1/repos/<owner>/<repo>/raw/terraform/state/production.tfstate/lock"
|
||||||
#
|
unlock_address = "https://forgejo.nyumbani.baobab.band/api/v1/repos/<owner>/<repo>/raw/terraform/state/production.tfstate/lock"
|
||||||
# To adopt a real remote backend later (e.g. MinIO/S3), add a
|
lock_method = "POST"
|
||||||
# `terraform { backend "..." {} }` block here.
|
unlock_method = "DELETE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
# Secrets must be exported as environment variables before running Terraform:
|
# Secrets must be exported as environment variables before running Terraform:
|
||||||
# export TF_VAR_proxmox_api_token="terraform@pve!tokenid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
# export TF_VAR_proxmox_api_token="terraform@pve!tokenid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
#
|
#
|
||||||
# State is local (see backend.tf) — no Forgejo backend credentials needed.
|
# Forgejo backend credentials:
|
||||||
|
# export TF_HTTP_USERNAME="your-forgejo-username"
|
||||||
|
# export TF_HTTP_PASSWORD="your-forgejo-personal-access-token"
|
||||||
|
|
||||||
proxmox_endpoint = "https://pve01.baobab.band:8006/"
|
proxmox_endpoint = "https://pve01.baobab.band:8006/"
|
||||||
proxmox_insecure = false
|
proxmox_insecure = false
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,17 @@
|
||||||
# Terraform state: LOCAL, on the control node.
|
terraform {
|
||||||
|
backend "http" {
|
||||||
|
# Forgejo HTTP state backend.
|
||||||
|
# Replace <owner> and <repo> with your Forgejo organisation and repository name.
|
||||||
|
# Verify the exact path format against your running Forgejo instance's API docs.
|
||||||
|
# Authentication: set TF_HTTP_USERNAME (Forgejo username) and
|
||||||
|
# TF_HTTP_PASSWORD (Forgejo personal access token) as environment variables.
|
||||||
#
|
#
|
||||||
# Forgejo (Gitea-based) has no usable Terraform HTTP state backend — its API
|
# If Forgejo's HTTP state endpoint is unavailable, remove this block entirely
|
||||||
# `/raw/` endpoint is read-only, so state cannot be written there. State therefore
|
# to fall back to local state on the control node.
|
||||||
# lives locally as `terraform.tfstate` (gitignored); back it up with the control
|
address = "https://forgejo.nyumbani.baobab.band/api/v1/repos/<owner>/<repo>/raw/terraform/state/staging.tfstate"
|
||||||
# node. See ADR-006.
|
lock_address = "https://forgejo.nyumbani.baobab.band/api/v1/repos/<owner>/<repo>/raw/terraform/state/staging.tfstate/lock"
|
||||||
#
|
unlock_address = "https://forgejo.nyumbani.baobab.band/api/v1/repos/<owner>/<repo>/raw/terraform/state/staging.tfstate/lock"
|
||||||
# To adopt a real remote backend later (e.g. MinIO/S3), add a
|
lock_method = "POST"
|
||||||
# `terraform { backend "..." {} }` block here.
|
unlock_method = "DELETE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
# Secrets must be exported as environment variables before running Terraform:
|
# Secrets must be exported as environment variables before running Terraform:
|
||||||
# export TF_VAR_proxmox_api_token="terraform@pve!tokenid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
# export TF_VAR_proxmox_api_token="terraform@pve!tokenid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
#
|
#
|
||||||
# State is local (see backend.tf) — no Forgejo backend credentials needed.
|
# Forgejo backend credentials:
|
||||||
|
# export TF_HTTP_USERNAME="your-forgejo-username"
|
||||||
|
# export TF_HTTP_PASSWORD="your-forgejo-personal-access-token"
|
||||||
|
|
||||||
proxmox_endpoint = "https://pve01.baobab.band:8006/"
|
proxmox_endpoint = "https://pve01.baobab.band:8006/"
|
||||||
proxmox_insecure = true # set false once a valid TLS cert is in place
|
proxmox_insecure = true # set false once a valid TLS cert is in place
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue