Skip to content

Secrets & connections (team guide)

How passwords, API keys, and service-to-service links work on this platform. Written for Rafael and Alex — simpler than Architecture → Secrets, but complete enough to understand day-to-day work and what not to commit.

Versión en español

Resumen al final de esta página: Resumen en español.


Two kinds of “passwords”

1. Human logins (you type in the browser)

These are normal user accounts. Not stored in git.

Service URL Username Where password is stored
Forgejo (git) git.rafaelgonzalezalbes.com alex or rafael.gonzalez.albes Forgejo database on VPS (hashed)
Plane (issues) pm.rafaelgonzalezalbes.com Your email Plane database on VPS (hashed)
Uptime Kuma (monitoring) status.rafaelgonzalezalbes.com Per-user Kuma SQLite on VPS
Woodpecker (CI) ci.rafaelgonzalezalbes.com No separate password — sign in with Forgejo OAuth
Docs docs.rafaelgonzalezalbes.com No login — static site, team URL only

Registration is disabled on Forgejo and Plane. Only Rafael and Alex have accounts.

2. Machine secrets (automation, no human typing)

Used by CI pipelines, webhooks, and deploy scripts. Never paste these in chat or commit them.

Category Examples Who needs them
CI deploy SFTP password, SSH private key, Plane API key for CI comments Woodpecker (per repo)
Platform stack Woodpecker DB password, OAuth client secret, bridge tokens VPS at deploy time (from SOPS)
Developer automation Forgejo PAT, Plane PAT, Woodpecker token local/.env on your PC only
Decryption Age private key Rafael’s PC + Woodpecker sops_age_key for infra deploy

Where secrets are stored (full map)

flowchart TB
  subgraph never_git [Never in git]
    UserPW[User passwords\nForgejo / Plane / Kuma DB]
    LocalEnv["local/.env\n(PC only)"]
    AgeKey["secrets/.age/key.txt\n(private age key)"]
    WPSecrets[Woodpecker repo secrets\nin Woodpecker DB]
  end

  subgraph encrypted_git [In git — encrypted]
    VPSEnc["secrets/vps-stack.enc.env"]
    BridgeEnc["secrets/plane-bridge.enc.env"]
    MCPEnc["secrets/plane-mcp.enc.env"]
    ProjEnc["secrets/projects/*.enc.env"]
  end

  subgraph vps_runtime [VPS — runtime only]
    Tmpfs["/run/infra-secrets/\n(tmpfs RAM)"]
    DockerEnv[Container environment\nin memory]
  end

  AgeKey -->|decrypt| VPSEnc
  AgeKey -->|decrypt| BridgeEnc
  WPSecrets -->|CI pipeline| CI[Woodpecker agent]
  VPSEnc --> Tmpfs
  BridgeEnc --> Tmpfs
  Tmpfs --> DockerEnv
  LocalEnv --> DevScripts[Scripts on your PC]
Location What goes here Committed? On VPS disk?
infra-devops/local/.env PLANE_API_KEY, FORGEJO_API_TOKEN, KUMA_PASSWORD, server IP No (gitignored) No
infra-devops/local/projects/portfolio.env SFTP host/user/pass for onboard script No No
secrets/*.enc.env Platform OAuth, DB passwords, bridge tokens Yes (encrypted with SOPS + age) Encrypted copy in /opt/devops/secrets/
secrets/.age/key.txt Private age key to decrypt SOPS files Never Never on VPS
Woodpecker → repo → Secrets sftp_*, plane_api_key, deploy_ssh_key, sops_age_key No Stored in Woodpecker Postgres only
Forgejo / Plane / Kuma DB User password hashes N/A Yes (app databases)
/opt/devops/.env Should not exist after SOPS migration
/run/infra-secrets/ Decrypted keys during deploy tmpfs (RAM, cleared on reboot)

Design goal: no long-lived plain password files on the VPS. Platform secrets decrypt at deploy into RAM, then Docker containers get env vars in memory.


Woodpecker secrets (per repository)

Woodpecker injects secrets into pipeline containers as environment variables. Developers usually cannot read secret values in the UI after save (only update them).

infra/devops repo (platform auto-deploy)

Secret name Purpose Who sets it
sops_age_key Decrypt secrets/*.enc.env during deploy Rafael (one line from secrets/.age/key.txt)
deploy_ssh_key SSH to VPS for rsync + deploy-platform.sh Rafael (base64 of ~/.ssh/bioscan_vps)

Host (85.215.32.166) and user (root) are hardcoded in .woodpecker.yml — not secrets.

rafael.gonzalez.albes/portfolio repo (site deploy)

Secret name Purpose
sftp_host IONOS hostname
sftp_user SFTP username
sftp_pass SFTP password
sftp_remote_path Remote folder (e.g. dist)
plane_api_key Post CI status comments to Plane
plane_workspace e.g. rafael-gonzalez-albes
plane_project_id Portfolio Plane project UUID
plane_api_url https://pm.rafaelgonzalezalbes.com/api/v1
plane_issue_prefix PORT
woodpecker_host https://ci.rafaelgonzalezalbes.com (optional)

Set via bash scripts/onboard-project.sh or Woodpecker UI.

Future bioscan/bioscancheck

Will add: ssh_host, ssh_user, ssh_key, docker_registry_*, SFTP secrets — see BioScanCheck.


SOPS encrypted files (platform)

Edited on Rafael’s PC only (needs age private key):

export SOPS_AGE_KEY_FILE=secrets/.age/key.txt
sops edit secrets/plane-bridge.enc.env
File Contents
secrets/vps-stack.enc.env WOODPECKER_DB_PASSWORD, WOODPECKER_AGENT_SECRET, WOODPECKER_FORGEJO_CLIENT, WOODPECKER_FORGEJO_SECRET, TRAEFIK_DASHBOARD_AUTH, DOMAIN, admin users
secrets/plane-bridge.enc.env PFB_FORGE_TOKEN, PFB_FORGE_WEBHOOK_SECRET, PFB_PLANE_API_KEY, PFB_PLANE_WEBHOOK_SECRET
secrets/plane-mcp.enc.env Plane MCP OAuth client ID/secret
secrets/projects/portfolio.enc.env Optional encrypted backup of SFTP creds (reference; Woodpecker is source of truth for CI)

After git push to infra/devops main, Woodpecker deploys and deploy-platform.sh decrypts these into /run/infra-secrets/.


How services connect to each other

Big picture

flowchart TB
  subgraph internet [Internet]
    Users[Rafael / Alex browsers]
    Public[rafaelgonzalezalbes.com visitors]
    IONOS[IONOS SFTP hosting]
  end

  subgraph vps [VPS 85.215.32.166]
    T[Traefik :443]
    F[Forgejo git.*]
    W[Woodpecker ci.*]
    P[Plane pm.*]
    B[plane-forge-bridge bridge.*]
    K[Uptime Kuma status.*]
    D[Docs nginx docs.*]
    M[Plane MCP mcp.*]
  end

  Users --> T
  T --> F & W & P & K & D & M
  Public --> IONOS

  Users -->|OAuth login| W
  W -->|OAuth token| F
  W -->|clone / API| F
  W -->|SFTP deploy secrets| IONOS
  W -->|SSH + SOPS deploy| vps
  W -->|notify-plane-ci.sh| P

  F -->|webhook + HMAC secret| B
  P -->|webhook + HMAC secret| B
  B -->|Forgejo API + PAT| F
  B -->|Plane API + PAT| P

  K -->|HTTP checks| F & W & P & D & IONOS

Connection table

From To Protocol Authentication
Browser Any *.rafaelgonzalezalbes.com subdomain HTTPS TLS (Let’s Encrypt via Traefik)
Browser Forgejo HTTPS + session cookie Username + password
Browser Woodpecker HTTPS → redirect to Forgejo OAuth Forgejo OAuth app (confidential client)
Browser Plane HTTPS + session Email + password (invite only)
Browser Plane MCP (mcp.*) HTTPS Plane OAuth
Woodpecker agent Forgejo HTTPS git clone User OAuth token (stored in Woodpecker DB after login)
Woodpecker agent IONOS SFTP Repo secrets sftp_*
Woodpecker agent VPS SSH deploy_ssh_key secret
Woodpecker agent Plane REST API HTTPS plane_api_key secret in notify script
Forgejo plane-forge-bridge HTTPS webhook POST PFB_FORGE_WEBHOOK_SECRET (HMAC)
Plane plane-forge-bridge HTTPS webhook POST PFB_PLANE_WEBHOOK_SECRET (HMAC)
plane-forge-bridge Forgejo API HTTPS PFB_FORGE_TOKEN (PAT)
plane-forge-bridge Plane API HTTPS PFB_PLANE_API_KEY (PAT)
Uptime Kuma All services HTTP(S) probes None (internal checks)
Cursor (local) Plane MCP HTTP OAuth
Cursor (local) Forgejo / Woodpecker stdio MCP PAT in local/.env

plane-forge-bridge (Forgejo ↔ Plane)

Runs at https://bridge.rafaelgonzalezalbes.com. Not for browsing — webhooks only.

Linked today:

Forgejo repo Plane project Prefix
rafael.gonzalez.albes/portfolio Portfolio PORT

Config: plane/bridge/config.yaml in infra/devops.

What it syncs:

  • Forgejo issue open/close → Plane state (e.g. PR opened → In Review)
  • Plane work item events → Forgejo (when workspace webhook configured)
  • PR merge → Done

One-time manual step (Rafael): Plane → Workspace Settings → Webhooks → URL https://bridge.rafaelgonzalezalbes.com/plane/webhook, secret = PFB_PLANE_WEBHOOK_SECRET from SOPS.

Woodpecker → Plane (CI comments)

On every pipeline, scripts/notify-plane-ci.sh runs if the branch or commit message contains PORT-N (or BIOS-N):

  1. Parses issue key from CI_COMMIT_BRANCH / message
  2. Calls Plane API: add comment with CI status + link to Woodpecker build
  3. Uses Woodpecker repo secrets (plane_api_key, etc.)

No link if branch doesn’t contain PORT-12 style key.


Data flows by project

Portfolio (live today)

sequenceDiagram
  participant Dev as Developer
  participant F as Forgejo
  participant W as Woodpecker
  participant I as IONOS SFTP
  participant Site as rafaelgonzalezalbes.com
  participant P as Plane

  Dev->>F: push feature/PORT-5-...
  F->>W: webhook trigger pipeline
  W->>F: clone repo (OAuth token)
  W->>W: npm ci, lint, build
  W->>P: notify success/failure (plane_api_key)
  Dev->>F: merge PR to main
  F->>W: pipeline on main
  W->>I: SFTP upload dist/ (sftp_* secrets)
  I->>Site: static files served

Important: Git repo is private on Forgejo. Public website is separate on IONOS — locking the repo does not take down the site.

Platform infra/devops

sequenceDiagram
  participant Dev as Rafael
  participant F as Forgejo
  participant W as Woodpecker
  participant VPS as VPS /opt/devops

  Dev->>F: push main
  F->>W: trigger pipeline
  W->>W: mkdocs build → site/
  W->>VPS: rsync repo + site/ (SSH)
  W->>VPS: deploy-platform.sh (SOPS_AGE_KEY)
  VPS->>VPS: decrypt secrets → tmpfs
  VPS->>VPS: docker compose up -d

BioScanCheck (planned)

Frontend → IONOS SFTP. Backend MCP Docker image → Forgejo registry → SSH to VPS. See apps/bioscancheck.md.


What Alex has vs what only Rafael has

Secret / access Alex Rafael
Forgejo login password Own password Own password
Plane login Own invite Admin
Woodpecker (via Forgejo OAuth) Yes Yes
Forgejo org admin (infra, bioscan) Yes Yes
Woodpecker repo secrets (view/edit) Admin — can update Admin
local/.env on PC His own if he sets up automation Yes
secrets/.age/key.txt No (unless explicitly shared) Yes
Woodpecker sops_age_key No Yes
IONOS SFTP password No (unless shared) Yes (in Woodpecker)
VPS root SSH key Optional alex.rodriguez.vera user Yes

Alex can develop, push, review PRs, and trigger CI without knowing SFTP or SOPS keys — Woodpecker already has them.


Rules for both of you

  1. Never commit local/.env, local/projects/*.env, secrets/.age/, or any file with passwords.
  2. Never paste live secrets in chat, tickets, or PRs.
  3. Rotate tokens if they were ever exposed — see scripts/rotate-exposed-secrets.md.
  4. SFTP / SSH / age key — ask Rafael to update Woodpecker secrets, don’t embed in code.
  5. Forgejo PAT for MCP — generate your own under Settings → Applications.
  6. Platform URLs use X-Robots-Tag: noindex — not in Google, but still use HTTPS and strong passwords.

Troubleshooting

Symptom Likely cause Fix
Woodpecker clone fails (exit 128) Repo private but Woodpecker repos.private=false bash scripts/fix-portfolio-repo-private.sh + refresh OAuth
Woodpecker “Sign in with Forgejo” fails OAuth app misconfigured Confidential client required — see scripts/setup-woodpecker-oauth.sh
No Plane comment on CI Branch missing PORT-N Rename branch to feature/PORT-12-...
No Plane comment on CI Missing Woodpecker plane secrets Re-run onboard-project.sh
infra/devops deploy fails Bad deploy_ssh_key paste Use base64 one-liner in Woodpecker
docs.* 404 or 403 Traefik middleware or file permissions See deploy runbook; chmod -R a+rX /opt/devops/site
Bridge not syncing PRs Webhook missing in Forgejo/Plane Check plane/bridge/config.yaml + workspace webhook

Resumen en español

Dos tipos de credenciales

  1. Contraseñas de usuario (Forgejo, Plane, Kuma): las guarda cada aplicación en su base de datos. No están en git.
  2. Secretos de máquina (CI, webhooks, deploy): nunca en el código.

Dónde viven los secretos

Lugar Qué hay
Woodpecker (por repo) SFTP, SSH deploy, API de Plane para comentarios CI
secrets/*.enc.env (git cifrado) OAuth Woodpecker, tokens del bridge Plane↔Forgejo, contraseñas de BD
local/.env (solo PC) Tokens para scripts y MCP de Cursor
secrets/.age/key.txt Clave privada para descifrar SOPS — solo Rafael, nunca en git

Cómo se conectan las apps

  • Traefik enruta todos los subdominios (git, ci, pm, docs, status, mcp, bridge).
  • Woodpecker entra con OAuth de Forgejo, clona repos, despliega portfolio por SFTP a IONOS, y la plataforma por SSH al VPS.
  • plane-forge-bridge sincroniza PRs/issues entre Forgejo y Plane con webhooks y tokens API.
  • Woodpecker avisa a Plane con notify-plane-ci.sh si la rama contiene PORT-N.
  • Cursor habla con Plane vía MCP OAuth; con Forgejo/Woodpecker vía tokens personales en el PC.

Qué necesita Alex de Rafael

  • Invitación a Plane y contraseña de Kuma (si aplica).
  • No necesita la clave age ni SFTP para trabajar en código — el CI ya tiene esos secretos en Woodpecker.