Compare commits
3 Commits
9566826ce9
...
fbab5c4669
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbab5c4669 | ||
|
|
1b552c0882 | ||
|
|
8ebc18c3bc |
@@ -146,3 +146,11 @@ def test_warn_kinds_do_not_block(monkeypatch):
|
|||||||
assert sg.status_for([f], True)[0] == "success"
|
assert sg.status_for([f], True)[0] == "success"
|
||||||
monkeypatch.setattr(sg, "WARN_KINDS", set())
|
monkeypatch.setattr(sg, "WARN_KINDS", set())
|
||||||
assert sg.status_for([f], True)[0] == "failure"
|
assert sg.status_for([f], True)[0] == "failure"
|
||||||
|
|
||||||
|
|
||||||
|
def test_pypi_token_shape_hash_only():
|
||||||
|
tok = "pypi-AgE" + "Ab1_-" * 20 # synthetic
|
||||||
|
got = ss.find(f"password = {tok}")
|
||||||
|
assert [k for k, _ in got] == ["pypi token"] and got[0][1] == ss.h8(tok)
|
||||||
|
assert tok not in repr(got)
|
||||||
|
assert ss.find("pypi-AgE-too-short") == []
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ services:
|
|||||||
|
|
||||||
gitea:
|
gitea:
|
||||||
# G2.1 — PIN AN EXACT VERSION. Never `latest`. Record it in SUBSTRATE.md.
|
# G2.1 — PIN AN EXACT VERSION. Never `latest`. Record it in SUBSTRATE.md.
|
||||||
image: docker.io/gitea/gitea:1.24.6
|
image: docker.io/gitea/gitea:1.24.7
|
||||||
environment:
|
environment:
|
||||||
GITEA__database__DB_TYPE: postgres
|
GITEA__database__DB_TYPE: postgres
|
||||||
GITEA__database__HOST: db:5432
|
GITEA__database__HOST: db:5432
|
||||||
|
|||||||
33
docs/RESTORE-DRILL.md
Normal file
33
docs/RESTORE-DRILL.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Restoring Windy Git from the encrypted state backup
|
||||||
|
|
||||||
|
What is backed up (`scripts/backup_state.sh`, restic repo `s3:…/windy-git-backups/restic`, tag `windygit-state`):
|
||||||
|
both Postgres databases (`gitea`, `windygit`, custom-format dumps + globals), the whole Gitea data root
|
||||||
|
(`/srv/windygit/git`: config, jwt, attachments, avatars, templates AND the bare repositories),
|
||||||
|
`/srv/windygit/src/.env`, `deploy/runner/.env`, `/etc/cloudflared`, the windygit systemd drop-ins.
|
||||||
|
NOT in it: the restic password itself (lockbox `RESTIC_WINDYGIT_PASSWORD`) and the R2 access key
|
||||||
|
(scoped token `windy-git-r2-scoped`, lockbox). Never print either: use `lockbox-get KEY FILE`.
|
||||||
|
|
||||||
|
## Drill (any machine with restic + docker; proven on Veron 2026-10-01, counts identical)
|
||||||
|
export RESTIC_PASSWORD_FILE=<0600 file from lockbox-get RESTIC_WINDYGIT_PASSWORD>
|
||||||
|
export AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… # from lockbox-get, into env, not echoed
|
||||||
|
export RESTIC_REPOSITORY=s3:https://<R2 account>.r2.cloudflarestorage.com/windy-git-backups/restic
|
||||||
|
restic snapshots --tag windygit-state
|
||||||
|
restic restore latest --tag windygit-state --target /var/tmp/wg-drill
|
||||||
|
docker run -d --name wg-drill-pg -e POSTGRES_PASSWORD=<random> -e POSTGRES_USER=drill postgres:16-alpine
|
||||||
|
for db in gitea windygit; do
|
||||||
|
docker exec wg-drill-pg psql -U drill -d postgres -c "create database $db"
|
||||||
|
docker exec -i wg-drill-pg pg_restore -U drill -d $db --no-owner --no-privileges \
|
||||||
|
< /var/tmp/wg-drill/var/backups/windygit-state/$db.dump
|
||||||
|
done
|
||||||
|
# compare row counts with live (or with the last known): repository, issue, pull_request, "user",
|
||||||
|
# external_login_user, access_token, action_run, action_run_job
|
||||||
|
docker rm -f wg-drill-pg; rm -rf /var/tmp/wg-drill
|
||||||
|
|
||||||
|
## Real disaster (Veron lost)
|
||||||
|
1. New Linux host with Docker, a Cloudflare tunnel connector, the repo (`git clone` from GitHub: windy-git).
|
||||||
|
2. `restic restore latest --tag windygit-state --target /` (puts /srv/windygit/git, the .env files, /etc/cloudflared back).
|
||||||
|
3. `docker compose -p windy-git up -d db`, then pg_restore both dumps into it (as above, into the real db names/owner from `.env`).
|
||||||
|
4. `docker compose -p windy-git up -d` + `deploy/runner` runners; re-register runners if the token changed.
|
||||||
|
5. Verify: `/api/healthz`, Windy SSO login, `git ls-remote`, one CI run. GitHub is still the source of truth for code,
|
||||||
|
so repo content can also be re-synced from there; the database is what only this backup holds.
|
||||||
|
Retention: 14 daily / 8 weekly / 6 monthly (prune on Sundays). Integrity: every run does `restic check --read-data-subset=2%`.
|
||||||
@@ -23,13 +23,14 @@ PATTERNS: list[tuple[str, re.Pattern[str]]] = [
|
|||||||
("twilio sid/api key", re.compile(r"\b(?:AC|SK)[0-9a-f]{32}\b")),
|
("twilio sid/api key", re.compile(r"\b(?:AC|SK)[0-9a-f]{32}\b")),
|
||||||
("32-hex secret assignment", re.compile(
|
("32-hex secret assignment", re.compile(
|
||||||
r"(?i)\b[a-z0-9_.-]*(?:token|secret|key|password)[a-z0-9_.-]*[\"']?\s*[:=]\s*[\"']?(?P<v>(?<![0-9a-f])[0-9a-f]{32}(?![0-9a-f]))")),
|
r"(?i)\b[a-z0-9_.-]*(?:token|secret|key|password)[a-z0-9_.-]*[\"']?\s*[:=]\s*[\"']?(?P<v>(?<![0-9a-f])[0-9a-f]{32}(?![0-9a-f]))")),
|
||||||
|
("pypi token", re.compile(r"\bpypi-AgE[A-Za-z0-9_-]{50,}")),
|
||||||
("private key block", re.compile(r"-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY-----")),
|
("private key block", re.compile(r"-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY-----")),
|
||||||
]
|
]
|
||||||
|
|
||||||
# git grep -E (POSIX ERE) prefilter: cheap superset of PATTERNS.
|
# git grep -E (POSIX ERE) prefilter: cheap superset of PATTERNS.
|
||||||
PREFILTER = ("[0-9]{8,10}:[A-Za-z0-9_-]{35}|gh[pousr]_[A-Za-z0-9]{36}|github_pat_|(AKIA|ASIA)[0-9A-Z]{16}"
|
PREFILTER = ("[0-9]{8,10}:[A-Za-z0-9_-]{35}|gh[pousr]_[A-Za-z0-9]{36}|github_pat_|(AKIA|ASIA)[0-9A-Z]{16}"
|
||||||
"|xox[abprs]-|sk-ant-|sk-[A-Za-z0-9_-]{32}|sk-proj-|[rs]k_live_|AIza[0-9A-Za-z_-]{35}"
|
"|xox[abprs]-|sk-ant-|sk-[A-Za-z0-9_-]{32}|sk-proj-|[rs]k_live_|AIza[0-9A-Za-z_-]{35}"
|
||||||
"|-----BEGIN [A-Z ]*PRIVATE KEY-----|(AC|SK)[0-9a-f]{32}|[0-9a-fA-F]{32}")
|
"|-----BEGIN [A-Z ]*PRIVATE KEY-----|(AC|SK)[0-9a-f]{32}|[0-9a-fA-F]{32}|pypi-AgE")
|
||||||
|
|
||||||
|
|
||||||
def h8(value: str | bytes) -> str:
|
def h8(value: str | bytes) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user