Compare commits

...

57 Commits

Author SHA1 Message Date
Nikholas Pcenicni
aeffc7d6dd Remove Argo CD application configurations for Fluent Bit, Headlamp, Loki, kube-prometheus, and associated kustomization files from the noble bootstrap directory. This cleanup streamlines the project by eliminating unused resources and simplifies the deployment structure. 2026-04-01 02:14:49 -04:00
Nikholas Pcenicni
0f88a33216 Remove deprecated Argo CD application configurations for various components including cert-manager, Cilium, CSI snapshot controllers, kube-vip, and others. Update README.md to reflect the current state of leaf applications and clarify optional components. Adjust kustomization files to streamline resource management for bootstrap workloads. 2026-04-01 02:13:15 -04:00
Nikholas Pcenicni
bfb72cb519 Update Argo CD documentation and kustomization files to include additional applications and namespace resources. Enhance README.md with current leaf applications and clarify optional components. This improves deployment clarity and organization for bootstrap workloads. 2026-04-01 02:11:19 -04:00
Nikholas Pcenicni
51eb64dd9d Add applications to Argo CD kustomization.yaml for enhanced deployment 2026-04-01 02:05:10 -04:00
Nikholas Pcenicni
f259285f6e Enhance Argo CD integration by adding support for a bootstrap root application. Update group_vars/all.yml and role defaults to include noble_argocd_apply_bootstrap_root_application. Modify tasks to apply the bootstrap application conditionally. Revise documentation to clarify the GitOps workflow and the relationship between the core platform and optional applications. Remove outdated references and streamline the README for better user guidance. 2026-04-01 01:55:41 -04:00
Nikholas Pcenicni
c312ceeb56 Remove Eclipse Che application configurations and related documentation from the repository. This includes the deletion of application-checluster.yaml, application-devworkspace.yaml, application-operator.yaml, checluster.yaml, dwoc.yaml, kustomization.yaml, and README.md, streamlining the project by eliminating outdated resources. 2026-04-01 01:21:32 -04:00
Nikholas Pcenicni
c15bf4d708 Enhance Ansible playbooks and documentation for Debian and Proxmox management. Add new playbooks for Debian hardening, maintenance, SSH key rotation, and Proxmox cluster setup. Update README.md with quick start instructions for Debian and Proxmox operations. Modify group_vars to include Argo CD application settings, improving deployment flexibility and clarity. 2026-04-01 01:19:50 -04:00
Nikholas Pcenicni
89be30884e Update compose.yaml for Tracearr service to change the image tag from 'latest' to 'supervised' and remove unnecessary environment variables for DATABASE_URL and REDIS_URL. This streamlines the configuration and focuses on essential settings for deployment. 2026-03-30 22:53:47 -04:00
Nikholas Pcenicni
16948c62f9 Update compose.yaml for Tracearr service to include production environment variables and database configurations. This enhances deployment settings by specifying NODE_ENV, PORT, HOST, DATABASE_URL, REDIS_URL, JWT_SECRET, COOKIE_SECRET, and CORS_ORIGIN, improving overall service configuration and security. 2026-03-30 22:49:01 -04:00
Nikholas Pcenicni
3a6e5dff5b Update Ansible configuration to integrate SOPS for managing secrets. Enhance README.md with SOPS usage instructions and prerequisites. Remove External Secrets Operator references and related configurations from the bootstrap process, streamlining the deployment. Adjust playbooks and roles to apply SOPS-encrypted secrets automatically, improving security and clarity in secret management. 2026-03-30 22:42:52 -04:00
Nikholas Pcenicni
023ebfee5d Enhance Eclipse Che configuration in checluster.yaml by adding externalTLSConfig for secure workspace subdomains. This change ensures cert-manager can issue TLS certificates, preventing issues with unavailable servers when opening workspaces. 2026-03-29 02:03:57 -04:00
Nikholas Pcenicni
27fb4113eb Refactor DevWorkspaceOperatorConfig in dwoc.yaml to simplify configuration structure. This change removes the unnecessary spec.config nesting, aligning with the v1alpha1 API requirements and improving clarity for users configuring development workspaces. 2026-03-28 19:58:18 -04:00
Nikholas Pcenicni
4026591f0b Update README.md with troubleshooting steps for Eclipse Che and enhance kustomization.yaml to include DevWorkspaceOperatorConfig. This improves guidance for users facing deployment issues and ensures proper configuration for development workspace management. 2026-03-28 19:56:07 -04:00
Nikholas Pcenicni
8a740019ad Add Eclipse Che applications to kustomization.yaml for improved development workspace management. This update includes application-devworkspace, application-operator, and application-checluster resources, enhancing the deployment capabilities for the Noble cluster. 2026-03-28 19:53:01 -04:00
Nikholas Pcenicni
544f75b0ee Enhance documentation and configuration for Velero integration. Update README.md to clarify Velero's lack of web UI and usage instructions for CLI. Add CSI Volume Snapshot support in playbooks and roles, and include Velero service details in noble_landing_urls. Adjust kustomization.yaml to include VolumeSnapshotClass configuration, ensuring proper setup for backups. Improve overall clarity in related documentation. 2026-03-28 19:34:43 -04:00
Nikholas Pcenicni
33a10dc7e9 Add Velero configuration to .env.sample, README.md, and Ansible playbooks. Update group_vars to include noble_velero_install variable. Enhance documentation for optional Velero installation and S3 integration, improving clarity for backup and restore processes. 2026-03-28 18:39:22 -04:00
Nikholas Pcenicni
a4b9913b7e Update .env.sample and compose.yaml for Versity S3 Gateway to enhance WebUI and CORS configuration. Add comments clarifying the purpose of VGW_CORS_ALLOW_ORIGIN and correct usage of VGW_WEBUI_GATEWAYS, improving deployment instructions and user understanding. 2026-03-28 18:28:52 -04:00
Nikholas Pcenicni
11c62009a4 Update README.md, .env.sample, and compose.yaml for Versity S3 Gateway to clarify WebUI configuration. Enhance README with details on separate API and WebUI ports, and update .env.sample and compose.yaml to include WebUI settings for improved deployment instructions and usability. 2026-03-28 18:20:55 -04:00
Nikholas Pcenicni
03ed4e70a2 Enhance .env.sample and compose.yaml for Versity S3 Gateway by adding detailed comments on NFS metadata handling and sidecar mode. This improves documentation clarity for users configuring NFS mounts and metadata storage options. 2026-03-28 18:17:54 -04:00
Nikholas Pcenicni
7855b10982 Update compose.yaml to change volume paths for Versity S3 Gateway from named volumes to NFS mounts. This adjustment improves data persistence and accessibility by linking directly to the NFS directory structure. 2026-03-28 18:13:52 -04:00
Nikholas Pcenicni
079c11b20c Refactor Versity S3 Gateway configuration in README.md, .env.sample, and compose.yaml. Update README to clarify environment variable usage and adjust .env.sample for local setup instructions. Modify compose.yaml to utilize environment variable interpolation, ensuring proper credential handling and enhancing deployment security. 2026-03-28 17:56:24 -04:00
Nikholas Pcenicni
bf108a37e2 Update compose.yaml to include .env file for environment variable injection, enhancing security and usability for the Versity S3 Gateway deployment. This change ensures that necessary environment variables are accessible within the container, improving the overall configuration process. 2026-03-28 17:49:43 -04:00
Nikholas Pcenicni
97b56581ed Update README.md and .env.sample for Versity S3 Gateway configuration. Change path in README to reflect new directory structure and clarify environment variable usage for credentials. Modify .env.sample to include additional credential options and improve documentation for setting up the environment. Adjust compose.yaml to utilize pass-through environment variables, enhancing security and usability for deployment. 2026-03-28 17:46:08 -04:00
Nikholas Pcenicni
f154658d79 Add Versity S3 Gateway documentation to README.md, detailing configuration requirements and usage for shared object storage. This addition enhances clarity for users integrating S3-compatible APIs with POSIX directories. 2026-03-28 17:25:44 -04:00
Nikholas Pcenicni
90509bacc5 Update homepage values.yaml to replace external siteMonitor URLs with in-cluster service URLs for improved reliability. Enhance comments for clarity on service monitoring and Prometheus widget configurations. Adjust description for better accuracy regarding uptime checks and resource monitoring. 2026-03-28 17:13:57 -04:00
Nikholas Pcenicni
e4741ecd15 Enhance homepage values.yaml by adding support for RBAC, service account creation, and site monitoring for various services. Update widget configurations for Prometheus and introduce new widgets for datetime and Kubernetes resource monitoring. Adjust layout and styling settings for improved UI presentation. 2026-03-28 17:11:01 -04:00
Nikholas Pcenicni
f6647056be Add homepage entry to noble_landing_urls and update kustomization.yaml to include homepage resource 2026-03-28 17:07:06 -04:00
Nikholas Pcenicni
76eb7df18c Refactor noble cluster configurations to transition from the deprecated apps structure to a streamlined bootstrap approach. Update paths in various YAML files and README documentation to reflect the new organization under clusters/noble/bootstrap. This change enhances clarity and consistency across the deployment process, ensuring that all components are correctly referenced and documented for user guidance. 2026-03-28 17:03:15 -04:00
Nikholas Pcenicni
90fd8fb8a6 Refactor noble cluster configurations by removing deprecated Argo CD application management files and transitioning to a streamlined Ansible-driven installation approach. Update kustomization.yaml files to reflect the new structure, ensuring clarity on resource management. Introduce new namespaces and configurations for cert-manager, external-secrets, and logging components, enhancing the overall deployment process. Add detailed README.md documentation for each component to guide users through the setup and management of the noble lab environment. 2026-03-28 17:02:50 -04:00
Nikholas Pcenicni
41841abc84 Update Kyverno policies-values.yaml to enhance namespace exclusions for policy enforcement. Add detailed comments on the exclusion strategy and provide instructions for refreshing PolicyReports. This change aims to improve clarity and management of policy violations across various namespaces. 2026-03-28 16:56:13 -04:00
Nikholas Pcenicni
7a62489ad6 Enhance noble_landing_urls role by adding support for generating a Headlamp ServiceAccount token with a configurable duration. Update documentation to reflect changes in the markdown output for Headlamp sign-in. Modify fetch_credentials task to include token generation alongside existing credential fetching. These updates improve the usability and security of the Headlamp integration. 2026-03-28 16:38:47 -04:00
Nikholas Pcenicni
0e8eaa2f0d Update .gitignore to include generated noble-lab-ui-urls.md and enhance README.md with new role documentation. Refactor noble.yml to incorporate noble_landing_urls role for improved URL management. Add ingress configurations for alertmanager, prometheus, longhorn, and vault to support TLS termination via Traefik. Update network policies and values.yaml for vault to allow traffic from Traefik. These changes aim to streamline deployment and enhance service accessibility. 2026-03-28 16:32:21 -04:00
Nikholas Pcenicni
a48ac16c14 Update .gitignore to include .env file and enhance README.md with instructions for deploying secrets. Refactor noble.yml to improve Kubernetes health check handling and update templates for error reporting. Modify cert-manager and metallb tasks to apply secrets from .env and adjust timeout settings. Clarify Newt installation requirements in tasks. These changes aim to streamline deployment processes and improve documentation clarity. 2026-03-28 15:36:52 -04:00
Nikholas Pcenicni
46cedc965f Refactor Argo CD application management by removing noble-kyverno and noble-platform configurations, transitioning to Ansible-driven installations. Update documentation to clarify the optional nature of app-of-apps and the role of kustomization.yaml as an empty resource holder. Ensure users are informed about the need to delete stale Applications when migrating from previous configurations. 2026-03-28 15:17:54 -04:00
Nikholas Pcenicni
207cdca0cf Enhance Kyverno configuration in values.yaml by adding support for two replicas and enabling API priority and fairness. This update aims to improve resilience during rolling restarts and manage API traffic more effectively under load. 2026-03-28 02:55:47 -04:00
Nikholas Pcenicni
bf185b71a9 Enhance Kyverno's performance by increasing Kubernetes client QPS and burst limits in values.yaml to prevent rate limiting issues under load. This adjustment aims to improve the stability of the Kyverno health lease during high API/etcd usage. 2026-03-28 02:50:23 -04:00
Nikholas Pcenicni
fc985932fe Update cert-manager configurations to use DNS-01 challenge with Cloudflare for both production and staging ClusterIssuers. Modify README.md to reflect the new DNS-01 setup and provide instructions for creating the necessary Cloudflare API token secret. This change enhances certificate issuance reliability when using Cloudflare's proxy services. 2026-03-28 02:41:51 -04:00
Nikholas Pcenicni
ee7669c788 Update Argo CD application configurations for noble-platform to include sync wave annotations and enhance documentation. Remove redundant Helm chart definitions, clarify application roles in README.md, and implement retry logic for improved deployment resilience. 2026-03-28 02:29:23 -04:00
Nikholas Pcenicni
90cd34c34f Enable cert-manager for admission webhook TLS in kube-prometheus-stack values.yaml to prevent Kyverno pre-hook Job failures during Argo sync. Update noble-platform.yaml to adjust Helm chart sources and maintain proper order for Kyverno and kube-prometheus-stack, ensuring correct resource management and deployment flow. 2026-03-28 02:25:59 -04:00
Nikholas Pcenicni
1a3c8378d4 Update Argo CD application configuration to use HEAD as the target revision in both root-application.yaml and noble-platform.yaml, enhancing clarity in documentation regarding repository tracking. This change supports a more dynamic reference to the latest code in the repository. 2026-03-28 02:20:35 -04:00
Nikholas Pcenicni
05717c7e6a Refactor Argo CD application configuration to adopt a multi-source approach for noble-platform, removing Helm chart definitions from kustomization.yaml. Update noble-platform.yaml to directly reference Helm chart sources and clarify documentation in README.md and apps/README.md regarding the new structure and resource representation in the UI. 2026-03-28 02:18:02 -04:00
Nikholas Pcenicni
0dd642f0c5 Add server-side apply option for large CRDs in noble-platform.yaml to avoid kubectl annotation limit 2026-03-28 02:15:32 -04:00
Nikholas Pcenicni
0a6c9976da Refactor Argo CD application configuration to utilize a single source for the noble-platform, consolidating Helm chart management within kustomization.yaml. Update values.yaml to enable Helm inflation and revise documentation in README.md and apps/README.md to clarify the new structure and improve UI representation of resources. 2026-03-28 02:13:36 -04:00
Nikholas Pcenicni
c5319a5436 Refactor Argo CD application configuration to utilize plain Kustomize for YAML resources and streamline Helm chart management. Update kustomization.yaml to remove Helm chart definitions, shifting to a multi-source approach in noble-platform.yaml. Revise documentation in README.md and apps/README.md to clarify the new structure and eliminate the need for Helm inflation in values.yaml. 2026-03-28 02:10:44 -04:00
Nikholas Pcenicni
c148454e91 Refine Argo CD documentation and configuration. Update README.md to clarify the relationship between noble-root and child applications, and enhance instructions for syncing workloads. Modify root-application.yaml to specify the use of kustomization.yaml for cluster workloads. Adjust values.yaml to enable Helm inflation for Kustomize charts. Update apps/README.md to streamline application management and clarify deployment processes. 2026-03-28 02:05:09 -04:00
Nikholas Pcenicni
445a1ac211 Update Headlamp and Vault documentation; enhance RBAC configurations in Argo CD. Modify Headlamp README to clarify sessionTTL handling and ServiceAccount permissions. Add Cilium network policy instructions to Vault README. Update Argo CD values.yaml for default RBAC settings, ensuring local admin retains full access while new users start with read-only permissions. Reflect these changes in CLUSTER-BUILD.md. 2026-03-28 02:02:17 -04:00
Nikholas Pcenicni
906c24b1d5 Remove obsolete Ansible configuration files and playbooks for Proxmox VM management, including ansible.cfg, README.md, inventory samples, and various playbooks. This cleanup eliminates unused roles and tasks, streamlining the project structure. 2026-03-28 01:43:25 -04:00
Nikholas Pcenicni
d5f38bd766 Update README.md and CLUSTER-BUILD.md to enhance documentation for Vault Kubernetes auth and ClusterSecretStore integration. Add one-shot configuration instructions for Kubernetes auth in README.md, and update CLUSTER-BUILD.md to reflect the current state of the Talos cluster, including new components like Headlamp and Renovate, along with their deployment details and next steps. 2026-03-28 01:41:52 -04:00
Nikholas Pcenicni
a65b553252 Stop tracking talos kubeconfig; remove Authentik token from git; add Newt kubeseal example
Remove committed talos/kubeconfig (cluster admin credentials). Ignore talos/kubeconfig at repo root.
Replace hardcoded LDAP outpost token with AUTHENTIK_LDAP_OUTPOST_TOKEN from .env.
Document Sealed Secrets workflow for Newt (kubeseal script + README updates). Clarify Talos secrets use talsecret/SOPS, not Sealed Secrets.

Made-with: Cursor
2026-03-28 01:19:58 -04:00
Nikholas Pcenicni
a5e624f542 Update CLUSTER-BUILD.md to reflect the current state of the Talos cluster, detailing progress through Phase D (observability) and advancements in Phase E (secrets). Include updates on Sealed Secrets, External Secrets Operator, and Vault configurations, along with deployment instructions and next steps for Kubernetes auth and ClusterSecretStore integration. Mark relevant tasks as completed and outline remaining objectives for future phases. 2026-03-28 01:17:22 -04:00
Nikholas Pcenicni
d2b52f3518 Enhance CLUSTER-BUILD.md with updates on the Talos cluster's current state, focusing on Phase D (observability) and next steps for Phase E (secrets). Clarify kube-prometheus-stack and Loki configurations, and update deployment instructions for Grafana and Fluent Bit integration. Mark tasks related to observability as completed and outline future phases for improved clarity. 2026-03-28 00:59:08 -04:00
Nikholas Pcenicni
2b4f568632 Update kube-prometheus-stack values.yaml to clarify Loki datasource configuration and enhance observability documentation in CLUSTER-BUILD.md. Include deployment instructions for Loki and Fluent Bit, and mark tasks related to Grafana integration as completed. 2026-03-28 00:56:49 -04:00
Nikholas Pcenicni
7caba0d90c Update CLUSTER-BUILD.md to include kube-prometheus-stack Helm chart details, enhance observability phase with Grafana ingress configuration, and clarify deployment instructions for monitoring components. Mark tasks as completed for kube-prometheus-stack installation and PVC binding on Longhorn. 2026-03-28 00:28:54 -04:00
Nikholas Pcenicni
fd4afef992 Update Argo CD README and root-application.yaml to clarify prerequisites, enhance Ingress configuration with Traefik and cert-manager, and specify finalizer guidance for resource deletion. Adjust instructions for accessing the Argo CD UI and managing applications. 2026-03-27 23:57:25 -04:00
Nikholas Pcenicni
092a6febe4 Update Argo CD values.yaml to configure Ingress with Traefik, enable TLS with cert-manager, and set server to insecure mode. Adjust domain settings and service type to ClusterIP for improved deployment configuration. 2026-03-27 23:47:40 -04:00
Nikholas Pcenicni
2a64f40f93 Enable pre-upgrade job for Longhorn in values.yaml, update MetalLB README for clarity on LoadBalancer IP assignment, and enhance Talos configuration with node IP validation for VIPs. Update cluster build documentation to reflect new application versions and configurations. 2026-03-27 23:45:00 -04:00
Nikholas Pcenicni
d2c53fc553 Remove deprecated Argo CD application configurations and related files for noble cluster, including root-application.yaml, kustomization.yaml, and individual application manifests for argocd, cilium, longhorn, kube-vip, and monitoring components. Update kube-vip daemonset.yaml to enhance deployment strategy and environment variables for improved configuration. 2026-03-27 23:02:17 -04:00
205 changed files with 5786 additions and 1793 deletions

19
.env.sample Normal file
View File

@@ -0,0 +1,19 @@
# Copy to **.env** in this repository root (`.env` is gitignored).
# Ansible **noble_cert_manager** role sources `.env` after cert-manager Helm install and creates
# **cert-manager/cloudflare-dns-api-token** when **CLOUDFLARE_DNS_API_TOKEN** is set.
#
# Cloudflare: Zone → DNS → Edit + Zone → Read for **pcenicni.dev** (see clusters/noble/bootstrap/cert-manager/README.md).
CLOUDFLARE_DNS_API_TOKEN=
# --- Optional: other deploy-time values (documented for manual use or future automation) ---
# Pangolin / Newt — with **noble_newt_install=true**, Ansible creates **newt/newt-pangolin-auth** when all are set (see clusters/noble/bootstrap/newt/README.md).
PANGOLIN_ENDPOINT=
NEWT_ID=
NEWT_SECRET=
# Velero — when **noble_velero_install=true**, set bucket + S3 API URL and credentials (see clusters/noble/bootstrap/velero/README.md).
NOBLE_VELERO_S3_BUCKET=
NOBLE_VELERO_S3_URL=
NOBLE_VELERO_AWS_ACCESS_KEY_ID=
NOBLE_VELERO_AWS_SECRET_ACCESS_KEY=

7
.gitignore vendored
View File

@@ -1,6 +1,11 @@
ansible/inventory/hosts.ini ansible/inventory/hosts.ini
# Talos generated # Talos generated
talos/out/ talos/out/
talos/kubeconfig
# Local secrets # Local secrets
age-key.txt age-key.txt
.env
# Generated by ansible noble_landing_urls
ansible/output/noble-lab-ui-urls.md

7
.sops.yaml Normal file
View File

@@ -0,0 +1,7 @@
# Mozilla SOPS — encrypt/decrypt Kubernetes Secret manifests under clusters/noble/secrets/
# Generate a key: age-keygen -o age-key.txt (age-key.txt is gitignored)
# Add the printed public key below (one recipient per line is supported).
creation_rules:
- path_regex: clusters/noble/secrets/.*\.yaml$
age: >-
age1juym5p3ez3dkt0dxlznydgfgqvaujfnyk9hpdsssf50hsxeh3p4sjpf3gn

View File

@@ -180,6 +180,12 @@ Shared services used across multiple applications.
**Configuration:** Requires Pangolin endpoint URL, Newt ID, and Newt secret. **Configuration:** Requires Pangolin endpoint URL, Newt ID, and Newt secret.
### versitygw/ (`komodo/s3/versitygw/`)
- **[Versity S3 Gateway](https://github.com/versity/versitygw)** — S3 API on port **10000** by default; optional **WebUI** on **8080** (not the same listener—enable `VERSITYGW_WEBUI_PORT` / `VGW_WEBUI_GATEWAYS` per `.env.sample`). Behind **Pangolin**, expose the API and WebUI separately (or you will see **404** browsing the API URL).
**Configuration:** Set either `ROOT_ACCESS_KEY` / `ROOT_SECRET_KEY` or `ROOT_ACCESS_KEY_ID` / `ROOT_SECRET_ACCESS_KEY`. Optional `VERSITYGW_PORT`. Compose uses `${VAR}` interpolation so credentials work with Komodos `docker compose --env-file <run_directory>/.env` (avoid `env_file:` in the service when `run_directory` is not the same folder as `compose.yaml`, or the written `.env` will not be found).
--- ---
## 📊 Monitoring (`komodo/monitor/`) ## 📊 Monitoring (`komodo/monitor/`)

1
ansible/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.ansible-tmp/

View File

@@ -1,119 +1,160 @@
# Proxmox VM Management Suite # Ansible — noble cluster
A comprehensive Ansible automation suite for managing Proxmox Virtual Machines. This suite allows you to easily create Cloud-Init templates, provision new VMs, manage backups, and decommission resources across multiple Proxmox hosts. Automates [`talos/CLUSTER-BUILD.md`](../talos/CLUSTER-BUILD.md): optional **Talos Phase A** (genconfig → apply → bootstrap → kubeconfig), then **Phase B+** (CNI → add-ons → ingress → Argo CD → Kyverno → observability, etc.). **Argo CD** does not reconcile core charts — optional GitOps starts from an empty [`clusters/noble/apps/kustomization.yaml`](../clusters/noble/apps/kustomization.yaml).
## Features ## Order of operations
- **Template Management**: 1. **From `talos/`:** `talhelper gensecret` / `talsecret` as in [`talos/README.md`](../talos/README.md) §1 (if not already done).
- Automatically download Cloud Images (Ubuntu, Debian, etc.). 2. **Talos Phase A (automated):** run [`playbooks/talos_phase_a.yml`](playbooks/talos_phase_a.yml) **or** the full pipeline [`playbooks/deploy.yml`](playbooks/deploy.yml). This runs **`talhelper genconfig -o out`**, **`talosctl apply-config`** on each node, **`talosctl bootstrap`**, and **`talosctl kubeconfig`** → **`talos/kubeconfig`**.
- Pre-configured with Cloud-Init (SSH keys, IP Config). 3. **Platform stack:** [`playbooks/noble.yml`](playbooks/noble.yml) (included at the end of **`deploy.yml`**).
- Support for selecting images from a curated list or custom URLs.
- **VM Provisioning**:
- Clone from templates (Full or Linked clones).
- Auto-start option.
- **Lifecycle Management**:
- Backup VMs (Snapshot mode).
- Delete/Purge VMs.
- **Security**:
- **Automatic SSH Key Injection**: Automatically adds a defined Admin SSH key to every template.
- Support for injecting additional SSH keys per deployment.
## Setup Your workstation must be able to reach **node IPs on the lab LAN** (Talos API **:50000** for `talosctl`, Kubernetes **:6443** for `kubectl` / Helm). If `kubectl` cannot reach the VIP (`192.168.50.230`), use `-e 'noble_k8s_api_server_override=https://<control-plane-ip>:6443'` on **`noble.yml`** (see `group_vars/all.yml`).
### 1. Requirements **One-shot full deploy** (after nodes are booted and reachable):
Install the required Ansible collections:
```bash
ansible-galaxy install -r requirements.yml
```
### 2. Configuration
Edit `roles/proxmox_vm/defaults/main.yml` to set your global defaults, specifically the **Admin SSH Key**.
**Important Variable to Change:**
```yaml
# ansible/roles/proxmox_vm/defaults/main.yml
admin_ssh_key: "ssh-ed25519 AAAAC3... your-actual-public-key"
```
## Usage
The main entry point is the playbook `playbooks/manage_vm.yml`. You control the behavior using the `proxmox_action` variable.
### 1. Create a Cloud-Init Template
You can create a template by selecting a predefined alias (e.g., `ubuntu-22.04`) or providing a custom URL.
**Option A: Select from List (Default)**
Current aliases: `ubuntu-22.04`, `ubuntu-24.04`, `debian-12`.
```bash ```bash
# Create Ubuntu 22.04 Template (ID: 9000) cd ansible
ansible-playbook playbooks/manage_vm.yml \ ansible-playbook playbooks/deploy.yml
-e "proxmox_action=create_template vmid=9000 template_name=ubuntu-22-template image_alias=ubuntu-22.04"
``` ```
**Option B: Custom URL** ## Deploy secrets (`.env`)
```bash
ansible-playbook playbooks/manage_vm.yml \
-e "proxmox_action=create_template \
vmid=9001 \
template_name=custom-linux \
image_source_type=url \
custom_image_url='https://example.com/image.qcow2'"
```
### 2. Create a VM from Template Copy **`.env.sample`** to **`.env`** at the repository root (`.env` is gitignored). At minimum set **`CLOUDFLARE_DNS_API_TOKEN`** for cert-manager DNS-01. The **cert-manager** role applies it automatically during **`noble.yml`**. See **`.env.sample`** for optional placeholders (e.g. Newt/Pangolin).
Clone a valid template to a new VM. ## Prerequisites
- `talosctl` (matches node Talos version), `talhelper`, `helm`, `kubectl`.
- **SOPS secrets:** `sops` and `age` on the control host if you use **`clusters/noble/secrets/`** with **`age-key.txt`** (see **`clusters/noble/secrets/README.md`**).
- **Phase A:** same LAN/VPN as nodes so **Talos :50000** and **Kubernetes :6443** are reachable (see [`talos/README.md`](../talos/README.md) §3).
- **noble.yml:** bootstrapped cluster and **`talos/kubeconfig`** (or `KUBECONFIG`).
## Playbooks
| Playbook | Purpose |
|----------|---------|
| [`playbooks/deploy.yml`](playbooks/deploy.yml) | **Talos Phase A** then **`noble.yml`** (full automation). |
| [`playbooks/talos_phase_a.yml`](playbooks/talos_phase_a.yml) | `genconfig``apply-config``bootstrap``kubeconfig` only. |
| [`playbooks/noble.yml`](playbooks/noble.yml) | Helm + `kubectl` platform (after Phase A). |
| [`playbooks/post_deploy.yml`](playbooks/post_deploy.yml) | SOPS reminders and optional Argo root Application note. |
| [`playbooks/talos_bootstrap.yml`](playbooks/talos_bootstrap.yml) | **`talhelper genconfig` only** (legacy shortcut; prefer **`talos_phase_a.yml`**). |
| [`playbooks/debian_harden.yml`](playbooks/debian_harden.yml) | Baseline hardening for Debian servers (SSH/sysctl/fail2ban/unattended-upgrades). |
| [`playbooks/debian_maintenance.yml`](playbooks/debian_maintenance.yml) | Debian maintenance run (apt upgrades, autoremove/autoclean, reboot when required). |
| [`playbooks/debian_rotate_ssh_keys.yml`](playbooks/debian_rotate_ssh_keys.yml) | Rotate managed users' `authorized_keys`. |
| [`playbooks/debian_ops.yml`](playbooks/debian_ops.yml) | Convenience pipeline: harden then maintenance for Debian servers. |
| [`playbooks/proxmox_prepare.yml`](playbooks/proxmox_prepare.yml) | Configure Proxmox community repos and disable no-subscription UI warning. |
| [`playbooks/proxmox_upgrade.yml`](playbooks/proxmox_upgrade.yml) | Proxmox maintenance run (apt dist-upgrade, cleanup, reboot when required). |
| [`playbooks/proxmox_cluster.yml`](playbooks/proxmox_cluster.yml) | Create a Proxmox cluster on the master and join additional hosts. |
| [`playbooks/proxmox_ops.yml`](playbooks/proxmox_ops.yml) | Convenience pipeline: prepare, upgrade, then cluster Proxmox hosts. |
```bash ```bash
ansible-playbook playbooks/manage_vm.yml \ cd ansible
-e "proxmox_action=create_vm \ export KUBECONFIG=/absolute/path/to/home-server/talos/kubeconfig
vmid=9000 \
new_vmid=105 \ # noble.yml only — if VIP is unreachable from this host:
new_vm_name=web-server-01" # ansible-playbook playbooks/noble.yml -e 'noble_k8s_api_server_override=https://192.168.50.20:6443'
ansible-playbook playbooks/noble.yml
ansible-playbook playbooks/post_deploy.yml
``` ```
### 3. Backup a VM ### Talos Phase A variables (role `talos_phase_a` defaults)
Create a snapshot backup of a specific VM. Override with `-e` when needed, e.g. **`-e noble_talos_skip_bootstrap=true`** if etcd is already initialized.
| Variable | Default | Meaning |
|----------|---------|---------|
| `noble_talos_genconfig` | `true` | Run **`talhelper genconfig -o out`** first. |
| `noble_talos_apply_mode` | `auto` | **`auto`** — **`talosctl apply-config --dry-run`** on the first node picks maintenance (**`--insecure`**) vs joined (**`TALOSCONFIG`**). **`insecure`** / **`secure`** force talos/README §2 A or B. |
| `noble_talos_skip_bootstrap` | `false` | Skip **`talosctl bootstrap`**. If etcd is **already** initialized, bootstrap is treated as a no-op (same as **`talosctl`** “etcd data directory is not empty”). |
| `noble_talos_apid_wait_delay` / `noble_talos_apid_wait_timeout` | `20` / `900` | Seconds to wait for **apid :50000** on the bootstrap node after **apply-config** (nodes reboot). Increase if bootstrap hits **connection refused** to `:50000`. |
| `noble_talos_nodes` | neon/argon/krypton/helium | IP + **`out/*.yaml`** filename — align with **`talos/talconfig.yaml`**. |
### Tags (partial runs)
```bash ```bash
ansible-playbook playbooks/manage_vm.yml \ ansible-playbook playbooks/noble.yml --tags cilium,metallb
-e "proxmox_action=backup_vm vmid=105" ansible-playbook playbooks/noble.yml --skip-tags newt
ansible-playbook playbooks/noble.yml --tags velero -e noble_velero_install=true -e noble_velero_s3_bucket=... -e noble_velero_s3_url=...
``` ```
### 4. Delete a VM ### Variables — `group_vars/all.yml` and role defaults
Stop and purge a VM. - **`group_vars/all.yml`:** **`noble_newt_install`**, **`noble_velero_install`**, **`noble_cert_manager_require_cloudflare_secret`**, **`noble_argocd_apply_root_application`**, **`noble_argocd_apply_bootstrap_root_application`**, **`noble_k8s_api_server_override`**, **`noble_k8s_api_server_auto_fallback`**, **`noble_k8s_api_server_fallback`**, **`noble_skip_k8s_health_check`**
- **`roles/noble_platform/defaults/main.yml`:** **`noble_apply_sops_secrets`**, **`noble_sops_age_key_file`** (SOPS secrets under **`clusters/noble/secrets/`**)
## Roles
| Role | Contents |
|------|----------|
| `talos_phase_a` | Talos genconfig, apply-config, bootstrap, kubeconfig |
| `helm_repos` | `helm repo add` / `update` |
| `noble_*` | Cilium, CSI Volume Snapshot CRDs + controller, metrics-server, Longhorn, MetalLB (20m Helm wait), kube-vip, Traefik, cert-manager, Newt, Argo CD, Kyverno, platform stack, Velero (optional) |
| `noble_landing_urls` | Writes **`ansible/output/noble-lab-ui-urls.md`** — URLs, service names, and (optional) Argo/Grafana passwords from Secrets |
| `noble_post_deploy` | Post-install reminders |
| `talos_bootstrap` | Genconfig-only (used by older playbook) |
| `debian_baseline_hardening` | Baseline Debian hardening (SSH policy, sysctl profile, fail2ban, unattended upgrades) |
| `debian_maintenance` | Routine Debian maintenance tasks (updates, cleanup, reboot-on-required) |
| `debian_ssh_key_rotation` | Declarative `authorized_keys` rotation for server users |
| `proxmox_baseline` | Proxmox repo prep (community repos) and no-subscription warning suppression |
| `proxmox_maintenance` | Proxmox package maintenance (dist-upgrade, cleanup, reboot-on-required) |
| `proxmox_cluster` | Proxmox cluster bootstrap/join automation using `pvecm` |
## Debian server ops quick start
These playbooks are separate from the Talos/noble flow and target hosts in `debian_servers`.
1. Copy `inventory/debian.example.yml` to `inventory/debian.yml` and update hosts/users.
2. Update `group_vars/debian_servers.yml` with your allowed SSH users and real public keys.
3. Run with the Debian inventory:
```bash ```bash
ansible-playbook playbooks/manage_vm.yml \ cd ansible
-e "proxmox_action=delete_vm vmid=105" ansible-playbook -i inventory/debian.yml playbooks/debian_harden.yml
ansible-playbook -i inventory/debian.yml playbooks/debian_rotate_ssh_keys.yml
ansible-playbook -i inventory/debian.yml playbooks/debian_maintenance.yml
``` ```
## Advanced Usage Or run the combined maintenance pipeline:
### Handling Multiple Hosts
You can target a specific Proxmox node using the `target_host` variable.
```bash ```bash
ansible-playbook playbooks/manage_vm.yml -e "proxmox_action=create_vm ... target_host=mercury" cd ansible
ansible-playbook -i inventory/debian.yml playbooks/debian_ops.yml
``` ```
### Injecting Additional SSH Keys ## Proxmox host + cluster quick start
You can add extra SSH keys for a specific run (or add them to the defaults file).
These playbooks are separate from the Talos/noble flow and target hosts in `proxmox_hosts`.
1. Copy `inventory/proxmox.example.yml` to `inventory/proxmox.yml` and update hosts/users.
2. Update `group_vars/proxmox_hosts.yml` with your cluster name (`proxmox_cluster_name`), chosen cluster master, and root public key file paths to install.
3. First run (no SSH keys yet): use `--ask-pass` **or** set `ansible_password` (prefer Ansible Vault). Keep `ansible_ssh_common_args: "-o StrictHostKeyChecking=accept-new"` in inventory for first-contact hosts.
4. Run prepare first to install your public keys on each host, then continue:
```bash ```bash
ansible-playbook playbooks/manage_vm.yml \ cd ansible
-e "proxmox_action=create_template ... additional_ssh_keys=['ssh-rsa AAAAB3... key1', 'ssh-ed25519 AAAA... key2']" ansible-playbook -i inventory/proxmox.yml playbooks/proxmox_prepare.yml --ask-pass
ansible-playbook -i inventory/proxmox.yml playbooks/proxmox_upgrade.yml
ansible-playbook -i inventory/proxmox.yml playbooks/proxmox_cluster.yml
``` ```
## Directory Structure After `proxmox_prepare.yml` finishes, SSH key auth should work for root (keys from `proxmox_root_authorized_key_files`), so `--ask-pass` is usually no longer needed.
- `roles/proxmox_vm/`: Core logic role. If `pvecm add` still prompts for the master root password during join, set `proxmox_cluster_master_root_password` (prefer Vault) to run join non-interactively.
- `defaults/main.yml`: Configuration variables (Images, Keys, Defaults).
- `tasks/`: Action modules (`create_template.yml`, `create_vm.yml`, etc.). Changing `proxmox_cluster_name` only affects new cluster creation; it does not rename an already-created cluster.
- `inventory/`: Host definitions.
Or run the full Proxmox pipeline:
```bash
cd ansible
ansible-playbook -i inventory/proxmox.yml playbooks/proxmox_ops.yml
```
## Migrating from Argo-managed `noble-platform`
```bash
kubectl delete application -n argocd noble-platform noble-kyverno noble-kyverno-policies --ignore-not-found
kubectl apply -f clusters/noble/bootstrap/argocd/root-application.yaml
```
Then run `playbooks/noble.yml` so Helm state matches git values.

View File

@@ -1,6 +1,10 @@
[defaults] [defaults]
inventory = inventory/hosts.ini inventory = inventory/localhost.yml
host_key_checking = False
retry_files_enabled = False
interpreter_python = auto_silent
roles_path = roles roles_path = roles
retry_files_enabled = False
stdout_callback = default
callback_result_format = yaml
local_tmp = .ansible-tmp
[privilege_escalation]
become = False

View File

@@ -0,0 +1,28 @@
---
# noble_repo_root / noble_kubeconfig are set in playbooks (use **playbook_dir** magic var).
# When kubeconfig points at the API VIP but this workstation cannot reach the lab LAN (VPN off, etc.),
# set a reachable control-plane URL — same as: kubectl config set-cluster noble --server=https://<cp-ip>:6443
# Example: ansible-playbook playbooks/noble.yml -e 'noble_k8s_api_server_override=https://192.168.50.20:6443'
noble_k8s_api_server_override: ""
# When /healthz fails with **network unreachable** to the VIP and **override** is empty, retry using this URL (neon).
noble_k8s_api_server_auto_fallback: true
noble_k8s_api_server_fallback: "https://192.168.50.20:6443"
# Only if you must skip the kubectl /healthz preflight (not recommended).
noble_skip_k8s_health_check: false
# Pangolin / Newt — set true only after newt-pangolin-auth Secret exists (SOPS: clusters/noble/secrets/ or imperative — see clusters/noble/bootstrap/newt/README.md)
noble_newt_install: false
# cert-manager needs Secret cloudflare-dns-api-token in cert-manager namespace before ClusterIssuers work
noble_cert_manager_require_cloudflare_secret: true
# Velero — set **noble_velero_install: true** plus S3 bucket/URL (and credentials — see clusters/noble/bootstrap/velero/README.md)
noble_velero_install: false
# Argo CD — apply app-of-apps root Application (clusters/noble/bootstrap/argocd/root-application.yaml). Set false to skip.
noble_argocd_apply_root_application: true
# Bootstrap kustomize in Argo (**noble-bootstrap-root** → **clusters/noble/bootstrap**). Applied with manual sync; enable automation after **noble.yml** (see **clusters/noble/bootstrap/argocd/README.md** §5).
noble_argocd_apply_bootstrap_root_application: true

View File

@@ -0,0 +1,12 @@
---
# Hardened SSH settings
debian_baseline_ssh_allow_users:
- admin
# Example key rotation entries. Replace with your real users and keys.
debian_ssh_rotation_users:
- name: admin
home: /home/admin
state: present
keys:
- "ssh-ed25519 AAAAEXAMPLE_REPLACE_ME admin@workstation"

View File

@@ -0,0 +1,37 @@
---
# Proxmox repositories
proxmox_repo_debian_codename: trixie
proxmox_repo_disable_enterprise: true
proxmox_repo_disable_ceph_enterprise: true
proxmox_repo_enable_pve_no_subscription: true
proxmox_repo_enable_ceph_no_subscription: true
# Suppress "No valid subscription" warning in UI
proxmox_no_subscription_notice_disable: true
# Public keys to install for root on each Proxmox host.
proxmox_root_authorized_key_files:
- "{{ lookup('env', 'HOME') }}/.ssh/id_ed25519.pub"
- "{{ lookup('env', 'HOME') }}/.ssh/ansible.pub"
# Package upgrade/reboot policy
proxmox_upgrade_apt_cache_valid_time: 3600
proxmox_upgrade_autoremove: true
proxmox_upgrade_autoclean: true
proxmox_upgrade_reboot_if_required: true
proxmox_upgrade_reboot_timeout: 1800
# Cluster settings
proxmox_cluster_enabled: true
proxmox_cluster_name: atomic-hub
# Bootstrap host name from inventory (first host by default if empty)
proxmox_cluster_master: ""
# Optional explicit IP/FQDN for joining; leave empty to use ansible_host of master
proxmox_cluster_master_ip: ""
proxmox_cluster_force: false
# Optional: use only for first cluster joins when inter-node SSH trust is not established.
# Prefer storing with Ansible Vault if you set this.
proxmox_cluster_master_root_password: "Hemroid8"

View File

@@ -0,0 +1,11 @@
---
all:
children:
debian_servers:
hosts:
debian-01:
ansible_host: 192.168.50.101
ansible_user: admin
debian-02:
ansible_host: 192.168.50.102
ansible_user: admin

View File

@@ -1,14 +0,0 @@
[proxmox]
# Replace pve1 with your proxmox node hostname or IP
mercury ansible_host=192.168.50.100 ansible_user=root
[proxmox:vars]
# If using password auth (ssh key recommended though):
# ansible_ssh_pass=yourpassword
# Connection variables for the proxmox modules (api)
proxmox_api_user=root@pam
proxmox_api_password=CHANGE_ME
proxmox_api_host=192.168.50.100
# proxmox_api_token_id=
# proxmox_api_token_secret=

View File

@@ -0,0 +1,6 @@
---
all:
hosts:
localhost:
ansible_connection: local
ansible_python_interpreter: "{{ ansible_playbook_python }}"

View File

@@ -0,0 +1,24 @@
---
all:
children:
proxmox_hosts:
vars:
ansible_ssh_common_args: "-o StrictHostKeyChecking=accept-new"
hosts:
helium:
ansible_host: 192.168.1.100
ansible_user: root
# First run without SSH keys:
# ansible_password: "{{ vault_proxmox_root_password }}"
neon:
ansible_host: 192.168.1.90
ansible_user: root
# ansible_password: "{{ vault_proxmox_root_password }}"
argon:
ansible_host: 192.168.1.80
ansible_user: root
# ansible_password: "{{ vault_proxmox_root_password }}"
krypton:
ansible_host: 192.168.1.70
ansible_user: root
# ansible_password: "{{ vault_proxmox_root_password }}"

View File

@@ -0,0 +1,24 @@
---
all:
children:
proxmox_hosts:
vars:
ansible_ssh_common_args: "-o StrictHostKeyChecking=accept-new"
hosts:
helium:
ansible_host: 192.168.1.100
ansible_user: root
# First run without SSH keys:
# ansible_password: "{{ vault_proxmox_root_password }}"
neon:
ansible_host: 192.168.1.90
ansible_user: root
# ansible_password: "{{ vault_proxmox_root_password }}"
argon:
ansible_host: 192.168.1.80
ansible_user: root
# ansible_password: "{{ vault_proxmox_root_password }}"
krypton:
ansible_host: 192.168.1.70
ansible_user: root
# ansible_password: "{{ vault_proxmox_root_password }}"

View File

@@ -1,16 +0,0 @@
---
- name: Create Ubuntu Cloud-Init Template
hosts: proxmox
become: yes
vars:
template_id: 9000
template_name: ubuntu-2204-cloud
# Override defaults if needed
image_alias: ubuntu-22.04
storage_pool: local-lvm
tasks:
- name: Run Proxmox Template Manage Role
include_role:
name: proxmox_template_manage

View File

@@ -0,0 +1,8 @@
---
- name: Debian server baseline hardening
hosts: debian_servers
become: true
gather_facts: true
roles:
- role: debian_baseline_hardening
tags: [hardening, baseline]

View File

@@ -0,0 +1,8 @@
---
- name: Debian maintenance (updates + reboot handling)
hosts: debian_servers
become: true
gather_facts: true
roles:
- role: debian_maintenance
tags: [maintenance, updates]

View File

@@ -0,0 +1,3 @@
---
- import_playbook: debian_harden.yml
- import_playbook: debian_maintenance.yml

View File

@@ -0,0 +1,8 @@
---
- name: Debian SSH key rotation
hosts: debian_servers
become: true
gather_facts: false
roles:
- role: debian_ssh_key_rotation
tags: [ssh, ssh_keys, rotation]

View File

@@ -0,0 +1,5 @@
---
# Full bring-up: Talos Phase A then platform stack.
# Run from **ansible/**: ansible-playbook playbooks/deploy.yml
- import_playbook: talos_phase_a.yml
- import_playbook: noble.yml

View File

@@ -1,33 +0,0 @@
---
- name: Hello World Provisioning
hosts: localhost # Run API calls from control node
gather_facts: no
vars_files:
- "../inventory/hosts.ini" # Load connection details if needed manually, OR rely on inventory
vars:
# Target Proxmox Details (override from inventory/extra vars)
proxmox_api_host: "192.168.50.100"
proxmox_api_user: "root@pam"
proxmox_api_password: "Hemroid8" # Consider moving to Vault!
proxmox_node: "mercury"
# VM Spec
vmid: 101
vm_name: "hello-world-vm"
template_name: "ubuntu-2204-cloud"
ci_user: "ubuntu"
# Replace with your actual public key or pass via -e "ssh_key=..."
ssh_keys:
- "{{ ssh_key | default('ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...') }}"
tasks:
- name: Run Proxmox Provision Role
include_role:
name: proxmox_provision
vars:
vmid: "{{ vmid }}"
vm_name: "{{ vm_name }}"
template_name: "{{ template_name }}"
ci_user: "{{ ci_user }}"
ssh_keys: "{{ ssh_keys }}"

View File

@@ -1,6 +0,0 @@
---
- name: Manage Proxmox VMs
hosts: "{{ target_host | default('proxmox') }}"
become: yes
roles:
- proxmox_vm

232
ansible/playbooks/noble.yml Normal file
View File

@@ -0,0 +1,232 @@
---
# Full platform install — **after** Talos bootstrap (`talosctl bootstrap` + working kubeconfig).
# Do not run until `kubectl get --raw /healthz` returns ok (see talos/README.md §3, CLUSTER-BUILD Phase A).
# Run from repo **ansible/** directory: ansible-playbook playbooks/noble.yml
#
# Tags: repos, cilium, csi_snapshot, metrics, longhorn, metallb, kube_vip, traefik, cert_manager, newt,
# argocd, kyverno, kyverno_policies, platform, velero, all (default)
- name: Noble cluster — platform stack (Ansible-managed)
hosts: localhost
connection: local
gather_facts: false
vars:
noble_repo_root: "{{ playbook_dir | dirname | dirname }}"
noble_kubeconfig: "{{ lookup('env', 'KUBECONFIG') | default(noble_repo_root + '/talos/kubeconfig', true) }}"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
pre_tasks:
# Helm/kubectl use $KUBECONFIG; a missing file yields "connection refused" to localhost:8080.
- name: Stat kubeconfig path from KUBECONFIG or default
ansible.builtin.stat:
path: "{{ noble_kubeconfig }}"
register: noble_kubeconfig_stat
tags: [always]
- name: Fall back to repo talos/kubeconfig when $KUBECONFIG is unset or not a file
ansible.builtin.set_fact:
noble_kubeconfig: "{{ noble_repo_root }}/talos/kubeconfig"
when: not noble_kubeconfig_stat.stat.exists | default(false)
tags: [always]
- name: Stat kubeconfig after fallback
ansible.builtin.stat:
path: "{{ noble_kubeconfig }}"
register: noble_kubeconfig_stat2
tags: [always]
- name: Require a real kubeconfig file
ansible.builtin.assert:
that:
- noble_kubeconfig_stat2.stat.exists | default(false)
- noble_kubeconfig_stat2.stat.isreg | default(false)
fail_msg: >-
No kubeconfig file at {{ noble_kubeconfig }}.
Fix: export KUBECONFIG=/actual/path/from/talosctl-kubeconfig (see talos/README.md),
or copy the admin kubeconfig to {{ noble_repo_root }}/talos/kubeconfig.
Do not use documentation placeholders as the path.
tags: [always]
- name: Ensure temp dir for kubeconfig API override
ansible.builtin.file:
path: "{{ noble_repo_root }}/ansible/.ansible-tmp"
state: directory
mode: "0700"
when: noble_k8s_api_server_override | default('') | length > 0
tags: [always]
- name: Copy kubeconfig for API server override (original file unchanged)
ansible.builtin.copy:
src: "{{ noble_kubeconfig }}"
dest: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.patched"
mode: "0600"
when: noble_k8s_api_server_override | default('') | length > 0
tags: [always]
- name: Resolve current cluster name (for set-cluster)
ansible.builtin.command:
argv:
- kubectl
- config
- view
- --minify
- -o
- jsonpath={.clusters[0].name}
environment:
KUBECONFIG: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.patched"
register: noble_k8s_cluster_name
changed_when: false
when: noble_k8s_api_server_override | default('') | length > 0
tags: [always]
- name: Point patched kubeconfig at reachable apiserver
ansible.builtin.command:
argv:
- kubectl
- config
- set-cluster
- "{{ noble_k8s_cluster_name.stdout }}"
- --server={{ noble_k8s_api_server_override }}
- --kubeconfig={{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.patched
when: noble_k8s_api_server_override | default('') | length > 0
changed_when: true
tags: [always]
- name: Use patched kubeconfig for this play
ansible.builtin.set_fact:
noble_kubeconfig: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.patched"
when: noble_k8s_api_server_override | default('') | length > 0
tags: [always]
- name: Verify Kubernetes API is reachable from this host
ansible.builtin.command:
argv:
- kubectl
- get
- --raw
- /healthz
- --request-timeout=15s
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_k8s_health_first
failed_when: false
changed_when: false
tags: [always]
# talosctl kubeconfig often sets server to the VIP; off-LAN you can reach a control-plane IP but not 192.168.50.230.
# kubectl stderr is often "The connection to the server ... was refused" (no substring "connection refused").
- name: Auto-fallback API server when VIP is unreachable (temp kubeconfig)
tags: [always]
when:
- noble_k8s_api_server_auto_fallback | default(true) | bool
- noble_k8s_api_server_override | default('') | length == 0
- not (noble_skip_k8s_health_check | default(false) | bool)
- (noble_k8s_health_first.rc | default(1)) != 0 or (noble_k8s_health_first.stdout | default('') | trim) != 'ok'
- (((noble_k8s_health_first.stderr | default('')) ~ (noble_k8s_health_first.stdout | default(''))) | lower) is search('network is unreachable|no route to host|connection refused|was refused', multiline=False)
block:
- name: Ensure temp dir for kubeconfig auto-fallback
ansible.builtin.file:
path: "{{ noble_repo_root }}/ansible/.ansible-tmp"
state: directory
mode: "0700"
- name: Copy kubeconfig for API auto-fallback
ansible.builtin.copy:
src: "{{ noble_kubeconfig }}"
dest: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.auto-fallback"
mode: "0600"
- name: Resolve cluster name for kubectl set-cluster
ansible.builtin.command:
argv:
- kubectl
- config
- view
- --minify
- -o
- jsonpath={.clusters[0].name}
environment:
KUBECONFIG: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.auto-fallback"
register: noble_k8s_cluster_fb
changed_when: false
- name: Point temp kubeconfig at fallback apiserver
ansible.builtin.command:
argv:
- kubectl
- config
- set-cluster
- "{{ noble_k8s_cluster_fb.stdout }}"
- --server={{ noble_k8s_api_server_fallback | default('https://192.168.50.20:6443', true) }}
- --kubeconfig={{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.auto-fallback
changed_when: true
- name: Use kubeconfig with fallback API server for this play
ansible.builtin.set_fact:
noble_kubeconfig: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.auto-fallback"
- name: Re-verify Kubernetes API after auto-fallback
ansible.builtin.command:
argv:
- kubectl
- get
- --raw
- /healthz
- --request-timeout=15s
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_k8s_health_after_fallback
failed_when: false
changed_when: false
- name: Mark that API was re-checked after kubeconfig fallback
ansible.builtin.set_fact:
noble_k8s_api_fallback_used: true
- name: Normalize API health result for preflight (scalars; avoids dict merge / set_fact stringification)
ansible.builtin.set_fact:
noble_k8s_health_rc: "{{ noble_k8s_health_after_fallback.rc | default(1) if (noble_k8s_api_fallback_used | default(false) | bool) else (noble_k8s_health_first.rc | default(1)) }}"
noble_k8s_health_stdout: "{{ noble_k8s_health_after_fallback.stdout | default('') if (noble_k8s_api_fallback_used | default(false) | bool) else (noble_k8s_health_first.stdout | default('')) }}"
noble_k8s_health_stderr: "{{ noble_k8s_health_after_fallback.stderr | default('') if (noble_k8s_api_fallback_used | default(false) | bool) else (noble_k8s_health_first.stderr | default('')) }}"
tags: [always]
- name: Fail when API check did not return ok
ansible.builtin.fail:
msg: "{{ lookup('template', 'templates/api_health_hint.j2') }}"
when:
- not (noble_skip_k8s_health_check | default(false) | bool)
- (noble_k8s_health_rc | int) != 0 or (noble_k8s_health_stdout | default('') | trim) != 'ok'
tags: [always]
roles:
- role: helm_repos
tags: [repos, helm]
- role: noble_cilium
tags: [cilium, cni]
- role: noble_csi_snapshot_controller
tags: [csi_snapshot, snapshot, storage]
- role: noble_metrics_server
tags: [metrics, metrics_server]
- role: noble_longhorn
tags: [longhorn, storage]
- role: noble_metallb
tags: [metallb, lb]
- role: noble_kube_vip
tags: [kube_vip, vip]
- role: noble_traefik
tags: [traefik, ingress]
- role: noble_cert_manager
tags: [cert_manager, certs]
- role: noble_newt
tags: [newt]
- role: noble_argocd
tags: [argocd, gitops]
- role: noble_kyverno
tags: [kyverno, policy]
- role: noble_kyverno_policies
tags: [kyverno_policies, policy]
- role: noble_platform
tags: [platform, observability, apps]
- role: noble_velero
tags: [velero, backups]
- role: noble_landing_urls
tags: [landing, platform, observability, apps]

View File

@@ -0,0 +1,7 @@
---
# Manual follow-ups after **noble.yml**: SOPS key backup, optional Argo root Application.
- hosts: localhost
connection: local
gather_facts: false
roles:
- noble_post_deploy

View File

@@ -0,0 +1,9 @@
---
- name: Proxmox cluster bootstrap/join
hosts: proxmox_hosts
become: true
gather_facts: false
serial: 1
roles:
- role: proxmox_cluster
tags: [proxmox, cluster]

View File

@@ -0,0 +1,4 @@
---
- import_playbook: proxmox_prepare.yml
- import_playbook: proxmox_upgrade.yml
- import_playbook: proxmox_cluster.yml

View File

@@ -0,0 +1,8 @@
---
- name: Proxmox host preparation (community repos + no-subscription notice)
hosts: proxmox_hosts
become: true
gather_facts: true
roles:
- role: proxmox_baseline
tags: [proxmox, prepare, repos, ui]

View File

@@ -0,0 +1,9 @@
---
- name: Proxmox host maintenance (upgrade to latest)
hosts: proxmox_hosts
become: true
gather_facts: true
serial: 1
roles:
- role: proxmox_maintenance
tags: [proxmox, maintenance, updates]

View File

@@ -1,26 +0,0 @@
---
- name: Register Target Host
hosts: localhost
connection: local
gather_facts: no
tasks:
- name: Verify target_host is defined
fail:
msg: "The 'target_host' variable must be defined (e.g. 192.168.1.10)"
when: target_host is not defined
- name: Add target host to inventory
add_host:
name: target_node
ansible_host: "{{ target_host }}"
ansible_user: "{{ target_user | default('root') }}"
ansible_ssh_pass: "{{ target_password | default(omit) }}"
ansible_ssh_private_key_file: "{{ target_private_key_file | default(omit) }}"
ansible_python_interpreter: /usr/bin/python3
- name: Bootstrap Node
hosts: target_node
become: yes
gather_facts: yes
roles:
- common

View File

@@ -1,29 +0,0 @@
---
- name: Register Target Host
hosts: localhost
connection: local
gather_facts: no
tasks:
- name: Verify target_host is defined
fail:
msg: "The 'target_host' variable must be defined (e.g. 192.168.1.10)"
when: target_host is not defined
- name: Add target host to inventory
add_host:
name: target_node
ansible_host: "{{ target_host }}"
ansible_user: "{{ target_user | default('root') }}"
ansible_ssh_pass: "{{ target_password | default(omit) }}"
ansible_ssh_private_key_file: "{{ target_private_key_file | default(omit) }}"
ansible_python_interpreter: /usr/bin/python3
- name: Configure Networking
hosts: target_node
become: yes
gather_facts: yes
tasks:
- name: Run networking task from common role
include_role:
name: common
tasks_from: networking.yml

View File

@@ -1,29 +0,0 @@
---
- name: Register Target Host
hosts: localhost
connection: local
gather_facts: no
tasks:
- name: Verify target_host is defined
fail:
msg: "The 'target_host' variable must be defined (e.g. 192.168.1.10)"
when: target_host is not defined
- name: Add target host to inventory
add_host:
name: target_node
ansible_host: "{{ target_host }}"
ansible_user: "{{ target_user | default('root') }}"
ansible_ssh_pass: "{{ target_password | default(omit) }}"
ansible_ssh_private_key_file: "{{ target_private_key_file | default(omit) }}"
ansible_python_interpreter: /usr/bin/python3
- name: Configure Users
hosts: target_node
become: yes
gather_facts: yes
tasks:
- name: Run users task from common role
include_role:
name: common
tasks_from: users.yml

View File

@@ -1,34 +0,0 @@
---
- name: Register Proxmox Host
hosts: localhost
connection: local
gather_facts: no
tasks:
- name: Verify proxmox_host is defined
fail:
msg: "The 'proxmox_host' variable must be defined."
when: proxmox_host is not defined
- name: Verify proxmox_action is defined
fail:
msg: "The 'proxmox_action' variable must be defined (e.g. create_vm, create_template, delete_vm)."
when: proxmox_action is not defined
- name: Add Proxmox host to inventory
add_host:
name: proxmox_node
ansible_host: "{{ proxmox_host }}"
ansible_user: "{{ proxmox_user | default('root') }}"
ansible_ssh_pass: "{{ proxmox_password | default(omit) }}"
ansible_ssh_private_key_file: "{{ proxmox_private_key_file | default(omit) }}"
ansible_python_interpreter: /usr/bin/python3
- name: Execute Proxmox Action
hosts: proxmox_node
become: yes
gather_facts: yes
vars:
# Explicitly map the action variable if needed, though role should pick it up from host vars or extra vars
proxmox_action: "{{ proxmox_action }}"
roles:
- proxmox_vm

View File

@@ -0,0 +1,11 @@
---
# Genconfig only — for full Talos Phase A (apply, bootstrap, kubeconfig) use **playbooks/talos_phase_a.yml**
# or **playbooks/deploy.yml**. Run: ansible-playbook playbooks/talos_bootstrap.yml -e noble_talos_genconfig=true
- name: Talos — optional genconfig helper
hosts: localhost
connection: local
gather_facts: false
vars:
noble_repo_root: "{{ playbook_dir | dirname | dirname }}"
roles:
- role: talos_bootstrap

View File

@@ -0,0 +1,15 @@
---
# Talos Phase A — **talhelper genconfig** → **apply-config** (all nodes) → **bootstrap** → **kubeconfig**.
# Requires: **talosctl**, **talhelper**, reachable node IPs (same LAN as nodes for Talos API :50000).
# See **talos/README.md** §1§3. Then run **playbooks/noble.yml** or **deploy.yml**.
- name: Talos — genconfig, apply, bootstrap, kubeconfig
hosts: localhost
connection: local
gather_facts: false
vars:
noble_repo_root: "{{ playbook_dir | dirname | dirname }}"
noble_talos_dir: "{{ noble_repo_root }}/talos"
noble_talos_kubeconfig_out: "{{ noble_repo_root }}/talos/kubeconfig"
roles:
- role: talos_phase_a
tags: [talos, phase_a]

View File

@@ -0,0 +1,22 @@
{# Error output for noble.yml API preflight when kubectl /healthz fails #}
Cannot use the Kubernetes API from this host (kubectl get --raw /healthz).
rc={{ noble_k8s_health_rc | default('n/a') }}
stderr: {{ noble_k8s_health_stderr | default('') | trim }}
{% set err = (noble_k8s_health_stderr | default('')) | lower %}
{% if 'connection refused' in err %}
Connection refused: the TCP path to that host works, but nothing is accepting HTTPS on port 6443 there.
• **Not bootstrapped yet?** Finish Talos first: `talosctl bootstrap` (once on a control plane), then `talosctl kubeconfig`, then confirm `kubectl get nodes`. See talos/README.md §2§3 and CLUSTER-BUILD.md Phase A. **Do not run this playbook before the Kubernetes API exists.**
• If bootstrap is done: try another control-plane IP (CLUSTER-BUILD inventory: neon 192.168.50.20, argon .30, krypton .40), or the VIP if kube-vip is up and you are on the LAN:
-e 'noble_k8s_api_server_override=https://192.168.50.230:6443'
• Do not point the API URL at a worker-only node.
• `talosctl health` / `kubectl get nodes` from a working client.
{% elif 'network is unreachable' in err or 'no route to host' in err %}
Network unreachable / no route: this machine cannot route to the API IP. Join the lab LAN or VPN, or set a reachable API server URL (talos/README.md §3).
{% else %}
If kubeconfig used the VIP from off-LAN, try a reachable control-plane IP, e.g.:
-e 'noble_k8s_api_server_override=https://192.168.50.20:6443'
See talos/README.md §3.
{% endif %}
To skip this check (not recommended): -e noble_skip_k8s_health_check=true

View File

@@ -1,2 +0,0 @@
collections:
- name: community.general

View File

@@ -1,30 +0,0 @@
---
# Common packages to install
common_packages:
- curl
- wget
- git
- vim
- htop
- net-tools
- unzip
- dnsutils
- software-properties-common
- ca-certificates
- gnupg
- openssh-server
# SSH Configuration
common_ssh_users:
- name: "{{ ansible_user_id }}"
keys: []
# Add your keys in inventory or group_vars override
# Networking
common_configure_static_ip: false
common_interface_name: "eth0"
# common_ip_address: "192.168.1.100/24"
# common_gateway: "192.168.1.1"
common_dns_servers:
- "1.1.1.1"
- "8.8.8.8"

View File

@@ -1,6 +0,0 @@
---
- name: Apply Netplan
shell: netplan apply
async: 45
poll: 0
ignore_errors: yes

View File

@@ -1,10 +0,0 @@
---
- name: Install common packages
import_tasks: packages.yml
- name: Configure users and SSH keys
import_tasks: users.yml
- name: Configure networking
import_tasks: networking.yml
when: common_configure_static_ip | bool

View File

@@ -1,23 +0,0 @@
---
- name: Verify required variables for static IP
fail:
msg: "common_ip_address and common_interface_name must be defined when common_configure_static_ip is true."
when:
- common_configure_static_ip | bool
- (common_ip_address is not defined or common_ip_address | length == 0 or common_interface_name is not defined)
- name: Install netplan.io
apt:
name: netplan.io
state: present
when: ansible_os_family == "Debian"
- name: Configure Netplan
template:
src: netplan_config.yaml.j2
dest: /etc/netplan/01-netcfg.yaml
owner: root
group: root
mode: '0644'
notify: Apply Netplan
when: common_configure_static_ip | bool

View File

@@ -1,12 +0,0 @@
---
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family == "Debian"
- name: Install common packages
apt:
name: "{{ common_packages }}"
state: present
when: ansible_os_family == "Debian"

View File

@@ -1,18 +0,0 @@
---
- name: Ensure users exist
user:
name: "{{ item.name }}"
shell: /bin/bash
groups: sudo
append: yes
state: present
loop: "{{ common_ssh_users }}"
when: item.create_user | default(false)
- name: Add SSH keys
authorized_key:
user: "{{ item.0.name }}"
key: "{{ item.1 }}"
loop: "{{ common_ssh_users | subelements('keys', skip_missing=True) }}"
loop_control:
label: "{{ item.0.name }}"

View File

@@ -1,15 +0,0 @@
network:
version: 2
ethernets:
{{ common_interface_name }}:
dhcp4: no
addresses:
- {{ common_ip_address }}
{% if common_gateway %}
gateway4: {{ common_gateway }}
{% endif %}
nameservers:
addresses:
{% for server in common_dns_servers %}
- {{ server }}
{% endfor %}

View File

@@ -0,0 +1,39 @@
---
# Update apt metadata only when stale (seconds)
debian_baseline_apt_cache_valid_time: 3600
# Core host hardening packages
debian_baseline_packages:
- unattended-upgrades
- apt-listchanges
- fail2ban
- needrestart
- sudo
- ca-certificates
# SSH hardening controls
debian_baseline_ssh_permit_root_login: "no"
debian_baseline_ssh_password_authentication: "no"
debian_baseline_ssh_pubkey_authentication: "yes"
debian_baseline_ssh_x11_forwarding: "no"
debian_baseline_ssh_max_auth_tries: 3
debian_baseline_ssh_client_alive_interval: 300
debian_baseline_ssh_client_alive_count_max: 2
debian_baseline_ssh_allow_users: []
# unattended-upgrades controls
debian_baseline_enable_unattended_upgrades: true
debian_baseline_unattended_auto_upgrade: "1"
debian_baseline_unattended_update_lists: "1"
# Kernel and network hardening sysctls
debian_baseline_sysctl_settings:
net.ipv4.conf.all.accept_redirects: "0"
net.ipv4.conf.default.accept_redirects: "0"
net.ipv4.conf.all.send_redirects: "0"
net.ipv4.conf.default.send_redirects: "0"
net.ipv4.conf.all.log_martians: "1"
net.ipv4.conf.default.log_martians: "1"
net.ipv4.tcp_syncookies: "1"
net.ipv6.conf.all.accept_redirects: "0"
net.ipv6.conf.default.accept_redirects: "0"

View File

@@ -0,0 +1,12 @@
---
- name: Restart ssh
ansible.builtin.service:
name: ssh
state: restarted
- name: Reload sysctl
ansible.builtin.command:
argv:
- sysctl
- --system
changed_when: true

View File

@@ -0,0 +1,52 @@
---
- name: Refresh apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: "{{ debian_baseline_apt_cache_valid_time }}"
- name: Install baseline hardening packages
ansible.builtin.apt:
name: "{{ debian_baseline_packages }}"
state: present
- name: Configure unattended-upgrades auto settings
ansible.builtin.copy:
dest: /etc/apt/apt.conf.d/20auto-upgrades
mode: "0644"
content: |
APT::Periodic::Update-Package-Lists "{{ debian_baseline_unattended_update_lists }}";
APT::Periodic::Unattended-Upgrade "{{ debian_baseline_unattended_auto_upgrade }}";
when: debian_baseline_enable_unattended_upgrades | bool
- name: Configure SSH hardening options
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/99-hardening.conf
mode: "0644"
content: |
PermitRootLogin {{ debian_baseline_ssh_permit_root_login }}
PasswordAuthentication {{ debian_baseline_ssh_password_authentication }}
PubkeyAuthentication {{ debian_baseline_ssh_pubkey_authentication }}
X11Forwarding {{ debian_baseline_ssh_x11_forwarding }}
MaxAuthTries {{ debian_baseline_ssh_max_auth_tries }}
ClientAliveInterval {{ debian_baseline_ssh_client_alive_interval }}
ClientAliveCountMax {{ debian_baseline_ssh_client_alive_count_max }}
{% if debian_baseline_ssh_allow_users | length > 0 %}
AllowUsers {{ debian_baseline_ssh_allow_users | join(' ') }}
{% endif %}
notify: Restart ssh
- name: Configure baseline sysctls
ansible.builtin.copy:
dest: /etc/sysctl.d/99-hardening.conf
mode: "0644"
content: |
{% for key, value in debian_baseline_sysctl_settings.items() %}
{{ key }} = {{ value }}
{% endfor %}
notify: Reload sysctl
- name: Ensure fail2ban service is enabled
ansible.builtin.service:
name: fail2ban
enabled: true
state: started

View File

@@ -0,0 +1,7 @@
---
debian_maintenance_apt_cache_valid_time: 3600
debian_maintenance_upgrade_type: dist
debian_maintenance_autoremove: true
debian_maintenance_autoclean: true
debian_maintenance_reboot_if_required: true
debian_maintenance_reboot_timeout: 1800

View File

@@ -0,0 +1,30 @@
---
- name: Refresh apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: "{{ debian_maintenance_apt_cache_valid_time }}"
- name: Upgrade Debian packages
ansible.builtin.apt:
upgrade: "{{ debian_maintenance_upgrade_type }}"
- name: Remove orphaned packages
ansible.builtin.apt:
autoremove: "{{ debian_maintenance_autoremove }}"
- name: Clean apt package cache
ansible.builtin.apt:
autoclean: "{{ debian_maintenance_autoclean }}"
- name: Check if reboot is required
ansible.builtin.stat:
path: /var/run/reboot-required
register: debian_maintenance_reboot_required_file
- name: Reboot when required by package updates
ansible.builtin.reboot:
reboot_timeout: "{{ debian_maintenance_reboot_timeout }}"
msg: "Reboot initiated by Ansible maintenance playbook"
when:
- debian_maintenance_reboot_if_required | bool
- debian_maintenance_reboot_required_file.stat.exists | default(false)

View File

@@ -0,0 +1,10 @@
---
# List of users to manage keys for.
# Example:
# debian_ssh_rotation_users:
# - name: deploy
# home: /home/deploy
# state: present
# keys:
# - "ssh-ed25519 AAAA... deploy@laptop"
debian_ssh_rotation_users: []

View File

@@ -0,0 +1,50 @@
---
- name: Validate SSH key rotation inputs
ansible.builtin.assert:
that:
- item.name is defined
- item.home is defined
- (item.state | default('present')) in ['present', 'absent']
- (item.state | default('present')) == 'absent' or (item.keys is defined and item.keys | length > 0)
fail_msg: >-
Each entry in debian_ssh_rotation_users must include name, home, and either:
state=absent, or keys with at least one SSH public key.
loop: "{{ debian_ssh_rotation_users }}"
loop_control:
label: "{{ item.name | default('unknown') }}"
- name: Ensure ~/.ssh exists for managed users
ansible.builtin.file:
path: "{{ item.home }}/.ssh"
state: directory
owner: "{{ item.name }}"
group: "{{ item.name }}"
mode: "0700"
loop: "{{ debian_ssh_rotation_users }}"
loop_control:
label: "{{ item.name }}"
when: (item.state | default('present')) == 'present'
- name: Rotate authorized_keys for managed users
ansible.builtin.copy:
dest: "{{ item.home }}/.ssh/authorized_keys"
owner: "{{ item.name }}"
group: "{{ item.name }}"
mode: "0600"
content: |
{% for key in item.keys %}
{{ key }}
{% endfor %}
loop: "{{ debian_ssh_rotation_users }}"
loop_control:
label: "{{ item.name }}"
when: (item.state | default('present')) == 'present'
- name: Remove authorized_keys for users marked absent
ansible.builtin.file:
path: "{{ item.home }}/.ssh/authorized_keys"
state: absent
loop: "{{ debian_ssh_rotation_users }}"
loop_control:
label: "{{ item.name }}"
when: (item.state | default('present')) == 'absent'

View File

@@ -0,0 +1,16 @@
---
noble_helm_repos:
- { name: cilium, url: "https://helm.cilium.io/" }
- { name: metallb, url: "https://metallb.github.io/metallb" }
- { name: longhorn, url: "https://charts.longhorn.io" }
- { name: traefik, url: "https://traefik.github.io/charts" }
- { name: jetstack, url: "https://charts.jetstack.io" }
- { name: fossorial, url: "https://charts.fossorial.io" }
- { name: argo, url: "https://argoproj.github.io/argo-helm" }
- { name: metrics-server, url: "https://kubernetes-sigs.github.io/metrics-server/" }
- { name: prometheus-community, url: "https://prometheus-community.github.io/helm-charts" }
- { name: grafana, url: "https://grafana.github.io/helm-charts" }
- { name: fluent, url: "https://fluent.github.io/helm-charts" }
- { name: headlamp, url: "https://kubernetes-sigs.github.io/headlamp/" }
- { name: kyverno, url: "https://kyverno.github.io/kyverno/" }
- { name: vmware-tanzu, url: "https://vmware-tanzu.github.io/helm-charts" }

View File

@@ -0,0 +1,16 @@
---
- name: Add Helm repositories
ansible.builtin.command:
cmd: "helm repo add {{ item.name }} {{ item.url }}"
loop: "{{ noble_helm_repos }}"
loop_control:
label: "{{ item.name }}"
register: helm_repo_add
changed_when: helm_repo_add.rc == 0
failed_when: >-
helm_repo_add.rc != 0 and
('already exists' not in (helm_repo_add.stderr | default('')))
- name: helm repo update
ansible.builtin.command: helm repo update
changed_when: true

View File

@@ -0,0 +1,6 @@
---
# When true, applies clusters/noble/bootstrap/argocd/root-application.yaml (app-of-apps).
# Edit spec.source.repoURL in that file if your Git remote differs.
noble_argocd_apply_root_application: false
# When true, applies clusters/noble/bootstrap/argocd/bootstrap-root-application.yaml (noble-bootstrap-root; manual sync until README §5).
noble_argocd_apply_bootstrap_root_application: true

View File

@@ -0,0 +1,46 @@
---
- name: Install Argo CD
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- argocd
- argo/argo-cd
- --namespace
- argocd
- --create-namespace
- --version
- "9.4.17"
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/argocd/values.yaml"
- --wait
- --timeout
- 15m
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Apply Argo CD root Application (app-of-apps)
ansible.builtin.command:
argv:
- kubectl
- apply
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/argocd/root-application.yaml"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
when: noble_argocd_apply_root_application | default(false) | bool
changed_when: true
- name: Apply Argo CD bootstrap app-of-apps Application
ansible.builtin.command:
argv:
- kubectl
- apply
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/argocd/bootstrap-root-application.yaml"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
when: noble_argocd_apply_bootstrap_root_application | default(false) | bool
changed_when: true

View File

@@ -0,0 +1,3 @@
---
# Warn when **cloudflare-dns-api-token** is missing after apply (also set in **group_vars/all.yml** when loaded).
noble_cert_manager_require_cloudflare_secret: true

View File

@@ -0,0 +1,28 @@
---
# See repository **.env.sample** — copy to **.env** (gitignored).
- name: Stat repository .env for deploy secrets
ansible.builtin.stat:
path: "{{ noble_repo_root }}/.env"
register: noble_deploy_env_file
changed_when: false
- name: Create cert-manager Cloudflare DNS secret from .env
ansible.builtin.shell: |
set -euo pipefail
set -a
. "{{ noble_repo_root }}/.env"
set +a
if [ -z "${CLOUDFLARE_DNS_API_TOKEN:-}" ]; then
echo NO_TOKEN
exit 0
fi
kubectl -n cert-manager create secret generic cloudflare-dns-api-token \
--from-literal=api-token="${CLOUDFLARE_DNS_API_TOKEN}" \
--dry-run=client -o yaml | kubectl apply -f -
echo APPLIED
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
when: noble_deploy_env_file.stat.exists | default(false)
no_log: true
register: noble_cf_secret_from_env
changed_when: "'APPLIED' in (noble_cf_secret_from_env.stdout | default(''))"

View File

@@ -0,0 +1,68 @@
---
- name: Create cert-manager namespace
ansible.builtin.command:
argv:
- kubectl
- apply
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/cert-manager/namespace.yaml"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Install cert-manager
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- cert-manager
- jetstack/cert-manager
- --namespace
- cert-manager
- --version
- v1.20.0
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/cert-manager/values.yaml"
- --wait
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Apply secrets from repository .env (optional)
ansible.builtin.include_tasks: from_env.yml
- name: Check Cloudflare DNS API token Secret (required for ClusterIssuers)
ansible.builtin.command:
argv:
- kubectl
- -n
- cert-manager
- get
- secret
- cloudflare-dns-api-token
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_cf_secret
failed_when: false
changed_when: false
- name: Warn when Cloudflare Secret is missing
ansible.builtin.debug:
msg: >-
Secret cert-manager/cloudflare-dns-api-token not found.
Create it per clusters/noble/bootstrap/cert-manager/README.md before ClusterIssuers can succeed.
when:
- noble_cert_manager_require_cloudflare_secret | default(true) | bool
- noble_cf_secret.rc != 0
- name: Apply ClusterIssuers (staging + prod)
ansible.builtin.command:
argv:
- kubectl
- apply
- -k
- "{{ noble_repo_root }}/clusters/noble/bootstrap/cert-manager"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true

View File

@@ -0,0 +1,25 @@
---
- name: Install Cilium (required CNI for Talos cni:none)
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- cilium
- cilium/cilium
- --namespace
- kube-system
- --version
- "1.16.6"
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/cilium/values.yaml"
- --wait
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Wait for Cilium DaemonSet
ansible.builtin.command: kubectl -n kube-system rollout status ds/cilium --timeout=300s
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: false

View File

@@ -0,0 +1,2 @@
---
noble_csi_snapshot_kubectl_timeout: 120s

View File

@@ -0,0 +1,39 @@
---
# Volume Snapshot CRDs + snapshot-controller (Velero CSI / Longhorn snapshots).
- name: Apply Volume Snapshot CRDs (snapshot.storage.k8s.io)
ansible.builtin.command:
argv:
- kubectl
- apply
- "--request-timeout={{ noble_csi_snapshot_kubectl_timeout | default('120s') }}"
- -k
- "{{ noble_repo_root }}/clusters/noble/bootstrap/csi-snapshot-controller/crd"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Apply snapshot-controller in kube-system
ansible.builtin.command:
argv:
- kubectl
- apply
- "--request-timeout={{ noble_csi_snapshot_kubectl_timeout | default('120s') }}"
- -k
- "{{ noble_repo_root }}/clusters/noble/bootstrap/csi-snapshot-controller/controller"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Wait for snapshot-controller Deployment
ansible.builtin.command:
argv:
- kubectl
- -n
- kube-system
- rollout
- status
- deploy/snapshot-controller
- --timeout=120s
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: false

View File

@@ -0,0 +1,11 @@
---
- name: Apply kube-vip (Kubernetes API VIP)
ansible.builtin.command:
argv:
- kubectl
- apply
- -k
- "{{ noble_repo_root }}/clusters/noble/bootstrap/kube-vip"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true

View File

@@ -0,0 +1,32 @@
---
- name: Create Kyverno namespace
ansible.builtin.command:
argv:
- kubectl
- apply
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/kyverno/namespace.yaml"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Install Kyverno operator
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- kyverno
- kyverno/kyverno
- -n
- kyverno
- --version
- "3.7.1"
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/kyverno/values.yaml"
- --wait
- --timeout
- 15m
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true

View File

@@ -0,0 +1,21 @@
---
- name: Install Kyverno policy chart (PSS baseline, Audit)
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- kyverno-policies
- kyverno/kyverno-policies
- -n
- kyverno
- --version
- "3.7.1"
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/kyverno/policies-values.yaml"
- --wait
- --timeout
- 10m
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true

View File

@@ -0,0 +1,51 @@
---
# Regenerated when **noble_landing_urls** runs (after platform stack). Paths match Traefik + cert-manager Ingresses.
noble_landing_urls_dest: "{{ noble_repo_root }}/ansible/output/noble-lab-ui-urls.md"
# When true, run kubectl to fill Argo CD / Grafana secrets and a bounded Headlamp SA token in the markdown (requires working kubeconfig).
noble_landing_urls_fetch_credentials: true
# Headlamp: bounded token for UI sign-in (`kubectl create token`); cluster may cap max duration.
noble_landing_urls_headlamp_token_duration: 48h
noble_lab_ui_entries:
- name: Argo CD
description: GitOps UI (sync, apps, repos)
namespace: argocd
service: argocd-server
url: https://argo.apps.noble.lab.pcenicni.dev
- name: Grafana
description: Dashboards, Loki explore (logs)
namespace: monitoring
service: kube-prometheus-grafana
url: https://grafana.apps.noble.lab.pcenicni.dev
- name: Prometheus
description: Prometheus UI (queries, targets) — lab; protect in production
namespace: monitoring
service: kube-prometheus-kube-prome-prometheus
url: https://prometheus.apps.noble.lab.pcenicni.dev
- name: Alertmanager
description: Alertmanager UI (silences, status)
namespace: monitoring
service: kube-prometheus-kube-prome-alertmanager
url: https://alertmanager.apps.noble.lab.pcenicni.dev
- name: Headlamp
description: Kubernetes UI (cluster resources)
namespace: headlamp
service: headlamp
url: https://headlamp.apps.noble.lab.pcenicni.dev
- name: Longhorn
description: Storage volumes, nodes, backups
namespace: longhorn-system
service: longhorn-frontend
url: https://longhorn.apps.noble.lab.pcenicni.dev
- name: Velero
description: Cluster backups — no web UI (velero CLI / kubectl CRDs)
namespace: velero
service: velero
url: ""
- name: Homepage
description: App dashboard (links to lab UIs)
namespace: homepage
service: homepage
url: https://homepage.apps.noble.lab.pcenicni.dev

View File

@@ -0,0 +1,72 @@
---
# Populates template variables from Secrets + Headlamp token (no_log on kubectl to avoid leaking into Ansible stdout).
- name: Fetch Argo CD initial admin password (base64)
ansible.builtin.command:
argv:
- kubectl
- -n
- argocd
- get
- secret
- argocd-initial-admin-secret
- -o
- jsonpath={.data.password}
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_fetch_argocd_pw_b64
failed_when: false
changed_when: false
no_log: true
- name: Fetch Grafana admin user (base64)
ansible.builtin.command:
argv:
- kubectl
- -n
- monitoring
- get
- secret
- kube-prometheus-grafana
- -o
- jsonpath={.data.admin-user}
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_fetch_grafana_user_b64
failed_when: false
changed_when: false
no_log: true
- name: Fetch Grafana admin password (base64)
ansible.builtin.command:
argv:
- kubectl
- -n
- monitoring
- get
- secret
- kube-prometheus-grafana
- -o
- jsonpath={.data.admin-password}
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_fetch_grafana_pw_b64
failed_when: false
changed_when: false
no_log: true
- name: Create Headlamp ServiceAccount token (for UI sign-in)
ansible.builtin.command:
argv:
- kubectl
- -n
- headlamp
- create
- token
- headlamp
- "--duration={{ noble_landing_urls_headlamp_token_duration | default('48h') }}"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_fetch_headlamp_token
failed_when: false
changed_when: false
no_log: true

View File

@@ -0,0 +1,20 @@
---
- name: Ensure output directory for generated landing page
ansible.builtin.file:
path: "{{ noble_repo_root }}/ansible/output"
state: directory
mode: "0755"
- name: Fetch initial credentials from cluster Secrets (optional)
ansible.builtin.include_tasks: fetch_credentials.yml
when: noble_landing_urls_fetch_credentials | default(true) | bool
- name: Write noble lab UI URLs (markdown landing page)
ansible.builtin.template:
src: noble-lab-ui-urls.md.j2
dest: "{{ noble_landing_urls_dest }}"
mode: "0644"
- name: Show landing page path
ansible.builtin.debug:
msg: "Noble lab UI list written to {{ noble_landing_urls_dest }}"

View File

@@ -0,0 +1,51 @@
# Noble lab — web UIs (LAN)
> **Sensitive:** This file may include **passwords read from Kubernetes Secrets** when credential fetch ran. It is **gitignored** — do not commit or share.
**DNS:** point **`*.apps.noble.lab.pcenicni.dev`** at the Traefik **LoadBalancer** (MetalLB **`192.168.50.211`** by default — see `clusters/noble/bootstrap/traefik/values.yaml`).
**TLS:** **cert-manager** + **`letsencrypt-prod`** on each Ingress (public **DNS-01** for **`pcenicni.dev`**).
This file is **generated** by Ansible (`noble_landing_urls` role). Use it as a temporary landing page to find services after deploy.
| UI | What | Kubernetes service | Namespace | URL |
|----|------|----------------------|-----------|-----|
{% for e in noble_lab_ui_entries %}
| {{ e.name }} | {{ e.description }} | `{{ e.service }}` | `{{ e.namespace }}` | {% if e.url | default('') | length > 0 %}[{{ e.url }}]({{ e.url }}){% else %}—{% endif %} |
{% endfor %}
## Initial access (logins)
| App | Username / identity | Password / secret |
|-----|---------------------|-------------------|
| **Argo CD** | `admin` | {% if (noble_fetch_argocd_pw_b64 is defined) and (noble_fetch_argocd_pw_b64.rc | default(1) == 0) and (noble_fetch_argocd_pw_b64.stdout | default('') | length > 0) %}`{{ noble_fetch_argocd_pw_b64.stdout | b64decode }}`{% else %}*(not fetched — use commands below)*{% endif %} |
| **Grafana** | {% if (noble_fetch_grafana_user_b64 is defined) and (noble_fetch_grafana_user_b64.rc | default(1) == 0) and (noble_fetch_grafana_user_b64.stdout | default('') | length > 0) %}`{{ noble_fetch_grafana_user_b64.stdout | b64decode }}`{% else %}*(from Secret — use commands below)*{% endif %} | {% if (noble_fetch_grafana_pw_b64 is defined) and (noble_fetch_grafana_pw_b64.rc | default(1) == 0) and (noble_fetch_grafana_pw_b64.stdout | default('') | length > 0) %}`{{ noble_fetch_grafana_pw_b64.stdout | b64decode }}`{% else %}*(not fetched — use commands below)*{% endif %} |
| **Headlamp** | ServiceAccount **`headlamp`** | {% if (noble_fetch_headlamp_token is defined) and (noble_fetch_headlamp_token.rc | default(1) == 0) and (noble_fetch_headlamp_token.stdout | default('') | trim | length > 0) %}Token ({{ noble_landing_urls_headlamp_token_duration | default('48h') }}): `{{ noble_fetch_headlamp_token.stdout | trim }}`{% else %}*(not generated — use command below)*{% endif %} |
| **Prometheus** | — | No auth in default install (lab). |
| **Alertmanager** | — | No auth in default install (lab). |
| **Longhorn** | — | No default login unless you enable access control in the UI settings. |
### Commands to retrieve passwords (if not filled above)
```bash
# Argo CD initial admin (Secret removed after you change password)
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d
echo
# Grafana admin user / password
kubectl -n monitoring get secret kube-prometheus-grafana -o jsonpath='{.data.admin-user}' | base64 -d
echo
kubectl -n monitoring get secret kube-prometheus-grafana -o jsonpath='{.data.admin-password}' | base64 -d
echo
```
To generate this file **without** calling kubectl, run Ansible with **`-e noble_landing_urls_fetch_credentials=false`**.
## Notes
- **Argo CD** `argocd-initial-admin-secret` disappears after you change the admin password.
- **Grafana** password is random unless you set `grafana.adminPassword` in chart values.
- **Prometheus / Alertmanager** UIs are unauthenticated by default — restrict when hardening (`talos/CLUSTER-BUILD.md` Phase G).
- **SOPS:** cluster secrets in git under **`clusters/noble/secrets/`** are encrypted; decrypt with **`age-key.txt`** (not in git). See **`clusters/noble/secrets/README.md`**.
- **Headlamp** token above expires after the configured duration; re-run Ansible or `kubectl create token` to refresh.
- **Velero** has **no web UI** — use **`velero`** CLI or **`kubectl -n velero get backup,schedule,backupstoragelocation`**. Metrics: **`velero`** Service in **`velero`** (Prometheus scrape). See `clusters/noble/bootstrap/velero/README.md`.

View File

@@ -0,0 +1,29 @@
---
- name: Apply Longhorn namespace (PSA) from kustomization
ansible.builtin.command:
argv:
- kubectl
- apply
- -k
- "{{ noble_repo_root }}/clusters/noble/bootstrap/longhorn"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Install Longhorn chart
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- longhorn
- longhorn/longhorn
- -n
- longhorn-system
- --create-namespace
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/longhorn/values.yaml"
- --wait
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true

View File

@@ -0,0 +1,3 @@
---
# Helm **--wait** default is often too short when images pull slowly or nodes are busy.
noble_helm_metallb_wait_timeout: 20m

View File

@@ -0,0 +1,39 @@
---
- name: Apply MetalLB namespace (Pod Security labels)
ansible.builtin.command:
argv:
- kubectl
- apply
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/metallb/namespace.yaml"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Install MetalLB chart
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- metallb
- metallb/metallb
- --namespace
- metallb-system
- --wait
- --timeout
- "{{ noble_helm_metallb_wait_timeout }}"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Apply IPAddressPool and L2Advertisement
ansible.builtin.command:
argv:
- kubectl
- apply
- -k
- "{{ noble_repo_root }}/clusters/noble/bootstrap/metallb"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true

View File

@@ -0,0 +1,19 @@
---
- name: Install metrics-server
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- metrics-server
- metrics-server/metrics-server
- -n
- kube-system
- --version
- "3.13.0"
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/metrics-server/values.yaml"
- --wait
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true

View File

@@ -0,0 +1,3 @@
---
# Set true after creating the newt-pangolin-auth Secret (see role / cluster docs).
noble_newt_install: true

View File

@@ -0,0 +1,30 @@
---
# See repository **.env.sample** — copy to **.env** (gitignored).
- name: Stat repository .env for deploy secrets
ansible.builtin.stat:
path: "{{ noble_repo_root }}/.env"
register: noble_deploy_env_file
changed_when: false
- name: Create newt-pangolin-auth Secret from .env
ansible.builtin.shell: |
set -euo pipefail
set -a
. "{{ noble_repo_root }}/.env"
set +a
if [ -z "${PANGOLIN_ENDPOINT:-}" ] || [ -z "${NEWT_ID:-}" ] || [ -z "${NEWT_SECRET:-}" ]; then
echo NO_VARS
exit 0
fi
kubectl -n newt create secret generic newt-pangolin-auth \
--from-literal=PANGOLIN_ENDPOINT="${PANGOLIN_ENDPOINT}" \
--from-literal=NEWT_ID="${NEWT_ID}" \
--from-literal=NEWT_SECRET="${NEWT_SECRET}" \
--dry-run=client -o yaml | kubectl apply -f -
echo APPLIED
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
when: noble_deploy_env_file.stat.exists | default(false)
no_log: true
register: noble_newt_secret_from_env
changed_when: "'APPLIED' in (noble_newt_secret_from_env.stdout | default(''))"

View File

@@ -0,0 +1,41 @@
---
- name: Skip Newt when not enabled
ansible.builtin.debug:
msg: "noble_newt_install is false — set PANGOLIN_ENDPOINT, NEWT_ID, NEWT_SECRET in repo .env (or create the Secret manually) and set noble_newt_install=true to deploy Newt."
when: not (noble_newt_install | bool)
- name: Create Newt namespace
ansible.builtin.command:
argv:
- kubectl
- apply
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/newt/namespace.yaml"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
when: noble_newt_install | bool
changed_when: true
- name: Apply Newt Pangolin auth Secret from repository .env (optional)
ansible.builtin.include_tasks: from_env.yml
when: noble_newt_install | bool
- name: Install Newt chart
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- newt
- fossorial/newt
- --namespace
- newt
- --version
- "1.2.0"
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/newt/values.yaml"
- --wait
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
when: noble_newt_install | bool
changed_when: true

View File

@@ -0,0 +1,9 @@
---
# kubectl apply -k can hit transient etcd timeouts under load; retries + longer API deadline help.
noble_platform_kubectl_request_timeout: 120s
noble_platform_kustomize_retries: 5
noble_platform_kustomize_delay: 20
# Decrypt **clusters/noble/secrets/*.yaml** with SOPS and kubectl apply (requires **sops**, **age**, and **age-key.txt**).
noble_apply_sops_secrets: true
noble_sops_age_key_file: "{{ noble_repo_root }}/age-key.txt"

View File

@@ -0,0 +1,117 @@
---
# Mirrors former **noble-platform** Argo Application: Helm releases + plain manifests under clusters/noble/bootstrap.
- name: Apply clusters/noble/bootstrap kustomize (namespaces, Grafana Loki datasource)
ansible.builtin.command:
argv:
- kubectl
- apply
- "--request-timeout={{ noble_platform_kubectl_request_timeout }}"
- -k
- "{{ noble_repo_root }}/clusters/noble/bootstrap"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_platform_kustomize
retries: "{{ noble_platform_kustomize_retries | int }}"
delay: "{{ noble_platform_kustomize_delay | int }}"
until: noble_platform_kustomize.rc == 0
changed_when: true
- name: Stat SOPS age private key (age-key.txt)
ansible.builtin.stat:
path: "{{ noble_sops_age_key_file }}"
register: noble_sops_age_key_stat
- name: Apply SOPS-encrypted cluster secrets (clusters/noble/secrets/*.yaml)
ansible.builtin.shell: |
set -euo pipefail
shopt -s nullglob
for f in "{{ noble_repo_root }}/clusters/noble/secrets"/*.yaml; do
sops -d "$f" | kubectl apply -f -
done
args:
executable: /bin/bash
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
SOPS_AGE_KEY_FILE: "{{ noble_sops_age_key_file }}"
when:
- noble_apply_sops_secrets | default(true) | bool
- noble_sops_age_key_stat.stat.exists
changed_when: true
- name: Install kube-prometheus-stack
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- kube-prometheus
- prometheus-community/kube-prometheus-stack
- -n
- monitoring
- --version
- "82.15.1"
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/kube-prometheus-stack/values.yaml"
- --wait
- --timeout
- 30m
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Install Loki
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- loki
- grafana/loki
- -n
- loki
- --version
- "6.55.0"
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/loki/values.yaml"
- --wait
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Install Fluent Bit
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- fluent-bit
- fluent/fluent-bit
- -n
- logging
- --version
- "0.56.0"
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/fluent-bit/values.yaml"
- --wait
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Install Headlamp
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- headlamp
- headlamp/headlamp
- --version
- "0.40.1"
- -n
- headlamp
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/headlamp/values.yaml"
- --wait
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true

View File

@@ -0,0 +1,15 @@
---
- name: SOPS secrets (workstation)
ansible.builtin.debug:
msg: |
Encrypted Kubernetes Secrets live under clusters/noble/secrets/ (Mozilla SOPS + age).
Private key: age-key.txt at repo root (gitignored). See clusters/noble/secrets/README.md
and .sops.yaml. noble.yml decrypt-applies these when age-key.txt exists.
- name: Argo CD optional root Application (empty app-of-apps)
ansible.builtin.debug:
msg: >-
App-of-apps: noble.yml applies root-application.yaml when noble_argocd_apply_root_application is true;
bootstrap-root-application.yaml when noble_argocd_apply_bootstrap_root_application is true (group_vars/all.yml).
noble-bootstrap-root uses manual sync until you enable automation after the playbook —
clusters/noble/bootstrap/argocd/README.md §5. See clusters/noble/apps/README.md and that README.

View File

@@ -0,0 +1,30 @@
---
- name: Create Traefik namespace
ansible.builtin.command:
argv:
- kubectl
- apply
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/traefik/namespace.yaml"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true
- name: Install Traefik
ansible.builtin.command:
argv:
- helm
- upgrade
- --install
- traefik
- traefik/traefik
- --namespace
- traefik
- --version
- "39.0.6"
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/traefik/values.yaml"
- --wait
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
changed_when: true

View File

@@ -0,0 +1,13 @@
---
# **noble_velero_install** is in **ansible/group_vars/all.yml**. Override S3 fields via extra-vars or group_vars.
noble_velero_chart_version: "12.0.0"
noble_velero_s3_bucket: ""
noble_velero_s3_url: ""
noble_velero_s3_region: "us-east-1"
noble_velero_s3_force_path_style: "true"
noble_velero_s3_prefix: ""
# Optional — if unset, Ansible expects Secret **velero/velero-cloud-credentials** (key **cloud**) to exist.
noble_velero_aws_access_key_id: ""
noble_velero_aws_secret_access_key: ""

View File

@@ -0,0 +1,68 @@
---
# See repository **.env.sample** — copy to **.env** (gitignored).
- name: Stat repository .env for Velero
ansible.builtin.stat:
path: "{{ noble_repo_root }}/.env"
register: noble_deploy_env_file
changed_when: false
- name: Load NOBLE_VELERO_S3_BUCKET from .env when unset
ansible.builtin.shell: |
set -a
. "{{ noble_repo_root }}/.env"
set +a
echo "${NOBLE_VELERO_S3_BUCKET:-}"
register: noble_velero_s3_bucket_from_env
when:
- noble_deploy_env_file.stat.exists | default(false)
- noble_velero_s3_bucket | default('') | length == 0
changed_when: false
- name: Apply NOBLE_VELERO_S3_BUCKET from .env
ansible.builtin.set_fact:
noble_velero_s3_bucket: "{{ noble_velero_s3_bucket_from_env.stdout | trim }}"
when:
- noble_velero_s3_bucket_from_env is defined
- (noble_velero_s3_bucket_from_env.stdout | default('') | trim | length) > 0
- name: Load NOBLE_VELERO_S3_URL from .env when unset
ansible.builtin.shell: |
set -a
. "{{ noble_repo_root }}/.env"
set +a
echo "${NOBLE_VELERO_S3_URL:-}"
register: noble_velero_s3_url_from_env
when:
- noble_deploy_env_file.stat.exists | default(false)
- noble_velero_s3_url | default('') | length == 0
changed_when: false
- name: Apply NOBLE_VELERO_S3_URL from .env
ansible.builtin.set_fact:
noble_velero_s3_url: "{{ noble_velero_s3_url_from_env.stdout | trim }}"
when:
- noble_velero_s3_url_from_env is defined
- (noble_velero_s3_url_from_env.stdout | default('') | trim | length) > 0
- name: Create velero-cloud-credentials from .env when keys present
ansible.builtin.shell: |
set -euo pipefail
set -a
. "{{ noble_repo_root }}/.env"
set +a
if [ -z "${NOBLE_VELERO_AWS_ACCESS_KEY_ID:-}" ] || [ -z "${NOBLE_VELERO_AWS_SECRET_ACCESS_KEY:-}" ]; then
echo SKIP
exit 0
fi
CLOUD="$(printf '[default]\naws_access_key_id=%s\naws_secret_access_key=%s\n' \
"${NOBLE_VELERO_AWS_ACCESS_KEY_ID}" "${NOBLE_VELERO_AWS_SECRET_ACCESS_KEY}")"
kubectl -n velero create secret generic velero-cloud-credentials \
--from-literal=cloud="${CLOUD}" \
--dry-run=client -o yaml | kubectl apply -f -
echo APPLIED
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
when: noble_deploy_env_file.stat.exists | default(false)
no_log: true
register: noble_velero_secret_from_env
changed_when: "'APPLIED' in (noble_velero_secret_from_env.stdout | default(''))"

View File

@@ -0,0 +1,85 @@
---
# Velero — S3 backup target + built-in CSI snapshots (Longhorn: label VolumeSnapshotClass per README).
- name: Apply velero namespace
ansible.builtin.command:
argv:
- kubectl
- apply
- -f
- "{{ noble_repo_root }}/clusters/noble/bootstrap/velero/namespace.yaml"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
when: noble_velero_install | default(false) | bool
changed_when: true
- name: Include Velero settings from repository .env (S3 bucket, URL, credentials)
ansible.builtin.include_tasks: from_env.yml
when: noble_velero_install | default(false) | bool
- name: Require S3 bucket and endpoint for Velero
ansible.builtin.assert:
that:
- noble_velero_s3_bucket | default('') | length > 0
- noble_velero_s3_url | default('') | length > 0
fail_msg: >-
Set NOBLE_VELERO_S3_BUCKET and NOBLE_VELERO_S3_URL in .env, or noble_velero_s3_bucket / noble_velero_s3_url
(e.g. -e ...), or group_vars when noble_velero_install is true.
when: noble_velero_install | default(false) | bool
- name: Create velero-cloud-credentials from Ansible vars
ansible.builtin.shell: |
set -euo pipefail
CLOUD="$(printf '[default]\naws_access_key_id=%s\naws_secret_access_key=%s\n' \
"${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}")"
kubectl -n velero create secret generic velero-cloud-credentials \
--from-literal=cloud="${CLOUD}" \
--dry-run=client -o yaml | kubectl apply -f -
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
AWS_ACCESS_KEY_ID: "{{ noble_velero_aws_access_key_id }}"
AWS_SECRET_ACCESS_KEY: "{{ noble_velero_aws_secret_access_key }}"
when:
- noble_velero_install | default(false) | bool
- noble_velero_aws_access_key_id | default('') | length > 0
- noble_velero_aws_secret_access_key | default('') | length > 0
no_log: true
changed_when: true
- name: Check velero-cloud-credentials Secret
ansible.builtin.command:
argv:
- kubectl
- -n
- velero
- get
- secret
- velero-cloud-credentials
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_velero_secret_check
failed_when: false
changed_when: false
when: noble_velero_install | default(false) | bool
- name: Require velero-cloud-credentials before Helm
ansible.builtin.assert:
that:
- noble_velero_secret_check.rc == 0
fail_msg: >-
Velero needs Secret velero/velero-cloud-credentials (key cloud). Set NOBLE_VELERO_AWS_ACCESS_KEY_ID and
NOBLE_VELERO_AWS_SECRET_ACCESS_KEY in .env, or noble_velero_aws_* extra-vars, or create the Secret manually
(see clusters/noble/bootstrap/velero/README.md).
when: noble_velero_install | default(false) | bool
- name: Optional object prefix argv for Helm
ansible.builtin.set_fact:
noble_velero_helm_prefix_argv: "{{ ['--set-string', 'configuration.backupStorageLocation[0].prefix=' ~ (noble_velero_s3_prefix | default(''))] if (noble_velero_s3_prefix | default('') | length > 0) else [] }}"
when: noble_velero_install | default(false) | bool
- name: Install Velero
ansible.builtin.command:
argv: "{{ ['helm', 'upgrade', '--install', 'velero', 'vmware-tanzu/velero', '--namespace', 'velero', '--version', noble_velero_chart_version, '-f', noble_repo_root ~ '/clusters/noble/bootstrap/velero/values.yaml', '--set-string', 'configuration.backupStorageLocation[0].bucket=' ~ noble_velero_s3_bucket, '--set-string', 'configuration.backupStorageLocation[0].config.s3Url=' ~ noble_velero_s3_url, '--set-string', 'configuration.backupStorageLocation[0].config.region=' ~ noble_velero_s3_region, '--set-string', 'configuration.backupStorageLocation[0].config.s3ForcePathStyle=' ~ noble_velero_s3_force_path_style] + (noble_velero_helm_prefix_argv | default([])) + ['--wait'] }}"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
when: noble_velero_install | default(false) | bool
changed_when: true

View File

@@ -0,0 +1,14 @@
---
proxmox_repo_debian_codename: "{{ ansible_facts['distribution_release'] | default('bookworm') }}"
proxmox_repo_disable_enterprise: true
proxmox_repo_disable_ceph_enterprise: true
proxmox_repo_enable_pve_no_subscription: true
proxmox_repo_enable_ceph_no_subscription: false
proxmox_no_subscription_notice_disable: true
proxmox_widget_toolkit_file: /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
# Bootstrap root SSH keys from the control machine so subsequent runs can use key auth.
proxmox_root_authorized_key_files:
- "{{ lookup('env', 'HOME') }}/.ssh/id_ed25519.pub"
- "{{ lookup('env', 'HOME') }}/.ssh/ansible.pub"

View File

@@ -0,0 +1,5 @@
---
- name: Restart pveproxy
ansible.builtin.service:
name: pveproxy
state: restarted

View File

@@ -0,0 +1,100 @@
---
- name: Check configured local public key files
ansible.builtin.stat:
path: "{{ item }}"
register: proxmox_root_pubkey_stats
loop: "{{ proxmox_root_authorized_key_files }}"
delegate_to: localhost
become: false
- name: Fail when a configured local public key file is missing
ansible.builtin.fail:
msg: "Configured key file does not exist on the control host: {{ item.item }}"
when: not item.stat.exists
loop: "{{ proxmox_root_pubkey_stats.results }}"
delegate_to: localhost
become: false
- name: Ensure root authorized_keys contains configured public keys
ansible.posix.authorized_key:
user: root
state: present
key: "{{ lookup('ansible.builtin.file', item) }}"
manage_dir: true
loop: "{{ proxmox_root_authorized_key_files }}"
- name: Remove enterprise repository lines from /etc/apt/sources.list
ansible.builtin.lineinfile:
path: /etc/apt/sources.list
regexp: ".*enterprise\\.proxmox\\.com.*"
state: absent
when:
- proxmox_repo_disable_enterprise | bool or proxmox_repo_disable_ceph_enterprise | bool
failed_when: false
- name: Find apt source files that contain Proxmox enterprise repositories
ansible.builtin.find:
paths: /etc/apt/sources.list.d
file_type: file
patterns:
- "*.list"
- "*.sources"
contains: "enterprise\\.proxmox\\.com"
use_regex: true
register: proxmox_enterprise_repo_files
when:
- proxmox_repo_disable_enterprise | bool or proxmox_repo_disable_ceph_enterprise | bool
- name: Remove enterprise repository lines from apt source files
ansible.builtin.lineinfile:
path: "{{ item.path }}"
regexp: ".*enterprise\\.proxmox\\.com.*"
state: absent
loop: "{{ proxmox_enterprise_repo_files.files | default([]) }}"
when:
- proxmox_repo_disable_enterprise | bool or proxmox_repo_disable_ceph_enterprise | bool
- name: Find apt source files that already contain pve-no-subscription
ansible.builtin.find:
paths: /etc/apt/sources.list.d
file_type: file
patterns:
- "*.list"
- "*.sources"
contains: "pve-no-subscription"
use_regex: false
register: proxmox_no_sub_repo_files
when: proxmox_repo_enable_pve_no_subscription | bool
- name: Ensure Proxmox no-subscription repository is configured when absent
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/pve-no-subscription.list
content: "deb http://download.proxmox.com/debian/pve {{ proxmox_repo_debian_codename }} pve-no-subscription\n"
mode: "0644"
when:
- proxmox_repo_enable_pve_no_subscription | bool
- (proxmox_no_sub_repo_files.matched | default(0) | int) == 0
- name: Remove duplicate pve-no-subscription.list when another source already provides it
ansible.builtin.file:
path: /etc/apt/sources.list.d/pve-no-subscription.list
state: absent
when:
- proxmox_repo_enable_pve_no_subscription | bool
- (proxmox_no_sub_repo_files.files | default([]) | map(attribute='path') | list | select('ne', '/etc/apt/sources.list.d/pve-no-subscription.list') | list | length) > 0
- name: Ensure Ceph no-subscription repository is configured
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/ceph-no-subscription.list
content: "deb http://download.proxmox.com/debian/ceph-{{ proxmox_repo_debian_codename }} {{ proxmox_repo_debian_codename }} no-subscription\n"
mode: "0644"
when: proxmox_repo_enable_ceph_no_subscription | bool
- name: Disable no-subscription pop-up in Proxmox UI
ansible.builtin.replace:
path: "{{ proxmox_widget_toolkit_file }}"
regexp: "if \\(data\\.status !== 'Active'\\)"
replace: "if (false)"
backup: true
when: proxmox_no_subscription_notice_disable | bool
notify: Restart pveproxy

View File

@@ -0,0 +1,7 @@
---
proxmox_cluster_enabled: true
proxmox_cluster_name: pve-cluster
proxmox_cluster_master: ""
proxmox_cluster_master_ip: ""
proxmox_cluster_force: false
proxmox_cluster_master_root_password: ""

View File

@@ -0,0 +1,63 @@
---
- name: Skip cluster role when disabled
ansible.builtin.meta: end_host
when: not (proxmox_cluster_enabled | bool)
- name: Check whether corosync cluster config exists
ansible.builtin.stat:
path: /etc/pve/corosync.conf
register: proxmox_cluster_corosync_conf
- name: Set effective Proxmox cluster master
ansible.builtin.set_fact:
proxmox_cluster_master_effective: "{{ proxmox_cluster_master | default(groups['proxmox_hosts'][0], true) }}"
- name: Set effective Proxmox cluster master IP
ansible.builtin.set_fact:
proxmox_cluster_master_ip_effective: >-
{{
proxmox_cluster_master_ip
| default(hostvars[proxmox_cluster_master_effective].ansible_host
| default(proxmox_cluster_master_effective), true)
}}
- name: Create cluster on designated master
ansible.builtin.command:
cmd: "pvecm create {{ proxmox_cluster_name }}"
when:
- inventory_hostname == proxmox_cluster_master_effective
- not proxmox_cluster_corosync_conf.stat.exists
- name: Ensure python3-pexpect is installed for password-based cluster join
ansible.builtin.apt:
name: python3-pexpect
state: present
update_cache: true
when:
- inventory_hostname != proxmox_cluster_master_effective
- not proxmox_cluster_corosync_conf.stat.exists
- proxmox_cluster_master_root_password | length > 0
- name: Join node to existing cluster (password provided)
ansible.builtin.expect:
command: >-
pvecm add {{ proxmox_cluster_master_ip_effective }}
{% if proxmox_cluster_force | bool %}--force{% endif %}
responses:
"Please enter superuser \\(root\\) password for '.*':": "{{ proxmox_cluster_master_root_password }}"
"password:": "{{ proxmox_cluster_master_root_password }}"
no_log: true
when:
- inventory_hostname != proxmox_cluster_master_effective
- not proxmox_cluster_corosync_conf.stat.exists
- proxmox_cluster_master_root_password | length > 0
- name: Join node to existing cluster (SSH trust/no prompt)
ansible.builtin.command:
cmd: >-
pvecm add {{ proxmox_cluster_master_ip_effective }}
{% if proxmox_cluster_force | bool %}--force{% endif %}
when:
- inventory_hostname != proxmox_cluster_master_effective
- not proxmox_cluster_corosync_conf.stat.exists
- proxmox_cluster_master_root_password | length == 0

View File

@@ -0,0 +1,6 @@
---
proxmox_upgrade_apt_cache_valid_time: 3600
proxmox_upgrade_autoremove: true
proxmox_upgrade_autoclean: true
proxmox_upgrade_reboot_if_required: true
proxmox_upgrade_reboot_timeout: 1800

View File

@@ -0,0 +1,30 @@
---
- name: Refresh apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: "{{ proxmox_upgrade_apt_cache_valid_time }}"
- name: Upgrade Proxmox host packages
ansible.builtin.apt:
upgrade: dist
- name: Remove orphaned packages
ansible.builtin.apt:
autoremove: "{{ proxmox_upgrade_autoremove }}"
- name: Clean apt package cache
ansible.builtin.apt:
autoclean: "{{ proxmox_upgrade_autoclean }}"
- name: Check if reboot is required
ansible.builtin.stat:
path: /var/run/reboot-required
register: proxmox_reboot_required_file
- name: Reboot when required by package upgrades
ansible.builtin.reboot:
reboot_timeout: "{{ proxmox_upgrade_reboot_timeout }}"
msg: "Reboot initiated by Ansible Proxmox maintenance playbook"
when:
- proxmox_upgrade_reboot_if_required | bool
- proxmox_reboot_required_file.stat.exists | default(false)

View File

@@ -1,23 +0,0 @@
---
# Defaults for proxmox_provision role
# Connection Details (fallbacks, but ideally inherited from inventory group_vars)
proxmox_api_host: "{{ ansible_host | default(inventory_hostname) }}"
proxmox_node: "{{ inventory_hostname }}"
# VM Details
vmid: 0 # 0 lets Proxmox choose next available, or specify fixed ID
vm_name: "new-vm"
template_name: "ubuntu-2204-cloud"
vm_memory: 2048
vm_cores: 2
vm_storage: "local-lvm"
vm_net_bridge: "vmbr0"
# Cloud Init / User Data
ci_user: "ubuntu"
# ssh_keys should be a list of public keys
ssh_keys: []
# State
vm_state: started

View File

@@ -1,39 +0,0 @@
---
- name: Provision VM from Template
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_password: "{{ proxmox_api_password }}"
# Use remote host verification if you have valid certs, else ignore
validate_certs: false
node: "{{ proxmox_node }}"
vmid: "{{ vmid if vmid | int > 0 else omit }}"
name: "{{ vm_name }}"
clone: "{{ template_name }}"
full: true # Full clone
cores: "{{ vm_cores }}"
memory: "{{ vm_memory }}"
storage: "{{ vm_storage }}"
net:
net0: "virtio,bridge={{ vm_net_bridge }}"
# Cloud Init
ciuser: "{{ ci_user }}"
sshkeys: "{{ ssh_keys | join('\n') }}"
ipconfig:
ipconfig0: "ip=dhcp"
state: "{{ vm_state }}"
register: vm_provision_result
- name: Debug Provision Result
debug:
var: vm_provision_result
# Note: Waiting for SSH requires knowing the IP.
# If qemu-guest-agent is installed in the template, we can fetch it.
# Otherwise, we might need a fixed IP or DNS check.

View File

@@ -1,41 +0,0 @@
---
# Defaults for proxmox_template_manage role
# Target Proxmox Node (where commands run)
proxmox_node: "{{ inventory_hostname }}"
# Storage Pool for Disks
storage_pool: local-lvm
# Template ID and Name
template_id: 9000
template_name: ubuntu-2204-cloud-template
# Hardware Specs
template_memory: 2048
template_cores: 2
# Image Source
# Options: 'list' (use image_alias) or 'url' (use custom_image_url)
image_source_type: list
image_list:
ubuntu-22.04:
url: "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
filename: "ubuntu-22.04-server-cloudimg-amd64.img"
ubuntu-24.04:
url: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
filename: "ubuntu-24.04-server-cloudimg-amd64.img"
debian-12:
url: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
filename: "debian-12-generic-amd64.qcow2"
image_alias: ubuntu-22.04
custom_image_url: ""
custom_image_name: "custom-image.img"
# Cloud Init / SSH
# Optional: Embed a default admin key into the template
embed_admin_ssh_key: false
admin_ssh_key: ""

View File

@@ -1,90 +0,0 @@
---
- name: Resolve Image Variables (List)
set_fact:
_image_url: "{{ image_list[image_alias].url }}"
_image_name: "{{ image_list[image_alias].filename }}"
when: image_source_type == 'list'
- name: Resolve Image Variables (URL)
set_fact:
_image_url: "{{ custom_image_url }}"
_image_name: "{{ custom_image_name }}"
when: image_source_type == 'url'
- name: Check if template already exists
command: "qm status {{ template_id }}"
register: vm_status
failed_when: false
changed_when: false
- name: Fail if template ID exists
fail:
msg: "VM ID {{ template_id }} already exists. Please delete it or choose a different ID."
when: vm_status.rc == 0
- name: Download Cloud Image
get_url:
url: "{{ _image_url }}"
dest: "/tmp/{{ _image_name }}"
mode: '0644'
- name: Install libguestfs-tools (for virt-customize if needed)
apt:
name: libguestfs-tools
state: present
ignore_errors: yes
- name: Create VM with hardware config
command: >
qm create {{ template_id }}
--name "{{ template_name }}"
--memory {{ template_memory }}
--core {{ template_cores }}
--net0 virtio,bridge=vmbr0
--scsihw virtio-scsi-pci
--ostype l26
--serial0 socket --vga serial0
- name: Import Disk
command: "qm importdisk {{ template_id }} /tmp/{{ _image_name }} {{ storage_pool }}"
- name: Attach Disk to SCSI
command: "qm set {{ template_id }} --scsi0 {{ storage_pool }}:vm-{{ template_id }}-disk-0"
- name: Add Cloud-Init Drive
command: "qm set {{ template_id }} --ide2 {{ storage_pool }}:cloudinit"
- name: Set Boot Order
command: "qm set {{ template_id }} --boot c --bootdisk scsi0"
- name: Configure Cloud-Init (Optional Admin Key)
block:
- name: Prepare SSH Keys File
copy:
content: "{{ admin_ssh_key }}"
dest: "/tmp/ssh_key_{{ template_id }}.pub"
mode: '0600'
- name: Set SSH Keys on Template
command: "qm set {{ template_id }} --sshkeys /tmp/ssh_key_{{ template_id }}.pub"
- name: Cleanup Key File
file:
path: "/tmp/ssh_key_{{ template_id }}.pub"
state: absent
when: embed_admin_ssh_key | bool and admin_ssh_key | length > 0
- name: Set Cloud-Init IP Config (DHCP)
command: "qm set {{ template_id }} --ipconfig0 ip=dhcp"
- name: Resize Disk (to Minimum 10G)
command: "qm resize {{ template_id }} scsi0 10G"
ignore_errors: yes
- name: Convert to Template
command: "qm template {{ template_id }}"
- name: Remove Downloaded Image
file:
path: "/tmp/{{ _image_name }}"
state: absent

View File

@@ -1,58 +0,0 @@
---
# Defaults for proxmox_vm role
# Action to perform: create_template, create_vm, delete_vm, backup_vm
proxmox_action: create_vm
# Common settings
storage_pool: Lithium
vmid: 9000
target_node: "{{ inventory_hostname }}"
# --- Template Creation Settings ---
# Image Source Selection
# Options: 'list' (use image_alias) or 'url' (use custom_image_url)
image_source_type: list
# Predefined Image List
# You can select these by setting image_alias
image_list:
ubuntu-22.04:
url: "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
filename: "ubuntu-22.04-server-cloudimg-amd64.img"
ubuntu-24.04:
url: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
filename: "ubuntu-24.04-server-cloudimg-amd64.img"
debian-12:
url: "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2"
filename: "debian-12-generic-amd64.qcow2"
# Selection (Default)
image_alias: ubuntu-22.04
# Custom URL (Used if image_source_type is 'url')
custom_image_url: ""
custom_image_name: "custom-image.img"
# Template Config
template_name: ubuntu-cloud-template
memory: 2048
cores: 2
# --- SSH Key Configuration ---
# The Admin Key is always added
admin_ssh_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..." # REPLACE THIS with your actual public key
# Additional keys (list of strings)
additional_ssh_keys: []
# --- Create VM Settings (Cloning) ---
new_vm_name: new-vm
clone_full: true # Full clone (independent) vs Linked clone
start_after_create: true
# --- Backup Settings ---
backup_mode: snapshot # snapshot, suspend, stop
backup_compress: zstd
backup_storage: Lithium

View File

@@ -1,7 +0,0 @@
---
- name: Create VM Backup
command: >
vzdump {{ vmid }}
--mode {{ backup_mode }}
--compress {{ backup_compress }}
--storage {{ backup_storage }}

View File

@@ -1,91 +0,0 @@
---
- name: Resolve Image Variables (List)
set_fact:
_image_url: "{{ image_list[image_alias].url }}"
_image_name: "{{ image_list[image_alias].filename }}"
when: image_source_type == 'list'
- name: Resolve Image Variables (URL)
set_fact:
_image_url: "{{ custom_image_url }}"
_image_name: "{{ custom_image_name }}"
when: image_source_type == 'url'
- name: Check if template already exists
command: "qm status {{ vmid }}"
register: vm_status
failed_when: false
changed_when: false
- name: Fail if template ID exists
fail:
msg: "VM ID {{ vmid }} already exists. Please choose a different ID or delete the existing VM."
when: vm_status.rc == 0
- name: Download Cloud Image
get_url:
url: "{{ _image_url }}"
dest: "/tmp/{{ _image_name }}"
mode: '0644'
- name: Install libguestfs-tools
apt:
name: libguestfs-tools
state: present
ignore_errors: yes
- name: Create VM with hardware config
command: >
qm create {{ vmid }}
--name "{{ template_name }}"
--memory {{ memory }}
--core {{ cores }}
--net0 virtio,bridge=vmbr0
--scsihw virtio-scsi-pci
--ostype l26
--serial0 socket --vga serial0
- name: Import Disk
command: "qm importdisk {{ vmid }} /tmp/{{ _image_name }} {{ storage_pool }}"
- name: Attach Disk to SCSI
command: "qm set {{ vmid }} --scsi0 {{ storage_pool }}:vm-{{ vmid }}-disk-0"
- name: Add Cloud-Init Drive
command: "qm set {{ vmid }} --ide2 {{ storage_pool }}:cloudinit"
- name: Set Boot Order
command: "qm set {{ vmid }} --boot c --bootdisk scsi0"
- name: Prepare SSH Keys File
copy:
content: |
{{ admin_ssh_key }}
{% for key in additional_ssh_keys %}
{{ key }}
{% endfor %}
dest: "/tmp/ssh_keys_{{ vmid }}.pub"
mode: '0600'
- name: Configure Cloud-Init (SSH Keys, User, IP)
command: >
qm set {{ vmid }}
--sshkeys /tmp/ssh_keys_{{ vmid }}.pub
--ipconfig0 ip=dhcp
- name: Resize Disk (Default 10G)
command: "qm resize {{ vmid }} scsi0 10G"
ignore_errors: yes
- name: Convert to Template
command: "qm template {{ vmid }}"
- name: Remove Downloaded Image
file:
path: "/tmp/{{ _image_name }}"
state: absent
- name: Remove Temporary SSH Keys File
file:
path: "/tmp/ssh_keys_{{ vmid }}.pub"
state: absent

View File

@@ -1,11 +0,0 @@
---
- name: Clone VM from Template
command: >
qm clone {{ vmid }} {{ new_vmid }}
--name "{{ new_vm_name }}"
--full {{ 1 if clone_full | bool else 0 }}
register: clone_result
- name: Start VM (Optional)
command: "qm start {{ new_vmid }}"
when: start_after_create | default(false) | bool

View File

@@ -1,7 +0,0 @@
---
- name: Stop VM (Force Stop)
command: "qm stop {{ vmid }}"
ignore_errors: yes
- name: Destroy VM
command: "qm destroy {{ vmid }} --purge"

View File

@@ -1,3 +0,0 @@
---
- name: Dispatch task based on action
include_tasks: "{{ proxmox_action }}.yml"

View File

@@ -0,0 +1,3 @@
---
# Set **true** to run `talhelper genconfig -o out` under **talos/** (requires talhelper + talconfig).
noble_talos_genconfig: false

Some files were not shown because too many files have changed in this diff Show More