Skip to content

maceosoft Platform — GitHub

How the GitHub org maceosoft connects to the HomeLab cluster: - Argo CD deploys from the private repo maceosoft/gitops. - ARC (Actions Runner Controller) runs a self-hosted GitHub Actions runner inside the cluster (one scale set per organization: arc-runners for maceosoft and arc-runners-symintel for the legacy org; see Two organizations). - OpenTofu manages the org from a pipeline that runs on one of those runners, and stores its state in the cluster.

Where it runs in the implementation guide

This page is the complete reference: permissions, fields, verification, and rotation.

Repos involved

Repo What it contains
maceosoft/homelab This site. Ansible: k3s, Argo CD, and platform secrets (1Password → cluster)
maceosoft/gitops Argo CD Applications, including ARC and the OpenTofu runner
maceosoft/core-pipelines Reusable workflows (tofu.yml, CI, FTP deploy)
maceosoft/infra OpenTofu: org repos, environments, and secrets

How it fits

flowchart LR
  OP[1Password<br/>bóveda HomeLab] -->|playbook-platform-secrets.yml| K8S
  subgraph K8S[Clúster k3s]
    ARGO[ArgoCD] -->|App maceosoft-argocd| GITOPS[(maceosoft/gitops)]
    ARGO --> ARC[ARC controller]
    ARC --> RT[arc-runners]
    RT --> STATE[(ns terraform<br/>Secret tfstate-default-github)]
  end
  INFRA[maceosoft/infra] -->|tofu.yml de core-pipelines| RT
  RT -->|App maceosoft-terraform| GH[org maceosoft]

Some pieces must exist before Argo CD or the pipeline can work. Those are created by hand, once, and OpenTofu neither manages nor imports them:

What Why manual
Repos gitops, infra, core-pipelines Without them there's nowhere to deploy from and no pipeline to run OpenTofu
App maceosoft-argocd Argo CD needs it to read the private repo that deploys everything else
GitHub Apps and OAuth App GitHub doesn't allow creating OAuth Apps via API, and GitHub Apps require browser confirmation

Everything else (repo api and future repos) is created by the infra pipeline.

1Password Items

All in the HomeLab vault. Scripts read them through the 1Password desktop app, which must be unlocked and have Settings → Developer → Integrate with other apps enabled. To indicate which account to use, define ONEPASSWORD_ACCOUNT_NAME with your account name as it appears in the top left of the app sidebar (for example Mi Cuenta; if not defined, it uses Personal):

# tu cuenta
export ONEPASSWORD_ACCOUNT_NAME="Mi Cuenta"
Item Item type Fields Used by
maceosoft-terraform Secure Note app_id, installation_id, private_key OpenTofu runner (TF_VAR_github_app_*)
maceosoft-arc-runners Secure Note app_id, installation_id, private_key ARC (runner registration)
maceosoft-argocd Secure Note app_id, installation_id, private_key Argo CD reads maceosoft/gitops (argocd/repo-gitops)
symintel-arc-runners (optional) Secure Note app_id, installation_id, private_key ARC for the legacy org (symintel): its scale set registration. See Two organizations
symintel-terraform (optional) Secure Note app_id, installation_id, private_key OpenTofu for the legacy org; without it, those runners lack OpenTofu credentials
sealed-secrets Secure Note certificate, private_key (optional until Phase 6) Sealed Secrets encryption key: survives K3s reinstall
maceosoft-dex Secure Note GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET (+ VCLUSTER_CLIENT_SECRET, generated by the playbook) Dex (GitHub login)
discord-webhooks Secure Note critical, warnings: Discord webhook URL per channel; deployments (optional): the despliegues channel one for Argo CD notifications (optional: monitoring) Alertmanager sends alerts (4.13). How to create them: Discord and healthchecks.io
healthchecks-ping Secure Note url: healthchecks.io check ping URL (optional: monitoring) Alertmanager pings it every 5 minutes to signal monitoring is alive. How to create it: Discord and healthchecks.io
grafana-admin Secure Note password and, optionally, username (default admin) (optional: monitoring) Grafana admin
ftp-<repo> Secure Note dev_host, dev_user, dev_password, dev_remote_dir, prod_host, prod_user, prod_password, prod_remote_dir OpenTofu → <repo> FTP secrets (TF_VAR_ftp)

How to create an item with custom fields

The playbook looks up each value by its path op://HomeLab/<item>/<campo>, so the item title and every field name must be exact (lowercase, with underscores).

  1. In 1Password open the HomeLab vault → New Item → Secure Note.
  2. In Title type the item name, for example maceosoft-terraform.
  3. For each field: + Add more → choose the type (Text or Password) → in the field label type the exact name (for example app_id) → paste the value.
  4. Add the fields directly on the item. Do not create a section to group them: with a section the path changes and the playbook won't find them.
  5. Save.

Use Password for secret values (they are hidden) and Text for the rest. Each table below tells which type each field uses.

You can also create the item with the 1Password CLI (op), with 1Password unlocked. Each field is written as nombre[tipo]=valor, where the type is password (secret value) or text; op creates loose fields, without a section. Secret values are not typed in the command: they are pasted without echo using read -rs:

# pega la contraseña y Enter (no se ve)
read -rs FTP_DEV_PASS
op item create --category "Secure Note" --vault HomeLab --title ftp-mi-repo \
  "dev_host[text]=ftp.ejemplo.com" "dev_user[text]=usuario" "dev_password[password]=$FTP_DEV_PASS"
unset FTP_DEV_PASS

If you have more than one 1Password account, export OP_ACCOUNT with yours first. For a random value use $(openssl rand -hex 32). Local AI items (router tokens, Open Terminal…) already have their commands in Secrets Management.

Step 1 — GitHub Apps and OAuth App (manual)

Do this with an organization owner account maceosoft.

maceosoft-terraform

maceosoft → Settings → Developer settings → GitHub Apps → New GitHub App

Field Value
GitHub App name maceosoft-terraform
Homepage URL https://github.com/maceosoft
Webhook → Active unchecked
Where can this GitHub App be installed? Only on this account

The rest of the form (Callback URL, Setup URL, Request user authorization, events) is left empty or unchecked.

Repository permission Access Purpose
Actions Read and write Actions management in repos
Administration Read and write Create repos and branch protection
Contents Read and write Repo files
Environments Read and write Environments dev / prod
Metadata Read-only Required
Secrets Read and write FTP secrets per environment
Variables Read and write PROJECT_STACK, FTP_PROTOCOL
Workflows Read and write .github/workflows/* files
Organization permission Access Purpose
Administration Read and write Create repos in the org

All other permissions remain at No access.

After Create GitHub App, gather three pieces of data and save them in the maceosoft-terraform item in 1Password (Secure Note, see how to create it):

Field in 1Password Type Data Where in GitHub
app_id Text App ID App page, General tab: App ID line (a number, e.g. 1234567)
private_key Password Private key Same page, bottom under Private keys → Generate a private key. A maceosoft-terraform.<fecha>.private-key.pem file downloads. Open it with a text editor and copy everything, including the -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- lines
installation_id Text Installation ID App left menu → Install App → Install next to maceosoft → All repositories → Install. GitHub takes you to https://github.com/organizations/maceosoft/settings/installations/<número>: that final number

The item looks like this:

maceosoft-terraform        (Nota segura · bóveda HomeLab)
├── app_id                1234567
├── installation_id       87654321
└── private_key           ••••••••   (el .pem completo)

Done only from the web: create the App, generate its private key (Generate a private key) and install it. GitHub offers no CLI or API for this (manifest-based App creation also requires browser confirmation). Saving the data in 1Password can be done either way:

When pasting the .pem into a Password field, line breaks are lost. Doesn't matter: the playbook reconstructs the key. Once the item is saved, delete the .pem from Downloads.

What is automated is reading the two numbers and saving the item.

  1. List installed Apps in the org and extract App ID and Installation ID (needs the organization permission: gh auth refresh -s admin:read:org once):

    gh api orgs/maceosoft/installations --jq '.installations[] | [.app_slug, .app_id, .id, .repository_selection] | @tsv'
    
  2. With the downloaded .pem, create the item (1Password unlocked; if you have more than one 1Password account, export OP_ACCOUNT first). The .pem value is read from the file and never typed in the command:

    APP=maceosoft-terraform
    read -r APP_ID INST_ID < <(gh api orgs/maceosoft/installations \
      --jq ".installations[] | select(.app_slug==\"$APP\") | \"\(.app_id) \(.id)\"")
    op item create --category "Secure Note" --vault HomeLab --title "$APP" \
      "app_id[text]=$APP_ID" "installation_id[text]=$INST_ID" \
      "private_key[password]=$(cat ~/Downloads/$APP.*.private-key.pem)"
    
  3. Verify without showing values and delete the .pem:

    python3 ansible/scripts/apply_platform_secrets.py --check
    # en macOS: rm -P
    shred -u ~/Downloads/$APP.*.private-key.pem
    

The same command works for the other Apps: just change APP (maceosoft-arc-runners, maceosoft-argocd, and symintel-arc-runners / symintel-terraform if using the old org, changing orgs/maceosoft to orgs/symintel).

maceosoft-arc-runners

This is the App ARC uses to register the runner arc-runners in the org. It is created in the same form as maceosoft-terraform, but it is a separate App: its own name, permissions, and credentials.

maceosoft → Settings → Developer settings → GitHub Apps → New GitHub App

Field Value
GitHub App name maceosoft-arc-runners
Homepage URL https://github.com/maceosoft
Webhook → Active unchecked
Where can this GitHub App be installed? Only on this account

The rest of the form (Callback URL, Setup URL, Request user authorization, events) is left empty or unchecked.

Repository permission Access Purpose
Actions Read-only View queued jobs the runner must pick up
Administration Read and write Register runners (requested by ARC)
Metadata Read-only Required
Organization permission Access Purpose
Self-hosted runners Read and write Register and delete the runner arc-runners in the org

All other permissions remain at No access.

After Create GitHub App, save its three pieces of data in the maceosoft-arc-runners item in 1Password (Secure Note, see how to create it):

1Password field Type Data Where in GitHub
app_id Text App ID App page, General tab: App ID line
private_key Password Private key Same page, bottom under Private keys → Generate a private key. Downloads maceosoft-arc-runners.<fecha>.private-key.pem. Open with a text editor and copy everything, including the -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- lines
installation_id Text Installation ID App left menu → Install App → Install next to maceosoft → All repositories → Install. The number at the end of the URL https://github.com/organizations/maceosoft/settings/installations/<número>

The item looks like this:

maceosoft-arc-runners      (Nota segura · bóveda HomeLab)
├── app_id                2345678
├── installation_id       98765432
└── private_key           ••••••••   (el .pem completo)

Creating the App, its key, and installing it is only done from the web; saving the item can be done either way:

The three values are different from those of maceosoft-terraform: each App has its own App ID, its own key, and its own installation. Do not copy the ones from the other App. Once the item is saved, delete the .pem from Downloads.

The same maceosoft-terraform command with APP=maceosoft-arc-runners: gh api outputs this App's numbers, so they don't get mixed up with the other one.

APP=maceosoft-arc-runners
read -r APP_ID INST_ID < <(gh api orgs/maceosoft/installations \
  --jq ".installations[] | select(.app_slug==\"$APP\") | \"\(.app_id) \(.id)\"")
op item create --category "Secure Note" --vault HomeLab --title "$APP" \
  "app_id[text]=$APP_ID" "installation_id[text]=$INST_ID" \
  "private_key[password]=$(cat ~/Downloads/$APP.*.private-key.pem)"
python3 ansible/scripts/apply_platform_secrets.py --check
# en macOS: rm -P
shred -u ~/Downloads/$APP.*.private-key.pem

OAuth App Maceosoft Dex

maceosoft → Settings → Developer settings → OAuth Apps → New OAuth App

Field Value
Application name Maceosoft Dex
Homepage URL https://argocd.homelab.local
Redirect URIs https://argocd.homelab.local/api/dex/callback (only this one)

After Register application, create the maceosoft-dex item in the HomeLab vault (Secure Note, see how to create it) with these two fields:

1Password field Type Data Where in GitHub
GITHUB_CLIENT_ID Text Client ID OAuth App page: Client ID line
GITHUB_CLIENT_SECRET Password Client secret Same page → Generate a new client secret. GitHub shows it only once: copy it at that moment

The item looks like this (the last field is added automatically by playbook-dex-oauth-secrets.yml the first time it runs: it's the vCluster Dex client secret, not from GitHub):

maceosoft-dex              (Nota segura · bóveda HomeLab)
├── GITHUB_CLIENT_ID      Ov23li…
├── GITHUB_CLIENT_SECRET  ••••••••
└── VCLUSTER_CLIENT_SECRET ••••••••  (lo genera el playbook)

Creating the OAuth App and generating its client secret is only done from the web: GitHub has no API for OAuth Apps. Saving the two values in 1Password can be done either way:

Create the Secure Note maceosoft-dex and paste each value from the table into its field (how to create it).

Once you have the two values, save them without displaying them on screen (if you have more than one 1Password account, export OP_ACCOUNT first):

# pega el Client ID y Enter
read -r  GH_CID
# pega el Client secret y Enter
read -rs GH_CSEC
op item create --category "Secure Note" --vault HomeLab --title maceosoft-dex \
  "GITHUB_CLIENT_ID[text]=$GH_CID" "GITHUB_CLIENT_SECRET[password]=$GH_CSEC"
unset GH_CID GH_CSEC

To verify someone is in the team devops (or add them) from the console: gh api orgs/maceosoft/teams/devops/memberships/<usuario> (returns "state": "active") and, to add them as a member, gh api -X PUT orgs/maceosoft/teams/devops/memberships/<usuario> -f role=member.

Only the devops team from maceosoft can enter, and its members become Argo CD admins. This is applied in Phase 4 step 4.7.

Step 2 — Repos, Argo CD App & FTP (manual)

  1. Repos (private), created in maceosoft with their content pushed via git:

    • gitops.
    • core-pipelines: afterwards, make it accessible to the other repos in the org (below). While testing, the other repos use it with @main; the tag v1 is created once everything works (see the README of core-pipelines).
    • infra.

    Create each repo in the org with New repository (private) and push its content with git. Then, in core-pipelines: Settings → Actions → General → Access, choose "Accessible from repositories in the 'maceosoft' organization".

    With the GitHub CLI, from each project's folder:

    gh repo create maceosoft/gitops --private --source . --remote origin --push
    # igual para core-pipelines e infra
    gh repo list maceosoft --json name,visibility --jq '.[] | [.name, .visibility] | @tsv'
    # core-pipelines accesible desde los demás repos de la org
    gh api -X PUT repos/maceosoft/core-pipelines/actions/permissions/access -f access_level=organization
    
    1. GitHub App maceosoft-argocd, used by Argo CD to read the private repo gitops. Comes after the repos because it is installed only on gitops. (Deploy keys are disabled in the organization, so an App is used.)

    maceosoft → Settings → Developer settings → GitHub Apps → New GitHub App

    Field Value
    GitHub App name maceosoft-argocd
    Homepage URL https://github.com/maceosoft
    Webhook → Active unchecked
    Where can this GitHub App be installed? Only on this account

    The rest of the form is left empty or unchecked.

    Repository permission Access Purpose
    Contents Read-only Clone the repo
    Metadata Read-only Required

    No organization permissions. All others remain No access.

    After Create GitHub App, save its three pieces of data in the maceosoft-argocd item in 1Password (Secure Note, see how to create it):

    1Password field Type Data Where in GitHub
    app_id Text App ID App page, General tab: App ID line
    private_key Password Private key Same page, bottom under Private keys → Generate a private key. Copy the entire downloaded .pem, including the BEGIN/END lines
    installation_id Text Installation ID Left menu → Install App → Install next to maceosoft → Only select repositories → choose gitops → Install. The number at the end of the URL …/settings/installations/<número>

    Install it only on gitops: with that key you can read the code of all repos where it is installed. Delete the .pem when finished.

    Creating the App, its key, and installing it is web-only; saving the item can be done either way:

    Create the Secure Note maceosoft-argocd and paste each piece of data from the table into its field (how to create it).

    The same command from maceosoft-terraform, with APP=maceosoft-argocd:

    APP=maceosoft-argocd
    read -r APP_ID INST_ID < <(gh api orgs/maceosoft/installations \
      --jq ".installations[] | select(.app_slug==\"$APP\") | \"\(.app_id) \(.id)\"")
    op item create --category "Secure Note" --vault HomeLab --title "$APP" \
      "app_id[text]=$APP_ID" "installation_id[text]=$INST_ID" \
      "private_key[password]=$(cat ~/Downloads/$APP.*.private-key.pem)"
    # en macOS: rm -P
    shred -u ~/Downloads/$APP.*.private-key.pem
    
  2. FTP (File Transfer Protocol) credentials for repo api, the first project in the map with deploy = true: title ftp-api, type Secure Note. For each repo you add later, follow the procedure — Add a repo. Fields, created the same way as the others:

    Field Type Example
    dev_host Text dev.tu-cpanel.example.com
    dev_user Text usuario_dev
    dev_password Password
    dev_remote_dir Text /home/usuario/dev.maceosoft.example.com
    prod_host Text tu-cpanel.example.com
    prod_user Text usuario_prod
    prod_password Password
    prod_remote_dir Text /home/usuario/public_html

    Change the hosts, users, and folders to yours; the two passwords are pasted without echo (if you have more than one 1Password account, export OP_ACCOUNT first):

    # pega cada contraseña y Enter
    read -rs FTP_DEV_PASS;  read -rs FTP_PROD_PASS
    op item create --category "Secure Note" --vault HomeLab --title ftp-api \
      "dev_host[text]=dev.tu-cpanel.example.com" "dev_user[text]=usuario_dev" \
      "dev_password[password]=$FTP_DEV_PASS" "dev_remote_dir[text]=/home/usuario/dev.maceosoft.example.com" \
      "prod_host[text]=tu-cpanel.example.com" "prod_user[text]=usuario_prod" \
      "prod_password[password]=$FTP_PROD_PASS" "prod_remote_dir[text]=/home/usuario/public_html"
    unset FTP_DEV_PASS FTP_PROD_PASS
    
  3. Sealed Secrets key (optional: needed when you start storing SealedSecret in Git, e.g., in Phase 6). Sealed Secrets encrypts secrets with a key pair that, by default, the controller generates inside the cluster: if you reinstall K3s it is lost, and the SealedSecret already in Git can no longer be decrypted. That's why it is generated once, saved in 1Password, and the secrets playbook brings it to the cluster before the controller starts.

    Generate the pair in a temporary folder (openssl ships with macOS and Linux):

    cd "$(mktemp -d)"
    openssl req -x509 -days 3650 -nodes -newkey rsa:4096 \
      -keyout tls.key -out tls.crt -subj "/CN=sealed-secret/O=sealed-secret"
    

    Create in 1Password the item sealed-secrets (Secure Note, created the same way as the others):

    Field Type Value
    certificate Text The full content of tls.crt (the public key), with the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- lines
    private_key Password The full content of tls.key (the private key), with its BEGIN/END lines

    To copy a file: pbcopy < tls.crt (macOS) or open it with an editor and copy everything.

    With the 1Password CLI the files are read directly (if you have more than one 1Password account, export OP_ACCOUNT first):

    op item create --category "Secure Note" --vault HomeLab --title sealed-secrets \
      "certificate[text]=$(cat tls.crt)" "private_key[password]=$(cat tls.key)"
    python3 ansible/scripts/apply_platform_secrets.py --check
    

    Once the item is saved, delete the files:

    rm -f tls.key tls.crt
    

    The certificate is valid for 10 years. The playbook verifies that the certificate and key are a pair (if you copied files from different pairs, it stops with a clear message) and creates the Secret kube-system/sealed-secrets-key-homelab, which the controller detects by its label sealedsecrets.bitnami.com/sealed-secrets-key: active. While the item doesn't exist, the playbook warns and continues: Sealed Secrets would generate its own key.

Step 3 — Cluster and platform secrets

With k3s installed (Phase 3, Argo CD included):

# tu cuenta
export ONEPASSWORD_ACCOUNT_NAME="Mi Cuenta"
export ONEPASSWORD_VAULT="HomeLab"
cd ansible
ansible-playbook -i inventory.ini playbook-platform-secrets.yml

playbook-platform-secrets.yml creates the namespaces argocd, arc-runners (PSS privileged, for the dind sidecar) and terraform (only stores OpenTofu state), and these Secrets:

Secret Namespace Content
repo-gitops argocd App maceosoft-argocd: Argo CD can clone https://github.com/maceosoft/gitops.git
arc-runners-github-app arc-runners ARC App
terraform-app arc-runners TF_VAR_github_app_id, TF_VAR_github_app_installation_id, TF_VAR_github_app_private_key
tofu-vars arc-runners TF_VAR_ftp (all ftp-<repo> items)
arc-runners-github-app, terraform-app arc-runners-symintel Same for the old org, from symintel-arc-runners and symintel-terraform (only if they exist). The namespace is created with PSS baseline
sealed-secrets-key-homelab kube-system Sealed Secrets key pair (item sealed-secrets, if exists)
discord-webhooks, healthchecks-ping, grafana-admin monitoring Discord webhooks, healthchecks.io ping URL, and Grafana password (items with the same names; only if all three exist). The namespace is created with PSS privileged, because node-exporter uses the node's disk and network

Single runner

It's a HomeLab: the single scale set (arc-runners, Default group) runs CI (continuous integration), deploys and OpenTofu, and has the OpenTofu credentials as environment variables. Any maceosoft repo that runs a job there can reach them. Keep the org with only your repos.

Always against the HomeLab

The playbook uses ~/.kube/homelab-k3s.yaml (the one left by k3s_fetch_kubeconfig), not the current kubectl context. This way it doesn't write secrets to another cluster. For another kubeconfig: -e platform_kubeconfig=/ruta.

It's idempotent: run it again whenever you rotate something in 1Password or add a ftp-<repo> item.

Two organizations: one scale set per org

An ARC scale set registers with a single GitHub organization (githubConfigUrl). For the same runners to serve the new org (maceosoft) and the old org (symintel) there are two scale sets, with a single controller (arc-controller) serving both:

Org maceosoft Org symintel (old)
Application arc-runners arc-runners-symintel
Namespace arc-runners arc-runners-symintel
ARC App (1Password item) maceosoft-arc-runners symintel-arc-runners
Name in runs-on: arc-runners arc-runners (the same)
Cache /cache (RWX, Longhorn) 20 GiB 10 GiB, separate
Docker (dind) Yes, with a privileged sidecar Yes, the same
OpenTofu and FTP Yes (terraform-app, tofu-vars) Only if item symintel-terraform exists

Both use the same runner template (values-scaleset.yaml); the old org changes githubConfigUrl (values-scaleset-symintel.yaml). Each namespace has its own Secrets, with the same name, so those of one org aren't visible to the other, and the cache isn't shared either (a poisoned cache in one org doesn't reach the other).

To enable the old org: create the ARC App in symintel (same as maceosoft-arc-runners, but in that org) and save it in item symintel-arc-runners; then run playbook-platform-secrets.yml. If the item doesn't exist, the script doesn't create the namespace or the Secret and the old org's scale set doesn't start. To remove it, delete the Application arc-runners-symintel and the items.

Step 4 — GitOps

kubectl apply -f gitops/bootstrap/root-appset.yaml

Argo CD syncs, among others, these Applications:

Application What it deploys
arc-controller ARC Controller (arc-systems)
terraform-rbac ServiceAccount tofu-runner + Role (only Secrets/Leases from namespace terraform)
arc-runners The runner (up to 3 in parallel), with namespace Secrets as environment variables

Step 5 — First OpenTofu pipeline

Push to main of infra, or trigger it from Actions → tofu → Run workflow (with the CLI: gh workflow run tofu.yml --repo maceosoft/infra --ref main and gh run watch --repo maceosoft/infra). The workflow tofu.yml runs on arc-runners: it does plan on PRs (pull requests) and apply on main. On the first run it creates api.

Verify

Before the cluster, ensure 1Password has everything (from the root of homelab):

# tu cuenta
export ONEPASSWORD_ACCOUNT_NAME="Mi Cuenta"
# items y campos: todo ✓
.venv/bin/python ansible/scripts/apply_platform_secrets.py --check
# Secrets que se van a crear, sin valores
.venv/bin/python ansible/scripts/apply_platform_secrets.py --dry-run

After the playbook and GitOps:

kubectl -n argocd get secret repo-gitops
kubectl -n arc-runners get secret \
  arc-runners-github-app terraform-app tofu-vars
# org antigua, si la usas:
kubectl -n arc-runners-symintel get secret arc-runners-github-app
kubectl -n argocd get applications | grep -E 'arc|terraform'
# controller + listener del scale set
kubectl -n arc-systems get pods
# después del primer apply de OpenTofu
kubectl -n terraform get secret tfstate-default-github

Rotate credentials

Full step-by-step: Operations — Rotate credentials.

What How
Private key of a GitHub App Generate a new one in GitHub (web only), replace private_key in 1Password (op item edit <item> "private_key[password]=$(cat llave.pem)"), re-run playbook-platform-secrets.yml and delete the old one in GitHub
Private key of maceosoft-argocd Same as other Apps: new key in GitHub, replace private_key, re-run the playbook and delete the old one
FTP credentials Edit ftp-<repo> (op item edit ftp-<repo> "prod_password[password]=..."), re-run the playbook and trigger the infra pipeline (gh workflow run tofu.yml --repo maceosoft/infra --ref main)
Discord webhook, healthchecks.io ping URL, or Grafana password Change the field in its item, re-run playbook-platform-secrets.yml and follow Operations — Rotate credentials
Dex client secret New secret in the OAuth App, update GITHUB_CLIENT_SECRET and run playbook-dex-oauth-secrets.yml