Initial commit

This commit is contained in:
2026-10-24 16:21:16 -04:00
commit b894fa148a
21 changed files with 719 additions and 0 deletions
Executable
+167
View File
@@ -0,0 +1,167 @@
# ansible-role-owncast
Reusable Ansible role for deploying [Owncast](https://owncast.online/) on an
existing Kubernetes cluster. Extracted from ansible-role-eom; no EOM services,
domains, certificate issuers, or Tor installation are assumed by role defaults.
## Requirements
- Ansible Core 2.16–2.19 on a Linux/macOS controller, WSL, or a Linux container.
- Install [requirements.txt](requirements.txt) with pip and the collection with
`ansible-galaxy collection install -r requirements.yml`. Dependencies belong
in the interpreter Ansible uses on localhost.
- An authorized kubeconfig or the usual `K8S_AUTH_*` authentication environment
settings supported by `kubernetes.core.k8s`.
- A default StorageClass supporting ReadWriteOnce, an explicit StorageClass, or
an existing PVC in the instance namespace. Use encrypted backing storage and
arrange encrypted backups separately.
- For optional ingress: DNS, an ingress controller, and a valid TLS secret or
cert-manager with a configured ClusterIssuer. For LoadBalancer services: a
working cluster load balancer implementation.
- Optional onion access requires the Tor operator and its
`tor.k8s.torproject.org/v1alpha2` OnionService CRD already installed.
Check out the repository as `ansible-role-owncast` under a directory in
`ANSIBLE_ROLES_PATH`. For sibling role repositories, set that environment
variable to their parent directory. The supplied playbooks run on the controller
using its current Kubernetes context, with no SSH target required. Deployment
commands below run from this repository on the controller.
## Your existing eom.dev instance
Use [playbooks/deploy-eom.yml](playbooks/deploy-eom.yml). It explicitly preserves:
- Namespace, Deployment, Service, PVC, and TLS secret named `owncast`.
- Image `owncast/owncast:0.3.0`, one replica, 128Gi ReadWriteOnce storage at
`/app/data`, CPU request `1.5`, HTTP 8080 and RTMP 1935.
- LoadBalancer service with `externalTrafficPolicy: Local`.
- NGINX ingress for `owncast.eom.dev`, with ClusterIssuer `ca-issuer`.
- OnionService `http` in namespace `owncast`, port 80 → HTTP 8080, formerly
part of the shared EOM onion task.
First run `ansible-playbook -i localhost, playbooks/deploy-eom.yml --check --diff`,
review the changes, then run `ansible-playbook -i localhost, playbooks/deploy-eom.yml`.
If Tor was never installed, add `-e owncast_onion_enabled=false` to both commands.
### Migration safety
1. Back up the data volume securely and record the live image, resource names,
StorageClass, selector, and any provider annotations before deploying.
2. Check the Kubernetes context. Keep the same namespace, PVC, and selector
`app: owncast`. A different live immutable Deployment selector needs manual
reconciliation; the role does not force-replace resources.
3. Stop invoking the retired EOM Owncast task. Its deployment code,
full-deployment entry, and Owncast-specific onion task have been removed.
The old task path only fails with migration guidance; it does not delegate
or deploy anything. No automatic dependency is added; use this playbook.
4. Do not delete the existing PVC or TLS secret. Resources are adopted by name;
administrator settings, stream keys, and the data directory are not reset.
Leave `owncast_storage_class` blank to preserve the existing claim's class.
5. Verify the rollout, HTTPS, administrator login, RTMP ingest, and optional
onion URL after deployment.
The Deployment API is corrected from the old invalid `v1` to `apps/v1`.
Updates use `Recreate` rather than overlapping replicas so the SQLite database
and RWO volume have a single writer; an explicit null removes inherited
`rollingUpdate` settings when patching the existing Deployment. Expect brief
downtime during an update.
Removing Ansible tasks does not delete any live cluster resources.
## Your friend's instance
Use [playbooks/deploy-friend.yml](playbooks/deploy-friend.yml). It uses separate
namespace, resource names, PVC, and TLS secret `owncast-friend`, with the same
image, storage sizing, resource request, and network layout as your instance.
Tor is disabled. Two required inputs deliberately have no invented defaults:
- `friend_owncast_hostname`: your friend's real streaming hostname.
- `friend_owncast_cluster_issuer`: a ClusterIssuer valid for that hostname.
Define these in inventory variables for localhost or an external YAML vars file.
Run `ansible-playbook -i localhost, playbooks/deploy-friend.yml -e @/secure/friend-vars.yml`.
Use `--check --diff` first. Add `--ask-vault-pass` for a Vault-encrypted vars file;
do not commit secrets or pass passwords on the command line. Extra variables
can also override the `owncast_*` settings when different sizing is needed.
The playbook rejects missing inputs, the eom.dev hostname, and the existing
namespace before changing the cluster. For deployment on another cluster, use
a separate kubeconfig/context. On a shared cluster, plan for another load
balancer address and volume. Namespace separation is not a tenant security
boundary by itself.
## Generic usage and variables
Invoke `ansible-role-owncast` from your own playbook or inventory; neither
site-specific playbook is required. All defaults are in
[defaults/main.yml](defaults/main.yml).
| Variable | Default / meaning |
| --- | --- |
| `owncast_namespace`, `owncast_name` | `owncast`; change for independent instances |
| `owncast_image` | `owncast/owncast:0.3.0`; override with a tested tag or digest |
| `owncast_image_pull_policy`, `owncast_image_pull_secrets` | `IfNotPresent`, `[]`; Secret references for private registries |
| `owncast_resources` | Requests of 250m CPU and 256Mi memory; configure requests/limits for your transcoding workload |
| `owncast_node_selector`, `owncast_tolerations` | `{}`, `[]` |
| `owncast_pvc_name` | Resource name |
| `owncast_existing_claim` | Empty; nonempty skips PVC creation and mounts that existing claim in this namespace |
| `owncast_storage_size`, `owncast_storage_class` | `10Gi`, empty (default StorageClass) |
| `owncast_service_type` | `ClusterIP`; also supports NodePort and LoadBalancer |
| `owncast_service_annotations` | `{}`; provider-specific configuration |
| `owncast_external_traffic_policy` | `Local`; emitted only for NodePort/LoadBalancer |
| `owncast_load_balancer_source_ranges` | `[]`; optional provider-supported CIDR restrictions on both service ports |
| `owncast_ingress_enabled`, `owncast_hostname` | `false`, empty; public HTTP exposure is opt-in |
| `owncast_ingress_class`, `owncast_ingress_annotations` | Empty (controller default), `{}` |
| `owncast_tls_enabled`, `owncast_tls_secret_name` | `true`, resource name; applies when ingress is enabled |
| `owncast_cluster_issuer` | Empty; provide a name to use cert-manager, otherwise provision the TLS secret yourself |
| `owncast_onion_enabled`, `owncast_onion_name` | `false`, `http` |
HTTP and RTMP container/service ports remain fixed at 8080 and 1935; configure
external TCP routing separately if not using a LoadBalancer/NodePort service.
One Owncast process per data volume is supported; horizontal scaling is not.
Storage expansion depends on the StorageClass; never shrink an existing claim
or change its immutable StorageClass. Changing namespaces/names creates another
instance, not a data migration.
Disabling ingress or Tor stops managing it; it **does not delete a previously
created resource**. Remove stale resources manually when revoking public access.
The role uses only `state: present` and provides no destructive purge task.
## Security and operations
- No secrets are embedded, generated, or rotated. For a fresh instance, first
deploy privately (`owncast_service_type=ClusterIP`, ingress disabled) and use
Owncast's supported admin workflow to set unique administrator credentials
and stream key **before exposing it publicly**. Existing credentials remain
in the persistent database and are not altered by this role.
- The site playbooks intentionally preserve existing LoadBalancer behavior:
**unencrypted HTTP 8080 is exposed alongside RTMP 1935**, bypassing ingress
TLS on that port. Restrict access with provider/firewall rules or source
ranges, or use ClusterIP plus separately configured RTMP routing. RTMP itself
is unencrypted; use trusted networking/tunnels when confidentiality matters.
- TLS terminates at ingress. Use a trusted certificate/issuer; Kubernetes API
certificate verification is not disabled. Storage encryption, backups, RBAC,
NetworkPolicies, ingress admin restrictions, and renewal are operator duties.
- Pods receive no Kubernetes API token, cannot escalate privileges, drop Linux
capabilities, and use runtime-default seccomp. UID and data ownership are
intentionally not changed during migration.
- The old image version is retained for migration compatibility, not presented
as the latest release. Review and test image upgrades separately after a
backup; a digest-pinned image can be supplied via `owncast_image`.
## Tests (no cluster needed)
[tests/test.yml](tests/test.yml) renders templates with actual playbook vars,
checks migration identity/settings and friend isolation, exercises optional
fields, and checks invalid-input rejection. [tests/run.sh](tests/run.sh) also
syntax-checks both playbooks and tests friend preflight failures before any
cluster access.
Run `sh tests/run.sh` on a configured controller, or build
[tests/Dockerfile](tests/Dockerfile) with
`docker build -f tests/Dockerfile -t owncast-role-tests .` and run
`docker run --rm --network none --cap-drop ALL --security-opt no-new-privileges owncast-role-tests`.
The image runs tests as a non-root user and requires no kubeconfig or secrets.
## License
BSD-3-Clause; see [LICENSE](LICENSE).