Compare commits

...

6 Commits

Author SHA1 Message Date
dependabot[bot] ea95d1fe6e build(deps): bump gramps-project/grampsweb in /docker/personal/gramps
Bumps [gramps-project/grampsweb](https://github.com/gramps-project/gramps-web) from 26.6.2 to 26.7.0.
- [Release notes](https://github.com/gramps-project/gramps-web/releases)
- [Commits](https://github.com/gramps-project/gramps-web/compare/v26.6.2...v26.7.0)

---
updated-dependencies:
- dependency-name: gramps-project/grampsweb
  dependency-version: 26.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-22 00:43:40 +00:00
vhaudiquet 204b675f50 longhorn: tolerate failover node 2026-07-21 22:19:40 +02:00
vhaudiquet ebbea69288 infra/p330: add p330 talos node 2026-07-21 17:56:06 +02:00
vhaudiquet 71ddb95d2f kube: migrate to longhorn by default 2026-07-18 16:22:16 +02:00
vhaudiquet b10ca16816 coredns: move etcd pvc from nfs-csi to longhorn 2026-07-17 11:07:04 +02:00
vhaudiquet ae42ca3a1f longhorn: deploy longhorn to kube 2026-07-11 00:54:08 +02:00
18 changed files with 607 additions and 57 deletions
+3
View File
@@ -9,3 +9,6 @@ terraform.tfstate.backup
kubeconfig
talosconfig
# Ignore rendered Talos machine configs (may contain cluster secrets)
p330.yaml
+2 -2
View File
@@ -1,7 +1,7 @@
services:
grampsweb:
container_name: grampsweb
image: ghcr.io/gramps-project/grampsweb:26.6.2
image: ghcr.io/gramps-project/grampsweb:26.7.0
restart: always
networks:
- default
@@ -31,7 +31,7 @@ services:
grampsweb_celery:
container_name: grampsweb_celery
image: ghcr.io/gramps-project/grampsweb:26.6.2
image: ghcr.io/gramps-project/grampsweb:26.7.0
restart: always
environment:
- GRAMPSWEB_TREE="Gramps Web" # will create a new tree if not exists
+195
View File
@@ -0,0 +1,195 @@
# Talos node for the P330 — joins the r740 "kube" cluster.
terraform {
required_providers {
talos = {
source = "siderolabs/talos"
version = "0.9.0"
}
null = {
source = "hashicorp/null"
version = "3.2.3"
}
}
}
# Read the r740 kube module state to reuse the cluster secrets & endpoint.
# The r740 module exposes: client_configuration, machine_secrets, cluster_name,
# cluster_endpoint, kube_host.
data "terraform_remote_state" "r740_kube" {
backend = var.r740_backend
config = var.r740_backend == "local" ? {
path = "${var.r740_state_path}/terraform.tfstate"
} : var.r740_backend_config
}
locals {
cluster_name = data.terraform_remote_state.r740_kube.outputs.cluster_name
cluster_endpoint = data.terraform_remote_state.r740_kube.outputs.cluster_endpoint
machine_secrets = data.terraform_remote_state.r740_kube.outputs.machine_secrets
client_config = data.terraform_remote_state.r740_kube.outputs.client_configuration
# kubeconfig produced by the r740 kube module — used to wait for the node and
# apply labels/taints. There is no in-tree kubernetes provider here on
# purpose: managing a `kubernetes_node` resource conflicts with the node
# object that kubelet itself creates, so we use a null_resource with kubectl
# to wait + label + taint idempotently.
kubeconfig_path = "${var.r740_state_path}/kubeconfig"
# Network config: static if node_subnet is provided, otherwise Talos DHCPs.
static_network = var.node_subnet == null ? {} : {
interfaces = [{
interface = var.network_interface
addresses = [var.node_subnet]
routes = var.node_gateway == null ? [] : [{ gateway = var.node_gateway }]
}]
}
network_patch = {
nameservers = var.nameservers
}
network_patch_merged = merge(local.network_patch, local.static_network)
machine_patch = {
install = {
image = var.installer_image
disk = var.install_disk
}
network = merge(local.network_patch_merged, {
# Pin the Kubernetes node name. Talos otherwise auto-generates a hostname
# (e.g. "talos-8ec-vd1"), so the node registers with that random name
# instead of var.p330_node_name — and our label/taint null_resource waits
# for the wrong node. Setting machine.network.hostname fixes the node name.
hostname = var.p330_node_name
})
# Kernel modules required by Longhorn (iSCSI + ext4) — must match the
# control-plane nodes so Longhorn can schedule replicas on the failover node.
kernel = {
modules = [
{ name = "iscsi_tcp" },
{ name = "libiscsi" },
{ name = "scsi_transport_iscsi" },
{ name = "ext4" },
]
}
sysctls = {
"fs.inotify.max_user_instances" = "1024"
"fs.inotify.max_user_watches" = "1048576"
}
kubelet = {
# Keep the failover node from accumulating non-essential DaemonSet pods
# via the regular scheduler; the taint does the heavy lifting, this is
# belt-and-braces.
extraArgs = {
"register-with-taints" = "${var.failover_taint_key}=${var.failover_taint_value}:${var.failover_taint_effect}"
}
}
}
}
# Control-plane machine configuration. machine_type = "controlplane" makes
# Talos generate a join config that runs the apiserver/controller-manager/
# scheduler AND joins the existing etcd cluster as a new member (the cluster
# was already bootstrapped by the r740 module's talos_machine_bootstrap).
data "talos_machine_configuration" "p330" {
cluster_name = local.cluster_name
machine_type = "controlplane"
cluster_endpoint = local.cluster_endpoint
machine_secrets = local.machine_secrets
config_patches = [
yamlencode({
machine = local.machine_patch
})
]
}
# Rendered config is written to disk so it can also be applied manually with
# `talosctl apply-config --nodes <p330_host> --file p330.yaml` if needed.
resource "local_file" "p330_machine_config" {
filename = "${path.module}/p330.yaml"
content = data.talos_machine_configuration.p330.machine_configuration
}
# Apply the machine config to the running (maintenance-mode) node over the
# Talos API. Because the config patch contains a `machine.install` block, when
# Talos receives this config on a node booted from the USB (maintenance) image
# it installs itself to install.disk and reboots into the installed system.
# For a controlplane node it then joins the existing etcd cluster as a new
# member and runs the control-plane components; for a worker it just registers
# via kubelet.
resource "talos_machine_configuration_apply" "p330" {
client_configuration = local.client_config
machine_configuration_input = data.talos_machine_configuration.p330.machine_configuration
node = var.p330_host
depends_on = [local_file.p330_machine_config]
}
# Emit a talosconfig scoped to this node for ad-hoc `talosctl` use.
data "talos_client_configuration" "p330" {
cluster_name = local.cluster_name
client_configuration = local.client_config
nodes = [var.p330_host]
}
resource "local_file" "talosconfig" {
content = data.talos_client_configuration.p330.talos_config
filename = "${path.module}/talosconfig"
depends_on = [data.talos_client_configuration.p330]
}
# Wait for the node to register with Kubernetes (kubelet creates the Node
# object after Talos installs and reboots), then label it and (re)apply the
# failover taint. This is idempotent: kubectl exits 0 if the label/taint already
# exists. The taint is also set via kubelet `register-with-taints`, so this
# null_resource is a safety net for manual edits / drift.
resource "null_resource" "p330_node_label_and_taint" {
triggers = {
node = var.p330_node_name
key = var.failover_taint_key
value = var.failover_taint_value
effect = var.failover_taint_effect
kubeconfig = local.kubeconfig_path
}
provisioner "local-exec" {
# Wait for the node to show up, then label + taint. The wait loop is bounded
# by kubectl --timeout; tune it via TF_LOG / re-run if the node is slow to
# join (a controlplane node must first complete the etcd join handshake).
command = <<-EOT
set -euo pipefail
KUBECONFIG="${local.kubeconfig_path}"
export KUBECONFIG
NODE="${var.p330_node_name}"
echo "Waiting for node $NODE to be registered (kubelet creates the Node object once Talos has installed, rebooted and joined etcd)..."
# kubectl wait --for=condition=Ready fails instantly with NotFound if the
# node object doesn't exist yet, so poll for existence first.
# /bin/sh (dash) has no $SECONDS, so count iterations with a bounded loop.
tries=240 # 240 * 5s = 20 minutes max
until kubectl get node "$NODE" >/dev/null 2>&1; do
tries=$((tries - 1))
if [ "$tries" -le 0 ]; then
echo "Timed out waiting for node $NODE to register." >&2
exit 1
fi
sleep 5
done
echo "Node $NODE registered. Waiting for it to become Ready..."
# Now wait for Ready (a controlplane node needs etcd joined + apiserver up).
kubectl wait --for=condition=Ready "node/$NODE" --timeout=20m || \
kubectl wait --for=jsonpath='{.status.conditions[?(@.reason=="KubeletReady")].status}'=True "node/$NODE" --timeout=20m
# Failover marker + taint (applied to both controlplane and worker nodes).
kubectl label --overwrite node "$NODE" homeprod.io/failover=true
# Apply the taint idempotently (kubectl taint --overwrite is a no-op if it exists).
kubectl taint --overwrite node "$NODE" \
"${var.failover_taint_key}=${var.failover_taint_value}:${var.failover_taint_effect}"
echo "Node $NODE ready, labeled and tainted for failover-only scheduling."
EOT
}
depends_on = [talos_machine_configuration_apply.p330]
}
+118
View File
@@ -0,0 +1,118 @@
# Variables for the P330 Talos worker node that joins the r740 cluster.
variable "p330_host" {
description = "Reachable IP/hostname of the P330 Talos node (for Talos API access)."
type = string
}
variable "p330_node_name" {
description = "Kubernetes/Talos node name for the P330 (e.g. p330)."
type = string
default = "p330"
}
variable "r740_state_path" {
description = <<EOT
Path to the Terraform state of the r740 kube module, used by terraform_remote_state
to read the cluster secrets and endpoint so this node can join the existing cluster.
Path is resolved by terraform_remote_state relative to the working directory where
terraform runs (this module dir). The default points two levels up to the repo
root and back down to the r740 kube module.
EOT
type = string
default = "../../r740/kube"
}
variable "r740_backend" {
description = <<EOT
Terraform backend type used by the r740 kube module.
Set to "local" (default) when r740 uses a local tfstate file in its own directory,
or the matching remote backend name ("s3", "remote", ...) if the r740 module uses
a configured backend.
EOT
type = string
default = "local"
}
variable "r740_backend_config" {
description = <<EOT
Backend configuration map passed to terraform_remote_state when r740_backend is
not "local". For a local backend this is ignored.
EOT
type = map(string)
default = {}
}
variable "installer_image" {
description = <<EOT
Talos installer image to use on the P330 (bare metal).
Must be a **metal** Image Factory build that includes ixgbe.allow_unsupported_sfp=1
in the kernel command line (sd-boot/UKI ignores machine.install.extraKernelArgs, so
the param must be baked into the image). The default is a custom factory build
(a18165114...).
EOT
type = string
default = "factory.talos.dev/installer/a18165114f80c28601d05bc4ff1f6ea6d6b214882c5b9af7928aaf4d09741beb:v1.13.6"
}
variable "install_disk" {
description = "Block device path to install Talos on (e.g. /dev/sda, /dev/nvme0n1)."
type = string
default = "/dev/nvme0n1"
}
variable "node_subnet" {
description = <<EOT
Static IPv4 address in CIDR notation for the P330 node (e.g. 10.1.2.132/24).
Set to null to use DHCP. A static address is recommended for a failover node so
DNS/affinity rules stay stable.
EOT
type = string
default = "10.1.2.132/24"
}
variable "node_gateway" {
description = "IPv4 gateway for the P330 node. Ignored when node_subnet is null."
type = string
default = "10.1.2.1"
}
variable "network_interface" {
description = <<EOT
Primary network interface name on the P330. Defaults to enp3s0f1 (the 10G Intel
X520 NIC), which must be on the same L2/subnet as the r740 control plane so etcd
peer traffic (TLS-verified against the r740's etcd cert SANs) doesn't cross a
router. eno1 (1G) is left unconfigured.
EOT
type = string
default = "enp3s0f1"
}
variable "nameservers" {
description = "DNS nameservers configured on the node (must work independently of kube)."
type = list(string)
default = ["10.1.2.148", "1.1.1.1"]
}
variable "failover_taint_key" {
description = "Taint key applied to the node to reserve it for failover workloads."
type = string
default = "dedicated"
}
variable "failover_taint_value" {
description = "Taint value applied to the node."
type = string
default = "failover"
}
variable "failover_taint_effect" {
description = "Taint effect applied to the node (NoSchedule / NoExecute)."
type = string
default = "NoSchedule"
validation {
condition = contains(["NoSchedule", "PreferNoSchedule", "NoExecute"], var.failover_taint_effect)
error_message = "failover_taint_effect must be NoSchedule, PreferNoSchedule or NoExecute."
}
}
+52 -1
View File
@@ -40,7 +40,9 @@ data "talos_machine_configuration" "kube" {
yamlencode({
machine = {
install = {
image = "factory.talos.dev/installer/ce4c980550dd2ab1b17bbf2b08801c7eb59418eafe8f279833297925d67c7515:v1.11.5"
# Image Factory image with iSCSI extension for Longhorn.
# Generated at https://factory.talos.dev — siderolabs/iscsi-tools + qemu-guest-agent
image = "factory.talos.dev/installer/dc7b152cb3ea99b821fcb7340ce7168313ce393d663740b791c36f6e95fc8586:v1.13.6"
}
network = {
nameservers = [
@@ -53,6 +55,28 @@ data "talos_machine_configuration" "kube" {
certSANs = [
"${var.kube_host}", "${var.kube_hostname}"
]
# Kernel modules required by Longhorn (iSCSI + ext4)
kernel = {
modules = [
{
name = "iscsi_tcp"
},
{
name = "libiscsi"
},
{
name = "scsi_transport_iscsi"
},
{
name = "ext4"
},
]
}
# Sysctls for Longhorn
sysctls = {
"fs.inotify.max_user_instances" = "1024"
"fs.inotify.max_user_watches" = "1048576"
}
}
cluster = {
clusterName = "kube-${var.physical_hostname}"
@@ -106,6 +130,33 @@ output "kubeconfig" {
value = talos_cluster_kubeconfig.kube.kubeconfig_raw
}
output "client_configuration" {
description = "Talos client configuration (sensitive) used to manage nodes."
sensitive = true
value = talos_machine_secrets.kube.client_configuration
}
output "machine_secrets" {
description = "Talos machine secrets (sensitive) used to generate node configs."
sensitive = true
value = talos_machine_secrets.kube.machine_secrets
}
output "cluster_name" {
description = "Name of the Talos cluster the worker joins."
value = "kube-${var.physical_hostname}"
}
output "cluster_endpoint" {
description = "Endpoint (host:port) of the Talos/Kubernetes API on the cluster."
value = "https://${var.kube_host}:6443"
}
output "kube_host" {
description = "Reachable IP/hostname of the control-plane node."
value = var.kube_host
}
resource "local_file" "kubeconfig" {
content = "${talos_cluster_kubeconfig.kube.kubeconfig_raw}"
filename = "${path.module}/kubeconfig"
@@ -19,9 +19,9 @@ pushShard: ""
# with these credentials on every cert-manager renewal.
recoveryAdmin:
enabled: true
username: ENC[AES256_GCM,data:83oORcE=,iv:X07gQXkAx2wdqEcOPi5mGTVoFkJjDHBKMCYUjpg3qv4=,tag:qvspLaPSsrld4UV/qXb0cw==,type:str]
username: ENC[AES256_GCM,data:kpf88Vc=,iv:k2kGcGlH0jQkLmb35V4+IIrBpDM93PaG6atcO/SLDB4=,tag:qZIR2LZlv1g0XBkEh0y8qA==,type:str]
password: ""
existingSecret: ENC[AES256_GCM,data:KuZYrQk+YQhciNRTHqOlZdqZWJ3o15I=,iv:x/cm4USFZqWE5xuuF7tUPm593NqX1kbmeKv0x2wzk8s=,tag:G9FRYYaJhWovN+LB/gLaoQ==,type:str]
existingSecret: ENC[AES256_GCM,data:DnjFoj8VNeqHmwM/KVebjOOHvYHPgZQ=,iv:JWBs6OeZzsa+8uJt/FjptI6t+tf+CRQDKA7149rBg1o=,tag:M4CXA2k8M65mbSUF0mnGvQ==,type:str]
usernameKey: username
passwordKey: password
# Recovery mode suspends mail services and exposes only the management listener.
@@ -40,7 +40,7 @@ bootstrap:
# certificate.yaml (letsencrypt-production, Cloudflare DNS-01).
mailTls:
enabled: true
existingSecret: ENC[AES256_GCM,data:Y+LfFx7nuCSMGqo2o3QoHG1Br+MYlQ==,iv:dLJDYmK2gEmNq5VnbpEJWFM1xLLT5jXqxB2WQQ4dr3E=,tag:Ya2cTf/ZpJdzFnwH53iZZQ==,type:str]
existingSecret: ENC[AES256_GCM,data:e8rzv2mvlpoZR/vMfd5sgNuYLDvptQ==,iv:qIQfpHxAOameSdDzOHh89n+RIOd9CcJcl0oRumSHAQQ=,tag:xwbQR5Jabvz+lEju5zuW2g==,type:str]
certKey: tls.crt
privateKeyKey: tls.key
mountPath: /etc/stalwart/tls/ingress
@@ -96,7 +96,7 @@ ingress:
persistence:
enabled: true
accessMode: ReadWriteOnce
storageClass: ""
storageClass: longhorn
size: 50Gi
resources:
requests:
@@ -120,27 +120,27 @@ podSecurityContext:
type: RuntimeDefault
containerSecurityContext: {}
sops:
lastmodified: "2026-07-03T13:00:45Z"
mac: ENC[AES256_GCM,data:qoGDDt03xtJbPH+I3edSsFJagZSlCcMk4XoFc/I2QZEm+45DjsCWe9Q7e9zhn5kF3HBNBwNCgEzJmq2Fn/vemMWHwI4+QZW6nkk5cYTb3r8+WysSeUyt2+Y7EjyQGwI6+2LrCBlJIqmKfpPtT7Y8ZbwRBedoVqFtnNVH5441FMY=,iv:fvfnedCEGXyPw4yRLCtKoAnzz8h3jyJOwpqA33NNJOI=,tag:fPd7RJAMMounXVkIoKrptQ==,type:str]
lastmodified: "2026-07-18T14:22:17Z"
mac: ENC[AES256_GCM,data:JjN4ziOnd65U8AzI9ZnMiPUBiYFGqjGjM3HJ7XoSJuJ+fgYjOH+WX/U2IAfSeqEIMkzvP435BxKGoylrwEVM1k5v07slul5zEclPZglphDjiX+md403lVDtYEXdCSo0+3nqv1DFEQDkKItEEEz882sZBbhMotBHhgcd41RSA1aY=,iv:MaSmisJoj3aobpMw1HGgEOktegX2LtOaXLh3601xgvc=,tag:acWtTiu4ub7GDNp8nM/yVA==,type:str]
pgp:
- created_at: "2026-07-03T13:00:45Z"
- created_at: "2026-07-18T14:22:17Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hQIMA7uy4qQr71wiAQ//WA7P2WeKV6QFKotw5UP1MXcdI9THypFFly4sweJ5MC+e
Lzy/zCidGCM3Cu+dJaaPJNKtfPfagQJ9FzHrnvRcHoCbKS7sTAqugdwt4uyplem9
l03kQWZfDj8R8MHQnAKWNmJDiJkjCoqHdR5f2JtS+n7MngI53SecY2fsFOXRbYJI
hfH1BR+agLjIHc+gOF340GJp4UT+Q6BZOKg8K5O4PYk1Xwj3pi6YFuhiedKHSybF
Ij9OWdTqpofeG3fhwd3jHLW+0WYiFNrVlchfClEDWMMc0UfS6Bfo96IL0JIoYlHD
4Zt8EEyjvAJ5478RTKTobMlsSUC5U51Wn6paapZ5zicCHDzdP2yTtwtSKK0Mdp3W
qt/pZoR4EyM+I2XCKp+z7N5FtH8cahtfZiUOBwQ4NK9Fz+2UBxtP2QzGj8YpJmWu
OkbGoSgwn+8bcNnRwnADTzdiTWf/a56WErp1NvuqF8VqdGzsJR9EgyYwENl+m+U8
fbTY58iAJDDpY5ipPL5v+lo0Fw5DrnQ0NPAm1ORi+rMJy0KUjgAzdS6lL5oWGfAC
rzbzThGpRt0E7O296pht8TCBFHqiKbiOdGEBXO3+w9NrvOgULno4+s88vu6KMvFL
s3MHz7oIgfwel7V6bVc/ZQvHYS3YHjG41fwn00cpKI/WRN8z5MnSwwkegD2Wv8fS
XgEqL6kQPgk97779c0jo9CJA8O6QaSvGyd1j680tN1NN9V64rJOaj/818SgwFLrA
XWaqrOnz8FIspqO836t6OaLq5WwzOtOR6zs7cUk6h1RHgwlFQ+0n1o6qYf6Q+CA=
=HnWo
hQIMA7uy4qQr71wiAQ/9G7UtHO7Ux0YNAPlnkVmU5BI+crzwbWPhdaUZFf5dmCkg
r67EAi/DjqFHR9Ew1Dhpn0InTA/Ld+mGZAAbWeP7xhIEQ+AqACmm/Oxgf3IkHKzE
P7dvco6lQO50W/m3fiU0Pv4A7w3Oq+AdFHcPr3UBi8YGYLgkCbzLs2r9JNviCJsl
uuvz438g92aXfa5JcyUjzxtxKFjzHrZ8seDx38X4fveS/mROpRqiw+G8aQ0BlXDM
9wfiQtiM9CcuahPUWopv4RCOjuqrSc14sxv9xRGsYt1YZFxZ/4Q7rqM+HXBsFp4u
8r4MgE+EflrhKp1aWPGnSn2kalDy3o+yFuk7IhWsrSKsNIU4j1J5xCfhliey+kNJ
387cVf7Bjw+iqoy+qiV7Q7XGip6NgF8WwSudZhznv/pS6LL5H5yHaKxjz5rny/TQ
0upK///2YGuiE66sBb8h2sqzIgr6hZecvb2VdY7BQX4FCo2uAmxTEh+h1fHlervc
i/03B9kNSjP5fSufhE9lsLl0oZgaFIKSpkzLlPdy6z0X5vDEXJGSL+qFudo9RKtf
aaEwaQ+I+snmhWyBeeywxpdyRE5g3xhRwuwaxp7zzVt5ykCnyrZIPvzaSxu/QjaF
jCUmvPp0ZLzAB2xY9AMwOHIrym9lPmcBEEqCiHywCauOQzsGJE3vLF8x3/nzZnvS
XgEE9z1NxK8o0ntL+JZO3+GYvm28Hyw0eXgDJbjJ+tlBYeWH8lfBUOsk2V485OJZ
4SDNvxzxpyF/4RJY4P4KR7HgwDC8gOCsDgqcyn1s4KzECXyQlqmrXxOrL8cFRhY=
=aeJw
-----END PGP MESSAGE-----
fp: DC6910268E657FF70BA7EC289974494E76938DDC
encrypted_regex: ^(password|value|ssh-key|api-key|user|username|privateKey|clientSecret|clientId|apiKey|extraArgs.*|.*Secret.*|extraEnvVars|.*SECRET.*|.*secret.*|key|.*Password|.*\.ya?ml)$
+1
View File
@@ -11,6 +11,7 @@ resources:
- system/caddy
- system/coredns
- system/csi-driver-nfs
- system/longhorn
- system/external-dns
- system/flux
- system/traefik
+23 -23
View File
@@ -18,46 +18,46 @@ configuration:
disableNewSSOUsers: false
PublicCredentialsEnabled: false
authURL: http://linkwarden.lan/api/v1/auth
authSecret: ENC[AES256_GCM,data:UUZagqAY6+9AM4gMv9WFsWFSzjLxAALUz2iGg3dBIrpP+JCdJWVbXIRpZ8bZPJxvwnTcZSPOsLHlqzeqJRHubA==,iv:xgPnCNp2dpy8UG/fjEHPUxdXYXEQzFn3K/EX9lfjpWo=,tag:1uFZDdgGUvMvyKHj93z4yw==,type:str]
authSecret: ENC[AES256_GCM,data:Qaxb9o3xtrDo9rsxHFpm6tkRd7OWPDyjiIyoPvc1SiIQIthOwXyHQ0b7YAW8rGg1vKBg2fDCIUK4Kgoyr26qUw==,iv:KFt4KqTUwB830flAmxfw7tQrLktacTtWmYDjBAiugk4=,tag:IAi0x3AlCdCTXz7MBryg8A==,type:str]
sso:
enabled: true
authentik:
enabled: true
clientId: ENC[AES256_GCM,data:6XqAMjwywGgZK23ywm1GKWpIhIrXNa28Z52+0J1yhocBxLXoJAiocw==,iv:DiIpcD0H6wnIkXCMPmhLEHTzE6qKQrotGZ7q7Lv2iME=,tag:Kx80RoPf7BFMWEO1lZ4wCQ==,type:str]
clientSecret: ENC[AES256_GCM,data:OO8vjjWVxzUNuY/Jhk348RykWnVRQ/xthunOyI4XVzCmT0WXAH/uWSupljfWQW8aj4WzUUHMpJOvFpd3iYTExFn6QqyBisOgVzS9jy4sP4qdhnSpu9jXzEUNmKyjQncl3QTQCcPZMjlGGf0oJzZgHivVWl6rFbnYQOJHDlu3r48=,iv:H2OvOpH2yxUivH8RTvSnM9k86i9jUJKcUmNPpSSos6g=,tag:XXTyOC+Hj7BvgZJ7qxEZyA==,type:str]
clientId: ENC[AES256_GCM,data:1u8Xe9ZMosAJWwPGZCloAFEQE6n0eTtBzaw149zsuL/yrVoiU8s7Pg==,iv:uxfiXfpocKbiFIu0dPq0So6Kdfr3iMFUM4hRJm3eQPQ=,tag:BxgV/Wl2ZE80+08uaDnsDw==,type:str]
clientSecret: ENC[AES256_GCM,data:LBIh0Jc2MtS5pvOhmFbWSrQaOMiM05e2i5aeMbDJJGNArQ7PGK+okbltWnZHonIwGU/sriFm/x4BMz+snrd+NgTUR2aPPXnomK6f9dEmSXNjLxgnVFUmgcT2P7zqlKUBjMT+SKjZLELfYGzysKTRkXHOCrOoGX8ZobEgOXGflpQ=,iv:iADfUCL30NrwPPof7r/ppO3Rt7ap4RoOkKpBuzyoO8A=,tag:lrt50iqFsZcEdBx25BY0PA==,type:str]
customName: Authentik
issuer: https://authentik.vhaudiquet.fr/application/o/linkwarden
persistence:
enabled: true
storageClass: nfs-csi
storageClass: longhorn
postgres:
enabled: true
host: linkwarden-postgresql.linkwarden.svc.kube-talos.lan
username: ENC[AES256_GCM,data:N+ozpU6TgAZSLA==,iv:XJT1fUe2XqSxpuuPfmzbyrJUUInUEhLyNddOX0jogZQ=,tag:z9rCjOKISMT+wpfwUzFd1Q==,type:str]
password: ENC[AES256_GCM,data:G7dF3Oo+1wzL9g==,iv:yAjh22rwaBhKtHgYAmQEjFSMFspOpG2hThwwjAo2KJk=,tag:DdXlyNk+TfR0IuSpQG3+Zg==,type:str]
username: ENC[AES256_GCM,data:eUTE15OnJZ4dBQ==,iv:bN7y9EwD9rzzH3pm6JJvFkSHmdXkCunui2tyerb3z98=,tag:apU0hdiJbp5fgKjesLfdrw==,type:str]
password: ENC[AES256_GCM,data:qA/FQZdMQG3vdQ==,iv:S0fBAfNiNUi1t7coaKFLloaXolv7l//tUVnSui0Tmo0=,tag:SbW/QBMoNqUSENkSCR83AQ==,type:str]
database: linkwarden
sops:
lastmodified: "2025-09-15T21:48:06Z"
mac: ENC[AES256_GCM,data:i9Mg17Z72cBJOSvmOlHkl0x0/FqhpCqS8iTGTYKlXacXi9E0nYYZr/y74qVjhubEaI1y6cRYXgaePI47lkyfvTzB3VGEpEs/y5fiL+PfiuV8Z18J9244+TyoasYR/a2sdRuJcY5jm6PSTKoGiwGnyZEthVwt1T38yr5msW3D79A=,iv:yj7uCySaoZcnO+7XsgB1EMfZOMwHSA0mtJXUq66MUgQ=,tag:YRvUG0/hj8Bjp+pMdJvePQ==,type:str]
lastmodified: "2026-07-18T14:22:17Z"
mac: ENC[AES256_GCM,data:DoHEvtWEetZGb/lCzQalLAMUlAZ7OVG66bDSaGY7s0i8mhDzRxrkvPq7FL07rnDQoLSW+SFSq6h7LB1PV1HodW35b4rmDzpCCHQ/S7Gj8WW/sX0t6dFpF1GlYeseqAMtNMPeMf095Q9LIHmQlILmjPAKbAuOg6shQ9oIesBzSDw=,iv:3D+wZjBHp0gZRDOvmVHaTIyZlC9IzMupvXfbvYYZVm4=,tag:ui9jt5/F/9NJ08P7MD+7AQ==,type:str]
pgp:
- created_at: "2025-09-15T21:48:06Z"
- created_at: "2026-07-18T14:22:16Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hQIMA7uy4qQr71wiAQ//dICUFaREdeVKhKkK7iPji73IRFZEnwoZwEnR0vtCBL3d
HXFfdXUMOVcXOvkkuF9zG52flUZBpVgilQz2W8Y7xI3tU8xX6wxmuz8LjegidCHk
R2xKAJ5iCEWZTIouFwg4fM399Mm9xhiIPuM4+25DAZnSJJAc7fQ2NaNaA8vjbJQ7
YUnqkmE+QPGbyeykudlTFfl/zZzrgKbQqWuUmD/vq1XckUaPxM5cdeFDsg87AZCs
o51lQ39b+CpWmdbDbJDF5nMWUrCzIz9okptoNfHM/5E0Jg53JBsXBvsqwWTOI5vo
djilDfsyu5nQj7UDstaax839T7P5gm5ass2/YtIjgrJUczyRt6vS97zuXLsbEGpH
6hXHan7OormqbmISnFrL68Q4w6JUZev43bdzqtX0EnYHNZVLXH3qAxD5EnuL/i20
oY/oMEteFh86Smw/LXwtWjVfPKYzNQ6GTkTCzM3hVcfMOzdFVPgYDM+5EXF+/oAB
+BGqZvWW4G3G9sbPTRSJ1k+toWuC/erY41r+g3+KHbW73YdqIVwBsTDAFhPPNbe5
hnr1JFLQ75GHdDRnKRl3nZSPc2j9JMUtMhX+rKwLb/0DoVjNz0lFut/Tj+6xiG/1
SqHTpROU1tkip+rP0shzdu218+hjrzj+cDKkawoqmpD9gOm83P7YEqrIwcBj+TPS
XgE3UHtwHdJM38kfPm1fkTuCejlAQ3Hsz6W4PSbXJfY8w1gO241dEzy+b0At9Rv2
6IklhRBAs4ioH/hD653yz0mkCYRpOLYDJU2iMWZdv61YLXb4rsnAwr6xLbDHDXM=
=69Pc
hQIMA7uy4qQr71wiAQ/+LxkvBdTDic8D31VBAtMiKJJ4nHAIpxja6gHsyVCmS+LP
VOqnEvvEBxf3ifXqwlYh/rY7ywjMyuQAW03zIlNq+9Ufv5QV9Py+UTiQV1mfmRoa
73kINswzbUGvH+HXVkcRGjusS/StPMWKC6jwyrov09gtTF1rJUfcfenqy2waHZOk
JhwcxhrPUnHGOsv4f1+91CCjKHux3/FVagpxJcEvxckMJweuZWm59z77dbwF1lQ3
ws4VMLKzt2wiYSULCDr5KM2YlYWkvzheqhwnOVKXk11ewM+6n8RHIvsE03nvkfRf
tJduJYmXnz249SM4hq3GvyMq+j3qShIMvvKRMv6+aYMZ3SFzAIxtV0dA3lCoA9oy
mGWvImrA7JX4GsG44tEzu0ECvoFV8lEb39QKBXBf8fN6UExasF4pSm276ZGG4Eom
IpfpuaB9oYL4TOEH0/56IEc2BRlvAm62ibra5jNCVLnLB0pzE3KdN7ZYMQyXooZi
dOa56z4KidI4N5oDTc63Hkg0tApdV0x+Rtm7qgdhEp/1i4zqciQqb8//yN6CAb7I
DkyPilrvNkZw95iGqpKVLrAp57BaFHLYNr0O7+WS9urCIUQl3Q37Nz2HyNLJt0Ej
i3VgiJUIoy5NcAIBIFvuAJDsXB+FQxzg+7XUZL44xxsSvA248Shz1CYH9CgwbszS
XgHdJTIcrq3ReybtFVDPtqMkLyItA1fbJzpCr4Fwk922hmEpqBeMHx1Upy81MZJj
T+4v8xBo7KR/6sx3U9kO/TjV7AhdTJPRlgG0WfnfLMncCkrvg4cQWxEIkySShFo=
=XPzS
-----END PGP MESSAGE-----
fp: DC6910268E657FF70BA7EC289974494E76938DDC
encrypted_regex: ^(password|value|ssh-key|api-key|user|username|privateKey|clientSecret|clientId|apiKey|extraArgs.*|.*Secret.*|extraEnvVars|.*SECRET.*|.*secret.*|key|.*Password|.*\.ya?ml)$
+1
View File
@@ -54,6 +54,7 @@ spec:
- metadata:
name: etcd-data
spec:
storageClassName: longhorn
accessModes: ["ReadWriteOnce"]
resources:
requests:
@@ -30,6 +30,9 @@ data:
docker-r740 IN A 10.1.2.212
truenas IN A 10.1.2.139
; P330
p330 IN A 10.1.2.132
; PVE
pve IN A 10.1.2.10
docker-homeprod IN A 10.1.2.12
+1 -1
View File
@@ -2,7 +2,7 @@ storageClass:
create: true
name: nfs-csi
annotations:
storageclass.kubernetes.io/is-default-class: "true"
storageclass.kubernetes.io/is-default-class: "false"
parameters:
server: truenas.lan
share: /mnt/fast_app_data/kube/
@@ -0,0 +1,13 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: longhorn-system
resources:
- namespace.yaml
- repository.yaml
- release.yaml
secretGenerator:
- name: longhorn-values
files:
- values.yaml=values.yaml
configurations:
- kustomizeconfig.yaml
@@ -0,0 +1,6 @@
nameReference:
- kind: Secret
version: v1
fieldSpecs:
- path: spec/valuesFrom/name
kind: HelmRelease
@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: longhorn-system
labels:
pod-security.kubernetes.io/enforce: privileged
+19
View File
@@ -0,0 +1,19 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: longhorn
namespace: longhorn-system
spec:
interval: 1m
chart:
spec:
sourceRef:
kind: HelmRepository
name: longhorn
namespace: longhorn-system
chart: longhorn
version: '1.12.0'
interval: 1m
valuesFrom:
- kind: Secret
name: longhorn-values
@@ -0,0 +1,8 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: longhorn
namespace: longhorn-system
spec:
interval: 1m
url: https://charts.longhorn.io
+99
View File
@@ -0,0 +1,99 @@
# Longhorn Helm Values
# Single-node R740 cluster, backup target on TrueNAS NFS
defaultSettings:
# Single node — only 1 replica needed
defaultReplicaCount: 1
# NFS backup target on TrueNAS
backupTarget: nfs://truenas.lan:/mnt/fast_app_data/longhorn-backup
# Allow full disk usage on single-node
storageOverProvisioningPercentage: 100
storageMinimalAvailablePercentage: 10
# Auto-salvage on node reboot (single-node)
autoSalvage: true
# Disable strict flushing for better single-node performance
strictSyncReplicas: false
persistence:
defaultClass: true
defaultClassReplicaCount: 1
# Retain volumes on PVC delete — safer during migration
reclaimPolicy: Retain
# Mount options for ext4
fstype: ext4
longhorn-ui:
replicas: 1
longhorn-manager:
tolerations:
- key: ENC[AES256_GCM,data:yWX0dGIIpvCVOeZbvY7Jjbr/t8mcyLiLmzHrCz0bwXIBRgG0Ig==,iv:m1wOItluKgql41BzKoBvgyjBaG7Fplz1c4ScONjZ/c8=,tag:g+7730DA8KzK/7ApgumDcQ==,type:str]
operator: Exists
effect: NoSchedule
# Tolerate failover nodes so longhorn-manager runs there
- key: ENC[AES256_GCM,data:PksAIja6KV8+,iv:oDJ47xFoCKysl8yf9EQVJp9PGLKJQMWUOsIAcirjzJM=,tag:n9HVtUsERVmlQqeXpObvWQ==,type:str]
value: ENC[AES256_GCM,data:JDsyDMHMh0k=,iv:D3oVkD3tsxVE1hvcMcCYogoD7IVetrAbMzp6VCTOF6s=,tag:/cx/tQuGf1ImsVTZkwwt6A==,type:str]
operator: Equal
effect: NoSchedule
longhorn-driver-deployer:
tolerations:
- key: ENC[AES256_GCM,data:lv255SLl898RJh8gx569wM+lL/YF8AUXhKuwEY5RUmw84z96Vg==,iv:3gY3OSeoVitIZd4rMgZK18bI9xtcsk1T0U0PphLZSZw=,tag:TMZLWxRTZB9fwNIThytnxw==,type:str]
operator: Exists
effect: NoSchedule
- key: ENC[AES256_GCM,data:dI3C6OPdnAW0,iv:G5vU4nypD3s/vffdQneasxHzUUXHsgUThjUtYKRfk7U=,tag:SD08w8Gz1WDaee/cM89hZA==,type:str]
value: ENC[AES256_GCM,data:GsbeabJMKjQ=,iv:qA+wC10BjpzMprPgoLeiBKAU1RcSef/yMfDzWbgAJ/s=,tag:9qS5tquOU7EkZ7hrd+VZhw==,type:str]
operator: Equal
effect: NoSchedule
# instance-manager is a DaemonSet that places engine/replica pods on storage
# nodes — it must tolerate the failover taint
longhorn-instance-manager:
tolerations:
- key: ENC[AES256_GCM,data:3EHDHYKTaDyk,iv:D6z907ohAGqkoytBZ7s1aWJI6tRPsF7N798MwF9um+Q=,tag:zvEM2dzoWx4kLbIrbVW45Q==,type:str]
value: ENC[AES256_GCM,data:D9mG3tOtQas=,iv:JpbQDYKMZvNQSkoxw+1zwTeIrdrIr09oa7v8tZupSb0=,tag:5ypuOjneHKzSsJEXE4enxA==,type:str]
operator: Equal
effect: NoSchedule
# Disable ingress for now
ingress:
enabled: false
# Recurring backup and snapshot schedules
# - Daily snapshots: fast local recovery from accidental deletes
# - Daily NFS backups: DR if node is lost
recurringJobs:
enable: true
jobs:
- name: daily-snapshot
task: snapshot
cron: 0 2 * * *
retain: 7
concurrency: 2
labels:
type: snapshot
- name: daily-backup
task: backup
cron: 0 3 * * *
retain: 7
concurrency: 2
labels:
type: backup
sops:
lastmodified: "2026-07-21T20:19:40Z"
mac: ENC[AES256_GCM,data:AxrJFHyOjoP5c/1uFhlSIwe0E/oIesu52EU7zIxwLVQ8p5r4Fa7/NpSbeK+K3lAuYTrUiXvjWdbmVO5IQhvd+PNj6nXPDxBO92eoxzMtit9m8mnAiEGGYayeJMU9DaKPlGEs3CUaYzAizP1Ub0n9g2iq8CRcHVO9zOQ/DfUk/tA=,iv:bQN9v/n0/Z1Sm/2Im1SGskjqnxJhAbBT19IC6HDSufA=,tag:qLxMS7HovkykXHqsvinLDQ==,type:str]
pgp:
- created_at: "2026-07-21T20:19:40Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hQIMA7uy4qQr71wiARAAoYpVy7elPl0SzogH1900DzVQsGV0SylAh4h40+YOmezu
k4y501ZRIRv/r0RiPp7pJ7o9zynVQdcsRos0h0GBy7BbzIXMJpjRrpqXIvSeqtgt
S5FJIUTkVraKo8kKtDvtbiN6rz7qwXkBsppF1euZRVVj33hCX8vqL6EvhP9FBOBO
354Q9h92F+D94+JQt3K8n3hW/qaQIcDhJ4AgC7I9NPrA+rb6alUWQPKfFDSlYUkb
mxrLz4DE5GOVwMhaE/PrKLYeV5+KLRdsK0zzQadxG4AUSwpK7ZwLIIcQ1L0zQ7ST
SxruIi5EahUsDxNKTZvv5NnUFZpncqKCFfnxF69xIlXTAXujdOfY/21HZ+mmCmKJ
kTmsNtlKE1ltDwzHiowdLFqzHlME83bdRtYPuMcvhAFSv7yuVd9zfXIjQNjPFmYc
55EedJBrvFbxWU9QP1dEUpcELGXroiOowjGM4cushYgeBZzXJkTMg7JmoB+HNiWY
Ld9XaJOOiJ87BI1zbhkMPo+L9NSF5zbtYkBiulSyb58gTuixTESnYxPxWC71EZLV
BxFEkapXY0Y89h6uFH7FKz0MiniojA4zvNFt2LQYICWXdUAJqqpeIRv88iUJkKDv
THVf7+wltQ4EHTuu4E0rCsg+3GeOIIYkBrLESjEsaSvQ/B+Iz9JgUFRkRr7lVvzS
XgFL/2+26cLT81wCUson9XW5X2GZV8SgfWTFAXCbaaTxluJU1l1jIyWUoWnTFF2S
tKqNQBSsUmYlXIyize8d+ueVQcldN91bwpOXVmVIbghFX2Cp3H+4R5qfJxpH6yA=
=03zX
-----END PGP MESSAGE-----
fp: DC6910268E657FF70BA7EC289974494E76938DDC
encrypted_regex: ^(password|value|ssh-key|api-key|user|username|privateKey|clientSecret|clientId|apiKey|extraArgs.*|.*Secret.*|extraEnvVars|.*SECRET.*|.*secret.*|key|.*Password|.*\.ya?ml)$
version: 3.10.2
+36 -9
View File
@@ -1,11 +1,38 @@
ingress:
main:
enabled: true
hosts:
- host: dashy.lan
paths:
- path: /
pathType: ImplementationSpecific
main:
enabled: true
hosts:
- host: dashy.lan
paths:
- path: /
pathType: ImplementationSpecific
persistence:
data:
enabled: true
data:
enabled: true
storageClass: longhorn
sops:
lastmodified: "2026-07-18T14:22:16Z"
mac: ENC[AES256_GCM,data:Svz0vne+sbEj5wlL4FHUrTPMHlprWnZqR/X3tEmpDIxKBD0C/C9G1LgPYBiHviHEiiD/XbxxgH9nGcJIo4blbWc6xRNyUjnhGoLPIPJlG9LclXVh7dm4w4bETq4VfQZjLpY6bhv0q2d5GgxI6i+Hp16FSk8Y4kReErmQdg/Vh9I=,iv:zt3Rjt9KapI7LMMlpA/MPWK8EgGVFRpLJ4d2r4P74eY=,tag:fo2CgvaoT6bz1xmYA9DvGg==,type:str]
pgp:
- created_at: "2026-07-18T14:22:16Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hQIMA7uy4qQr71wiAQ//RB59ULu3tagTsLKGU+2azz+yTgnRKUsauEhC2trzbpRO
Au0RYwQMyDmYwTAvIIRM5gWUGlIFpsiftpK6rZbvJiwb8npYVSF7/shqKzlqYiSi
+BHJCSqfk+emN9ULivxmq3i+t4YZ97Ap7uo+pUyBmpz4jis0vdiIDIszLwtJ3U0J
t6s2gVl4KE2STyVH+ShERG3yjSBuzvFpJVwSdAfGKeA8K6Umr1LQH/2JSlq8/2fV
Zubfu6aGXg564rsbZNs+P9/b5M4FmpSlSOlp6OLWnIosl9CfRVDynnRyLImvQ/8p
2Asf6vrAQZJVXHraGMfePNNA9sRxhMa8f1qgHDWGfHh8D8/7JwK5xoFWrNoO7Gb6
aex15qielE0N4jpiEC3t+jjRUQLdjMuSlRET/0RVrZRhZ4lUTOOINnw8eUic0vRz
OjPj/ffVI4+zqfq7GnopISWBtfMdc23Y+7IhL/8LWRJGLRSqB0fNQxWeS7dtze2o
2BWk+DsHiG/1bz2hdezuR+KTRoj7qPDI/eKPc1BpyHfVm8k1y3pFmwWY3rwGAGA0
6bB1VSBriE3pcvUiJw4N+Ja/C7gLcUVAtNKGLqxuWr+2bm/ExWItAbnZ/3cZzKtC
bULu5ic/zN3O3rRWCS5nFU+nD37pq1Iq3eHXOWygwNOJJcGGHl9+3j01y2iXUl3S
XgGKWMKthF+9AD1bASp8tZ1lfNuqyxjbdVyWb7xqRGa2jzWgAcVUaCWWaJU6yASF
LdXY6xVKr1DgBs666nv4xAESsgvZ9xP6e+YaxAr3piRuDhzoNFeveOD99tsgZOg=
=zi2x
-----END PGP MESSAGE-----
fp: DC6910268E657FF70BA7EC289974494E76938DDC
encrypted_regex: ^(password|value|ssh-key|api-key|user|username|privateKey|clientSecret|clientId|apiKey|extraArgs.*|.*Secret.*|extraEnvVars|.*SECRET.*|.*secret.*|key|.*Password|.*\.ya?ml)$
version: 3.10.2