Skip to content

feat(terraform): Forgejo Stage 0 — R2 buckets + Tailscale tag + Tunnel ingress#43

Merged
turtton merged 3 commits into
mainfrom
feat/forgejo-woodpecker-plan
May 30, 2026
Merged

feat(terraform): Forgejo Stage 0 — R2 buckets + Tailscale tag + Tunnel ingress#43
turtton merged 3 commits into
mainfrom
feat/forgejo-woodpecker-plan

Conversation

@turtton

@turtton turtton commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary

Stage 0 of the Forgejo + Woodpecker CI plan (see `.sisyphus/plans/forgejo-actions.md` Rev. 9).

This PR delivers the infrastructure prerequisites that must exist before any Stage 1/2/3 K8s manifests are applied:

  • Cloudflare R2 — two buckets (`forgejo-backup`, `longhorn-backup`) in `apac` location, each with a bucket-scoped Read+Write API token. Outputs (sensitive) feed SOPS-encrypted Kubernetes Secrets via `tofu output -raw `.
  • Tailscale — `tag:forgejo` added with same admin-only access pattern as `tag:nextcloud`. SSH (22) + Web UI (443) reachable from admin only. Woodpecker stays cluster-internal (no `tag:woodpecker`).
  • Cloudflare Tunnel — `forgejo.turtton.net` routes to `forgejo-http.forgejo.svc.cluster.local:3000`. NOT added to `access_protected_hostnames` — Forgejo handles its own auth (local + admin 2FA + self-registration disabled). The public hostname is needed so Woodpecker step Pods can `git clone https://forgejo.turtton.net/...\` and so OAuth callbacks resolve via public DNS.

Plan summary

```
Plan: 5 to add, 2 to change, 0 to destroy.
```

Outputs added (all sensitive except the first two):

  • `r2_account_id`, `r2_endpoint_url`
  • `forgejo_r2_access_key_id` / `forgejo_r2_secret_access_key`
  • `longhorn_r2_access_key_id` / `longhorn_r2_secret_access_key`

Access key = `cloudflare_api_token..id`. Secret = `sha256(token.value)` (Cloudflare R2 S3 API convention).

Next steps (after merge)

  1. `tofu apply`
  2. `tofu output -raw forgejo_r2_access_key_id` 等を SOPS Secret に書き込み (Stage 1 PR で使用)
  3. Cloudflare ダッシュボード上で `forgejo.turtton.net` の DNS が見えることを確認 (Stage 2 までは 502/404 で OK — 経路のみ検証)
  4. Stage 1 PR (Longhorn HelmRelease の R2 BackupTarget 設定) → merge → 24h soak → Stage 2

Verification

  • `tofu fmt -recursive` ✅
  • `tofu plan` ✅ (user-side: 5 add / 2 change / 0 destroy, errors なし、warns は既存)

Replace misleading '上位ルーター' comments with explicit device names
(ISP router/Buffalo/ICX) and add network-architecture.md documenting
the full topology, routing design, and Buffalo SPI root cause/fix.
@github-actions

github-actions Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

✅ Plan succeeded

OpenTofu output
data.talos_machine_configuration.worker["worker-1"]: Read complete after 0s [id=homelab]
data.talos_machine_configuration.worker["toliworker-2"]: Read complete after 0s [id=homelab]
data.talos_machine_configuration.worker["toliworker-3"]: Read complete after 0s [id=homelab]
data.talos_machine_configuration.worker["toliworker-1"]: Read complete after 0s [id=homelab]
data.talos_machine_configuration.controlplane["cp-1"]: Reading...
data.talos_machine_configuration.controlplane["cp-1"]: Read complete after 0s [id=homelab]
data.talos_machine_configuration.worker["worker-3"]: Read complete after 0s [id=homelab]
cloudflare_r2_bucket.this["longhorn"]: Refreshing state... [id=longhorn-backup]
cloudflare_r2_bucket.this["forgejo"]: Refreshing state... [id=forgejo-backup]
cloudflare_zero_trust_tunnel_cloudflared.homelab: Refreshing state... [id=8dcd868c-295b-4cd0-96d7-37d7928e903d]
cloudflare_zero_trust_access_application.protected: Refreshing state... [id=3906e026-6503-43a1-b3b0-29f43fdbdf44]
data.tailscale_device.proxmox["main"]: Reading...
tailscale_acl.this: Refreshing state... [id=acl]
data.tailscale_device.proxmox["toliunit"]: Reading...
data.tailscale_device.proxmox["data"]: Reading...
proxmox_virtual_environment_download_file.talos_image["main"]: Refreshing state... [id=local:iso/talos-v1.12.1-nocloud-amd64.img]
proxmox_virtual_environment_download_file.talos_image["data"]: Refreshing state... [id=local:iso/talos-v1.12.1-nocloud-amd64.img]
proxmox_virtual_environment_download_file.talos_image["toliunit"]: Refreshing state... [id=local:iso/talos-v1.12.1-nocloud-amd64.img]
data.tailscale_device.proxmox["toliunit"]: Read complete after 1s [id=6191717064938954]
data.tailscale_device.proxmox["data"]: Read complete after 1s [id=2054939555295743]
data.tailscale_device.proxmox["main"]: Read complete after 1s [id=4423433915612456]
cloudflare_dns_record.tunnel["kameuo"]: Refreshing state... [id=ac1e185a765b2dcb31c158b9e902b599]
cloudflare_dns_record.tunnel["grafana"]: Refreshing state... [id=8c589265d1fdc5692fe132d3ca49fef5]
cloudflare_dns_record.tunnel["livesync"]: Refreshing state... [id=5e8b2b94f3f60346ced98b7304fc8082]
data.cloudflare_zero_trust_tunnel_cloudflared_token.homelab: Reading...
cloudflare_dns_record.tunnel["longhorn"]: Refreshing state... [id=8343817358acba47c986378d1dd36264]
cloudflare_dns_record.tunnel["forgejo"]: Refreshing state... [id=d1540cd2bb6c88d90d2d1bc444c50512]
tailscale_device_tags.proxmox["data"]: Refreshing state... [id=nES7h6ng3H11CNTRL]
data.cloudflare_zero_trust_tunnel_cloudflared_token.homelab: Read complete after 0s
tailscale_device_tags.proxmox["toliunit"]: Refreshing state... [id=nVEQVfuEMq11CNTRL]
tailscale_device_tags.proxmox["main"]: Refreshing state... [id=nDFXnG7PYb11CNTRL]
cloudflare_account_token.forgejo_r2: Refreshing state... [id=71e1c0f0de486374c7af93a081bbd119]
cloudflare_account_token.longhorn_r2: Refreshing state... [id=dc5eca345691fcb4142489480418598b]
cloudflare_zero_trust_tunnel_cloudflared_config.homelab: Refreshing state... [id=8dcd868c-295b-4cd0-96d7-37d7928e903d]
proxmox_virtual_environment_vm.talos_node["toliworker-1"]: Refreshing state... [id=1013]
proxmox_virtual_environment_vm.talos_node["cp-1"]: Refreshing state... [id=1000]
proxmox_virtual_environment_vm.talos_node["worker-1"]: Refreshing state... [id=1010]
proxmox_virtual_environment_vm.talos_node["worker-2"]: Refreshing state... [id=1011]
proxmox_virtual_environment_vm.talos_node["toliworker-3"]: Refreshing state... [id=1015]
proxmox_virtual_environment_vm.talos_node["worker-3"]: Refreshing state... [id=1012]
proxmox_virtual_environment_vm.talos_node["toliworker-2"]: Refreshing state... [id=1014]
talos_machine_configuration_apply.worker["worker-1"]: Refreshing state... [id=machine_configuration_apply]
talos_machine_configuration_apply.worker["worker-2"]: Refreshing state... [id=machine_configuration_apply]
talos_machine_configuration_apply.worker["toliworker-2"]: Refreshing state... [id=machine_configuration_apply]
talos_machine_configuration_apply.worker["toliworker-1"]: Refreshing state... [id=machine_configuration_apply]
talos_machine_configuration_apply.worker["toliworker-3"]: Refreshing state... [id=machine_configuration_apply]
talos_machine_configuration_apply.worker["worker-3"]: Refreshing state... [id=machine_configuration_apply]
talos_machine_configuration_apply.controlplane["cp-1"]: Refreshing state... [id=machine_configuration_apply]
talos_machine_bootstrap.this: Refreshing state... [id=machine_bootstrap]
data.talos_cluster_health.this: Reading...
data.talos_cluster_health.this: Read complete after 4s [id=cluster_health]
talos_cluster_kubeconfig.this: Refreshing state... [id=homelab]

No changes. Your infrastructure matches the configuration.

OpenTofu has compared your real infrastructure against your configuration and
found no differences, so no changes are needed.

Warning: Deprecated

  with proxmox_virtual_environment_download_file.talos_image,
  on talos-image.tf line 29, in resource "proxmox_virtual_environment_download_file" "talos_image":
  29: resource "proxmox_virtual_environment_download_file" "talos_image" {

Use "proxmox_download_file" instead. This resource / data source will be
removed in v1.0.

(and 3 more similar warnings elsewhere)

Warning: error waiting for network interfaces from QEMU agent

  with proxmox_virtual_environment_vm.talos_node["toliworker-3"],
  on vms.tf line 1, in resource "proxmox_virtual_environment_vm" "talos_node":
   1: resource "proxmox_virtual_environment_vm" "talos_node" {

error waiting for VM network interfaces: error retrieving VM network
interfaces from agent: received an HTTP 403 response - Reason: Permission
check failed (/vms/1015, VM.GuestAgent.Audit|VM.GuestAgent.Unrestricted)

(and 2 more similar warnings elsewhere)

Comment /tf-apply to apply these changes.

@github-actions

github-actions Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

✅ Dry-run passed

Ansible output
ok: [data]

TASK [tailscale : Connect to Tailscale] ****************************************
skipping: [main]
skipping: [data]
skipping: [toliunit]

TASK [tailscale : Apply Tailscale DNS settings] ********************************
skipping: [main]
skipping: [toliunit]
skipping: [data]

TASK [monitoring-agent : Validate API token is set] ****************************
ok: [main] => {
    "changed": false,
    "msg": "All assertions passed"
}
ok: [data] => {
    "changed": false,
    "msg": "All assertions passed"
}
ok: [toliunit] => {
    "changed": false,
    "msg": "All assertions passed"
}

TASK [monitoring-agent : Create pve-exporter system group] *********************
ok: [main]
ok: [toliunit]
ok: [data]

TASK [monitoring-agent : Create pve-exporter system user] **********************
ok: [main]
ok: [toliunit]
ok: [data]

TASK [monitoring-agent : Check if old prometheus-pve-exporter binary exists] ***
ok: [main]
ok: [toliunit]
ok: [data]

TASK [monitoring-agent : Stop old pve-exporter if running (migration from prometheus-pve-exporter)] ***
skipping: [main]
skipping: [data]
skipping: [toliunit]

TASK [monitoring-agent : Remove old prometheus-pve-exporter binary] ************
skipping: [main]
skipping: [data]
skipping: [toliunit]

TASK [monitoring-agent : Download pve-exporter binary] *************************
ok: [main]
ok: [toliunit]
ok: [data]

TASK [monitoring-agent : Create config directory] ******************************
ok: [main]
ok: [toliunit]
ok: [data]

TASK [monitoring-agent : Deploy pve-exporter config] ***************************
ok: [main]
ok: [toliunit]
ok: [data]

TASK [monitoring-agent : Deploy systemd service] *******************************
ok: [main]
ok: [toliunit]
ok: [data]

TASK [monitoring-agent : Enable and start pve-exporter] ************************
ok: [main]
ok: [toliunit]
ok: [data]

PLAY RECAP *********************************************************************
data                       : ok=23   changed=1    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0   
main                       : ok=23   changed=1    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0   
toliunit                   : ok=23   changed=1    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0   

Comment /ansible-apply to apply these changes.

@turtton

turtton commented May 30, 2026

Copy link
Copy Markdown
Owner Author

/tf-apply

@github-actions

github-actions Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

❌ Apply failed

OpenTofu output
cloudflare_dns_record.tunnel["forgejo"]: Creating...
cloudflare_zero_trust_tunnel_cloudflared_config.homelab: Modifying... [id=8dcd868c-295b-4cd0-96d7-37d7928e903d]
cloudflare_zero_trust_tunnel_cloudflared_config.homelab: Modifications complete after 0s [id=8dcd868c-295b-4cd0-96d7-37d7928e903d]
cloudflare_r2_bucket.this["longhorn"]: Still creating... [10s elapsed]
cloudflare_r2_bucket.this["forgejo"]: Still creating... [10s elapsed]
cloudflare_r2_bucket.this["forgejo"]: Still creating... [20s elapsed]
cloudflare_r2_bucket.this["longhorn"]: Still creating... [20s elapsed]
cloudflare_r2_bucket.this["longhorn"]: Still creating... [30s elapsed]
cloudflare_r2_bucket.this["forgejo"]: Still creating... [30s elapsed]
cloudflare_r2_bucket.this["forgejo"]: Still creating... [40s elapsed]
cloudflare_r2_bucket.this["longhorn"]: Still creating... [40s elapsed]
cloudflare_r2_bucket.this["longhorn"]: Still creating... [50s elapsed]
cloudflare_r2_bucket.this["forgejo"]: Still creating... [50s elapsed]
cloudflare_r2_bucket.this["forgejo"]: Still creating... [1m0s elapsed]
cloudflare_r2_bucket.this["longhorn"]: Still creating... [1m0s elapsed]
cloudflare_r2_bucket.this["longhorn"]: Still creating... [1m10s elapsed]
cloudflare_r2_bucket.this["forgejo"]: Still creating... [1m10s elapsed]
cloudflare_r2_bucket.this["forgejo"]: Still creating... [1m20s elapsed]
cloudflare_r2_bucket.this["longhorn"]: Still creating... [1m20s elapsed]
cloudflare_r2_bucket.this["forgejo"]: Creation complete after 1m24s [id=forgejo-backup]
cloudflare_api_token.forgejo_r2: Creating...
cloudflare_r2_bucket.this["longhorn"]: Creation complete after 1m26s [id=longhorn-backup]
cloudflare_api_token.longhorn_r2: Creating...

Warning: Deprecated

  with proxmox_virtual_environment_download_file.talos_image,
  on talos-image.tf line 29, in resource "proxmox_virtual_environment_download_file" "talos_image":
  29: resource "proxmox_virtual_environment_download_file" "talos_image" {

Use "proxmox_download_file" instead. This resource / data source will be
removed in v1.0.

(and 3 more similar warnings elsewhere)

Warning: error waiting for network interfaces from QEMU agent

  with proxmox_virtual_environment_vm.talos_node["toliworker-1"],
  on vms.tf line 1, in resource "proxmox_virtual_environment_vm" "talos_node":
   1: resource "proxmox_virtual_environment_vm" "talos_node" {

error waiting for VM network interfaces: error retrieving VM network
interfaces from agent: received an HTTP 403 response - Reason: Permission
check failed (/vms/1013, VM.GuestAgent.Audit|VM.GuestAgent.Unrestricted)

(and 2 more similar warnings elsewhere)

Error: failed to make http request

  with cloudflare_api_token.forgejo_r2,
  on r2.tf line 37, in resource "cloudflare_api_token" "forgejo_r2":
  37: resource "cloudflare_api_token" "forgejo_r2" {

POST "https://api.cloudflare.com/client/v4/user/tokens": 403 Forbidden
{"success":false,"errors":[{"code":9109,"message":"Valid user-level
authentication not found"}],"messages":[],"result":null}

Error: failed to make http request

  with cloudflare_api_token.longhorn_r2,
  on r2.tf line 54, in resource "cloudflare_api_token" "longhorn_r2":
  54: resource "cloudflare_api_token" "longhorn_r2" {

POST "https://api.cloudflare.com/client/v4/user/tokens": 403 Forbidden
{"success":false,"errors":[{"code":9109,"message":"Valid user-level
authentication not found"}],"messages":[],"result":null}

Error: failed to make http request

  with cloudflare_dns_record.tunnel["forgejo"],
  on tunnel.tf line 44, in resource "cloudflare_dns_record" "tunnel":
  44: resource "cloudflare_dns_record" "tunnel" {

POST
"https://api.cloudflare.com/client/v4/zones/ef642a36cc3c9d8a9e3f757561fa0ce8/dns_records":
400 Bad Request
{"result":null,"success":false,"errors":[{"code":81053,"message":"An A, AAAA,
or CNAME record with that host already exists. For more details, refer to
\u003chttps://developers.cloudflare.com/dns/manage-dns-records/troubleshooting/records-with-same-name/\u003e."}],"messages":[]}
::error::OpenTofu exited with code 1.

Stage 0 of Forgejo + Woodpecker CI on Talos K8s.

- R2: two bucket-scoped tokens (forgejo-backup, longhorn-backup) for
  CNPG barmanObjectStore and Longhorn BackupTarget. Tokens use the
  Workers R2 Storage Bucket Item Read/Write permission groups with a
  resource selector pinning each token to a single bucket.
- Outputs (sensitive): r2_account_id, r2_endpoint_url, and per-bucket
  access_key_id (= token ID) / secret_access_key (=
sha256(token.value)).
  Consumed via 'tofu output -raw <name>' into SOPS-encrypted K8s
Secrets.
- Tailscale: add tag:forgejo (owner tag:k8s-operator), plus the same
  deny rule pattern used for tag:nextcloud, and grant admin ->
tag:forgejo
  on tcp:22/443 for SSH + Web UI access.
- Cloudflare Tunnel: add forgejo.turtton.net -> forgejo-http.forgejo
  cluster service. NOT added to access_protected_hostnames; Forgejo
  enforces its own auth (local + admin 2FA + self-registration
disabled).
@turtton turtton force-pushed the feat/forgejo-woodpecker-plan branch from 4984330 to 79e3167 Compare May 30, 2026 03:37
@turtton

turtton commented May 30, 2026

Copy link
Copy Markdown
Owner Author

/tf-apply

5 similar comments
@turtton

turtton commented May 30, 2026

Copy link
Copy Markdown
Owner Author

/tf-apply

@turtton

turtton commented May 30, 2026

Copy link
Copy Markdown
Owner Author

/tf-apply

@turtton

turtton commented May 30, 2026

Copy link
Copy Markdown
Owner Author

/tf-apply

@turtton

turtton commented May 30, 2026

Copy link
Copy Markdown
Owner Author

/tf-apply

@turtton

turtton commented May 30, 2026

Copy link
Copy Markdown
Owner Author

/tf-apply

cloudflare_api_token uses legacy /user/tokens endpoint which requires
user-level auth unavailable with account-scoped tokens. Switch to
cloudflare_account_token which uses /accounts/{id}/tokens endpoint.

Also imports existing R2 buckets into state after provider upgrade.
@turtton turtton merged commit 0385db4 into main May 30, 2026
2 checks passed
@turtton turtton deleted the feat/forgejo-woodpecker-plan branch May 30, 2026 05:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant