diff --git a/SKILL.md b/SKILL.md index c3064cc..a800513 100644 --- a/SKILL.md +++ b/SKILL.md @@ -122,6 +122,129 @@ DB_DATABASE_NAME= - `environments//.env` overrides paths for specific hosts - Common overrides: mount paths (`/mnt/user/` vs `/mnt/main/`), ports +## Secret Management with SOPS + +Projects use SOPS with age encryption to securely commit secrets to git. This replaces the manual "secret scrubbing" approach. + +### Directory Structure (with SOPS) + +``` +/docker/config// +├── docker-compose.yml +├── docker-compose.override.yml # (gitignored) +├── .env # (gitignored, generated by decrypt) +├── .gitignore +├── .sops.yaml # SOPS encryption config +├── Makefile # encrypt/decrypt targets +├── env.sops.yaml # Encrypted env vars (committed) +└── environments/ + └── / + ├── env.sops.yaml # Encrypted, host-specific (committed) + └── .env # (gitignored, generated) +``` + +### Key Repository + +Public keys are managed centrally at `~/repos/sops-age-keys/`: +- `recipients/*.pub` - Public keys for each host +- `scripts/init-host.sh` - Generate key for new host +- `scripts/add-recipient.sh` - Add host's public key +- `templates/Makefile` - Makefile template for projects + +Private keys stay at `~/.config/sops/age/keys.txt` (never committed). + +### .sops.yaml Configuration + +```yaml +creation_rules: + - path_regex: env\.sops\.yaml$ + unencrypted_regex: "_unencrypted$" + age: >- + age1abc...,age1def... +``` + +Get recipient keys from `~/repos/sops-age-keys/recipients/*.pub`. + +### env.sops.yaml Format + +Non-secret values use `_unencrypted` suffix (remain readable in git): + +```yaml +# Non-secrets (readable in git) +UID_unencrypted: "1000" +GID_unencrypted: "1000" +UPLOAD_LOCATION_unencrypted: "/mnt/user/pictures" +DB_DATA_LOCATION_unencrypted: "/mnt/user/appdata/immich" +APP_PORT_unencrypted: "2283" +DB_HOSTNAME_unencrypted: "database" +DB_USERNAME_unencrypted: "postgres" +DB_DATABASE_NAME_unencrypted: "immich" + +# Secrets (encrypted by SOPS) +DB_PASSWORD: "actual_secret_value" +``` + +After encryption, only `DB_PASSWORD` value is encrypted; `_unencrypted` values remain readable. + +### Makefile Targets + +```makefile +SOURCE ?= env.sops.yaml +TARGET ?= .env + +decrypt: + @sops decrypt --output-type dotenv $(SOURCE) | sed 's/_unencrypted=/=/g' > $(TARGET) + @echo "Decrypted $(SOURCE) -> $(TARGET)" + +encrypt: + @sops encrypt --input-type dotenv --output $(SOURCE) $(TARGET) + @echo "Encrypted $(TARGET) -> $(SOURCE)" + +clean: + @rm -f $(TARGET) + @echo "Removed $(TARGET)" +``` + +### Common SOPS Commands + +```bash +# Decrypt to .env (for Docker Compose) +make decrypt SOURCE=environments//env.sops.yaml TARGET=environments//.env + +# Edit secrets directly (decrypts in editor, re-encrypts on save) +sops environments//env.sops.yaml + +# Encrypt after editing .env +make encrypt SOURCE=environments//env.sops.yaml TARGET=environments//.env + +# Add new host to recipients (after adding to .sops.yaml) +sops updatekeys environments//env.sops.yaml + +# Clean up decrypted file +make clean TARGET=environments//.env +``` + +### Starting Services with SOPS + +```bash +cd /docker/config/ + +# Decrypt secrets +make decrypt SOURCE=environments//env.sops.yaml TARGET=environments//.env + +# Start services +docker compose --env-file environments//.env \ + -f docker-compose.yml \ + -f environments//docker-compose.override..yml up -d + +# Clean up (optional, secrets stay decrypted for restarts) +make clean TARGET=environments//.env +``` + +### Migrating Existing Projects + +See `~/repos/sops-age-keys/README.md` for full migration guide, or `~/repos/sops-age-keys/docs/design.md` for architecture details. + ## Common Commands ```bash @@ -165,34 +288,17 @@ Each project should be version controlled with its own git repository. ### .gitignore Configuration ```gitignore -# Root environment file (may contain active secrets) +# Root environment file (generated, may contain secrets) /.env # Docker compose override (host-specific, not committed) /docker-compose.override.yml + +# Per-host decrypted .env files (generated from env.sops.yaml) +/environments/*/.env ``` -**Note**: Use `/.env` (with leading slash) to only exclude the root `.env` file. Environment files in `environments//` are committed after secret scrubbing. - -### Secret Scrubbing - -Before committing `environments//.env` files, replace secret values: - -| Secret Type | Original | Scrubbed | -|-------------|----------|----------| -| Passwords | `DB_PASSWORD=actual_password` | `DB_PASSWORD=CHANGE_ME_SECRET` | -| API Keys | `API_KEY=sk-abc123...` | `API_KEY=CHANGE_ME_SECRET` | -| Tokens | `AUTH_TOKEN=token_value` | `AUTH_TOKEN=CHANGE_ME_SECRET` | - -**Keep in version control** (non-secret, host-specific): -- Paths: `DATA_LOCATION`, `UPLOAD_PATH` -- Ports: `APP_PORT`, `DB_PORT` -- UIDs/GIDs: `UID`, `GID` -- URLs: `APP_URL`, `DB_HOSTNAME` -- Names: `DB_DATABASE_NAME`, `DB_USERNAME` - -**Exclude or scrub**: -- Passwords, API keys, tokens, secrets +**Note**: All `.env` files are gitignored. Secrets are stored encrypted in `env.sops.yaml` files using SOPS (see "Secret Management with SOPS" section above). ### Creating a Git Repository