Create the three arrstack source-fork repos (Postgres + stateless
multi-replica work). No branch protection yet so the initial upstream
mirror can be force-pushed; a follow-up PR adds protection + required
Woodpecker checks once CI exists.
Match the sibling Go service repo convention (node-lookup/encapi):
require the woodpecker pre-commit/test/build PR status checks,
squash merge style, and restrict merge/approval to Owners/benvin.
Match the vault-secrets-netbox naming convention: repo
terraform-provider-vault-secrets-ghp, provider source address
git.unkin.net/unkin/vault-secrets-ghp, resources ghp_secret_backend /
ghp_secret_role. Renames the repo (the only content was an unmerged scaffold PR,
which is re-pushed to the new repo).
P4 steps 5-6: a Vault/OpenBao secrets engine that mints scoped ghp access tokens
via ghp's admin API (authenticating with the static admin service token now
deployed), plus its Terraform provider. Mirrors the vault-plugin-secrets-gitea /
terraform-provider-giteavaultsecret pair.
Provision the Gitea repo for the autobackup-operator, a Kubernetes
operator that creates S3 buckets and backup schedules from
backups.unkin.net/* annotations on PVCs and CNPG clusters. Matches the
convention of sibling operator repos (private=false, squash merges,
main branch protection with pre-commit/test/build checks).
Why: the jellyfin-ha-src main branch is protected but merges are not gated
on CI, so a pull request can be merged while its Woodpecker build is red.
How: add the ci/woodpecker/pr/ci context to the main branch protection
rule. The gitea provider computes enable_status_check from a non-empty
context list, so this turns required status checks on and blocks merge
until that exact check passes.
Why:
- The go-gitea/gitea provider schema defaults migration_mirror_interval to
"8h0m0s", but on read it writes repo.MirrorInterval straight back into state,
and Gitea returns an empty MirrorInterval for non-mirror repositories.
- So the stored value ("") never matches the schema default ("8h0m0s"), and tofu
plans an in-place update of every gitea_repository on every run even with no
config change, keeping terraform-git plans perpetually dirty.
- migration_mirror_interval is the only migration_* field read back from the API;
the rest are write-only migration options, so no sibling field churns.
Change:
- Add a targeted ignore_changes on migration_mirror_interval at the repository
module so the provider default no longer fights Gitea's empty value, making
every repository idempotent. It is a migration-only knob with no drift to track
for these non-mirror repos.
Now that the source fork is seeded on main with the pinned upstream tree and a
pristine upstream ref, enable branch protection on main: block direct pushes
and require an Owners approval so future changes land through reviewed pull
requests.
Add the jellyfin-ha-src Gitea repository that carries the HA source fork of
ZoltyMat/jellyfin-ha with local patches, replacing the direct upstream clone
in the build pipeline.
Declare the repository as public with a main default branch and
delete-branch-after-merge enabled. Leave branch protection unset so the
initial fork seed can push the pinned upstream tree and a pristine upstream
ref directly to main; protection is added in a follow-up change once the fork
is seeded.
Why:
- The go-gitea/gitea provider's SDK (v0.25.1, already shipped in provider
0.8.1 on main) rejects a team permission of `none` on every EditTeam with
"permission mode invalid"; it only accepts read/write/admin/owner
client-side. The same check exists in the older 0.21.0 SDK, so the provider
version is not the lever here.
- The team module's `units` (server returns an unordered space-separated
string that never matches the provider's comma-ordered default) and
`repositories` (provider sorts state; `include_all_repositories` populates
every repo) drift on representation alone, so every apply plans an in-place
update for every team.
- Each update calls EditTeam, which replays the stored `permission=none` kept
by `ignore_changes` and fails, blocking apply for every non-owner team
(docker/terraform/rpmbuild/puppet/pybuild/forgebot/agents) and the whole
terraform-git apply, including the unkin-agent rollout.
Change:
- Add `units` and `repositories` to the team `ignore_changes` so provider
representation churn no longer triggers an EditTeam call.
- Preserve each team's current on-server permission and repository membership
(no access change).
Why:
- tofu apply fails with "Branch protection already exist" because the nine
repos protected in #60 already have live branch protection in Gitea that is
absent from Terraform state, so Terraform tries to create rules that already
exist and Gitea rejects them.
- The pinned go-gitea/gitea provider 0.7.0 defines no importer for
gitea_repository_branch_protection, so the existing rules cannot be adopted
on that version.
How:
- Bump the gitea provider from 0.7.0 to 0.8.1, which adds an importer for
gitea_repository_branch_protection (id format <org>/<repo>/<rule_name>).
- Add import blocks adopting the nine existing branch protections into state
instead of recreating them.
Why:
- An audit for the new unkin-agent bot (org-wide write) found repos with
no branch_protection at all, so the agent (or anyone with write) could
push straight to their default branch.
- The rest of the estate protects default branches; these repos were the gap.
How:
- Add enable_push: false + approval_whitelist_teams: Owners on each active
repo's default branch, matching the estate's most common convention
(PR-only; Owners can still open and approve).
- Omit status checks on these repos as none run CI, so a required context
would permanently block merges.
- Leave the 8 archived repos untouched: they are read-only, nothing can push
to them, and branch-protection creation on archived repos is rejected.
Why: AI coding agents currently push and open PRs as Ben's own account
using Ben's token, so their work is unattributable and carries Ben's full
owner privileges. A dedicated least-privilege identity separates agent work
from Ben's and keeps merge a human decision.
How:
- add the unkin-agent bot user (non-admin, no org/repo creation, limited
visibility); it authenticates via ephemeral vault-plugin-secrets-gitea
tokens, not a stored password
- add the agents team granting Write on all unkin repositories, the minimum
needed to push branches and open PRs; unkin-agent is its only member
- add merge_whitelist_teams Owners to every default-branch protection rule
that lacked a merge whitelist, so Write no longer implies merge and only
Owners (Ben) can merge across the estate
Scope grew beyond IPAM (devices, provisioning). Renaming the config file renames
the managed repo; the gitea provider archives the old terraform-ipam repo
(archive_on_destroy=true) and creates terraform-infra. terraform-ipam only holds an
auto-init README, so nothing is lost.
Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
Provision the Gitea repo for a generic app-token Vault/OpenBao secrets
engine that issues short-lived signed JWTs for self-made services,
replacing per-app static bearer Secrets. Mirrors the existing plugin repo
entries (squash-only, main branch protection, PR CI checks).
Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
Provision the repo for the new Kea DHCP Kubernetes operator that replaces
the ISC dhcpd PXE-boot VM.
- add config/git.unkin.net/unkin/repository/kea-operator.yaml
Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT