feedc0de 0293469f5f
Validate and deploy Mumble / Validate manifests (push) Successful in 24s
Validate and deploy Mumble / Deploy to Kubernetes (push) Successful in 30s
Trying to fix CI/CD
2026-08-08 21:50:56 +02:00
2026-08-08 21:50:56 +02:00
2026-08-08 21:24:30 +02:00
2026-08-08 21:50:56 +02:00
2026-08-08 21:50:56 +02:00

Mumble on Kubernetes

Production deployment for the brunner.ninja Mumble server. It runs the official Mumble server image with trusted TLS, a MetalLB address, and the shared PostgreSQL instance in the default namespace.

Architecture

  • mumblevoip/mumble-server:v1.5.915-1 runs as an unprivileged user.
  • MetalLB advertises 192.168.0.14; TCP and UDP use port 64738 on the same LoadBalancer Service.
  • brunner-ninja-tls, maintained by cert-manager, supplies the certificate for both brunner.ninja and *.brunner.ninja.
  • A small sidecar watches the mounted certificate and asks Mumble to reload TLS with SIGUSR1 after renewal.
  • Mumble stores channels, ACLs, registered users, bans, and server state in the mumble database on postgresql.default.svc.cluster.local.
  • /data and the generated INI are ephemeral. No Mumble PVC is needed.
  • The single replica uses a Recreate strategy because two processes must not serve the same Mumble virtual server database concurrently.

The runtime INI is rendered into a mode-0600 in-memory volume. This avoids an upstream container-entrypoint behavior that can print registerPassword when configuration is supplied through environment variables.

Repository contents

  • mumble.yaml: LoadBalancer Service and Mumble Deployment.
  • install.sh: administrator installation, credential generation, idempotent PostgreSQL bootstrap, and rollout verification.
  • test.sh: local script and offline Kubernetes schema validation.
  • ci-deployer.yaml: restricted Gitea Actions ServiceAccount, Role, binding, and revocable token request.
  • create-ci-kubeconfig.sh: one-time CI identity bootstrap.
  • .gitea/workflows/deploy.yml: validation on every push and pull request; deployment after successful validation of main.

The old host-level mumble-server.ini is intentionally ignored because it contains obsolete plaintext credentials. Do not commit kubeconfigs, database passwords, TLS keys, or generated Kubernetes Secret values.

Prerequisites

  • Kubernetes and kubectl.
  • MetalLB pool default containing the reserved address 192.168.0.14.
  • default/postgresql Service and Secret. The Secret must contain postgres-password.
  • default/brunner-ninja-tls, maintained by the existing cert-manager letsencrypt-dns setup.
  • Router forwarding of TCP and UDP 64738 to 192.168.0.14. On the MikroTik, the two rules use dst-address-list=WANs, which also permits LAN hairpin connections through brunner.ninja.

Install or restore manually

Run the first installation with an administrator kubeconfig:

./install.sh

On the first run, the script creates default/mumble-secrets with independent 256-bit random database, SuperUser, and public-registry passwords. It then creates the PostgreSQL role and database if absent. Later runs preserve the Secret and database, reset the role to the stored password, reapply ownership and grants, and safely recreate or update the workload.

Rotate only the public Mumble registry password with:

ROTATE_REGISTRATION_PASSWORD=true ./install.sh

Retrieve the Mumble administrator password without placing it in Git:

kubectl get secret mumble-secrets -n default \
  -o jsonpath='{.data.superuser-password}' | base64 -d; echo

Connect to brunner.ninja:64738 or mumble.brunner.ninja:64738.

Restoring a PostgreSQL backup

Stop Mumble before replacing its database:

kubectl delete deployment mumble -n default

Restore the mumble database and ensure its objects are owned by role mumble, then run ./install.sh. The bootstrap is idempotent: it does not drop an existing database or tables. Mumble may migrate an older compatible schema when it starts. Its channels and registrations remain in PostgreSQL.

Validate locally

./test.sh

The test checks both shell scripts and validates the Kubernetes resources with kubeconform without contacting the cluster. It uses a locally installed kubeconform binary when available, otherwise the official ghcr.io/yannh/kubeconform:v0.7.0 container, pinned by digest, through Docker.

Automatic deployment with Gitea Actions

The workflow validates every push and pull request. A successful push to main applies mumble.yaml and polls the Deployment for up to five minutes. It does not build a container because this repository deliberately uses the official, version-pinned Mumble image.

CI does not invoke install.sh and cannot create application credentials or bootstrap PostgreSQL. Its Role can call get, patch, and update only for the existing default/mumble Service and Deployment. It cannot create or delete workloads and has no direct Kubernetes Secrets API permission.

One-time CI setup

  1. Install Mumble manually with ./install.sh so the Service, Deployment, application Secret, role, and database exist.

  2. With an administrator kubeconfig active, run:

    ./create-ci-kubeconfig.sh
    
  3. Copy the single base64 output line into the Gitea repository Actions secret named KUBE_CONFIG_BASE64.

  4. Protect main and require the validation job before merging.

  5. Push a harmless README change and confirm the deploy job reports a healthy rollout.

The runner networking in the workflow matches the existing brunner.ninja runner: API URL https://host.containers.internal:6443, with TLS server name 192.168.0.2.

To revoke CI access, delete the token and remove the Gitea secret:

kubectl delete secret mumble-deployer-token -n default

Running ./create-ci-kubeconfig.sh again recreates the token and prints a new kubeconfig.

Security boundary

The Role prevents ordinary API reads of Secrets and limits updates to two named objects. Kubernetes RBAC cannot restrict individual fields inside a Deployment patch, however. A malicious change merged into main could modify the Mumble pod specification and potentially expose Secrets already referenced by that workload. Branch protection and review of manifest and workflow changes remain part of the security boundary. Admission policy would be required to constrain specific pod fields further.

Gitea CI or Argo CD?

Gitea CI is the recommended deployment mechanism for this repository today. It is already proven on this cluster, the deployment is only two resources, and it does not automatically undo deliberate maintenance such as deleting the Deployment while restoring PostgreSQL.

Capability Gitea Actions used here Argo CD
Trigger Push to main Continuous Git reconciliation/webhook
Drift detection No Built-in diff and health view
Self-healing No; rerun CI or install.sh Optional automatic reconciliation
Deleted Deployment Stays deleted for maintenance Recreated when self-heal is enabled
Cluster credential Restricted token per repository Repository credential plus central Argo controller
Rollout view Action log Application resource tree, health, sync history, and UI
Pruning Not performed Optional, with confirmation controls available
Secret handling Existing Secrets remain out of Git Still needs an external Secret strategy

Argo CD becomes attractive when several more workloads should share one consistent deployment model, when live drift must be visible or automatically repaired, or when multiple clusters/environments are introduced. It can remove per-repository Kubernetes deploy tokens because CI only needs to update Git. Adoption should start with an AppProject, explicit namespace/resource limits, and a maintenance procedure that disables sync before database restores.

S
Description
Configuration files to run my mumble server in kubernetes with cert-manager and L2 LoadBalancer (uses my shared postgresql)
Readme GPL-3.0
66 KiB
Languages
Shell 100%