Compare commits
56
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cb8e65d85 | ||
|
|
41cadcb3ac | ||
|
|
4d97dd6e1f | ||
|
|
6dede6c422 | ||
|
|
3b1bd4cd2e | ||
|
|
2c22f66539 | ||
|
|
c32dd3bdfd | ||
|
|
5c416f8578 | ||
|
|
0c37b0d757 | ||
|
|
d1eb310673 | ||
|
|
46f56f1eb8 | ||
|
|
e1558800a0 | ||
|
|
4be6ad740d | ||
|
|
b78b705c6c | ||
|
|
4cbcebf53f | ||
|
|
256801e481 | ||
|
|
f3513c0ebe | ||
|
|
41f114de58 | ||
|
|
65ea8521c9 | ||
|
|
5071d34bd5 | ||
|
|
ea8aca23e4 | ||
|
|
e6d71ba033 | ||
|
|
1f835e31b6 | ||
|
|
20fcdfe738 | ||
|
|
a8c1710cab | ||
|
|
4d556c1b4b | ||
|
|
371f18c85f | ||
|
|
0c56e0dc1a | ||
|
|
45befc8af8 | ||
|
|
1a18755025 | ||
|
|
a2b6456d97 | ||
|
|
6da80f7761 | ||
|
|
9bb5490c33 | ||
|
|
c58631adcd | ||
|
|
011301fdef | ||
|
|
4f8443f3e8 | ||
|
|
478f39d727 | ||
|
|
86681c9d5b | ||
|
|
1f3bdb648a | ||
|
|
7989113433 | ||
|
|
52e935af8b | ||
|
|
8be540a8af | ||
|
|
e1092c4c24 | ||
|
|
30fb6dcccd | ||
|
|
0708e7504f | ||
|
|
6060ebdfd3 | ||
|
|
19968c16fe | ||
|
|
5015d41f93 | ||
|
|
bdf4defe29 | ||
|
|
fa9c7ec965 | ||
|
|
817e61c66c | ||
|
|
8386bde784 | ||
|
|
3248c01d33 | ||
|
|
20be3ada61 | ||
|
|
8d9a6955f9 | ||
|
|
2eb943cbf4 |
@@ -243,7 +243,8 @@ steps:
|
||||
- AUTH=$(printf '%s:%s' "$GITEA_USER" "$GITEA_TOKEN" | base64 | tr -d '\n')
|
||||
- GH_AUTH=$(printf '%s:%s' "$GHCR_USER" "$GHCR_TOKEN" | base64 | tr -d '\n')
|
||||
- printf '{"auths":{"gitea.renner.uno":{"auth":"%s"},"ghcr.io":{"auth":"%s"}}}' "$AUTH" "$GH_AUTH" > /kaniko/.docker/config.json
|
||||
- TAG=$(date +%Y-%m-%d)
|
||||
# konairius/ gets one tag per build: Gitea's cleanup rule counts tags.
|
||||
- TAG=$(date +%Y-%m-%d-%H%M)
|
||||
- |
|
||||
/kaniko/executor \
|
||||
--context . \
|
||||
@@ -251,9 +252,7 @@ steps:
|
||||
--destination gitea.renner.uno/gitea_admin/alpine-jq:$TAG \
|
||||
--destination gitea.renner.uno/gitea_admin/alpine-jq:latest \
|
||||
--destination gitea.renner.uno/konairius/alpine-jq:$TAG \
|
||||
--destination gitea.renner.uno/konairius/alpine-jq:latest \
|
||||
--destination ghcr.io/konairius/alpine-jq:$TAG \
|
||||
--destination ghcr.io/konairius/alpine-jq:latest
|
||||
--destination ghcr.io/konairius/alpine-jq:$TAG
|
||||
|
||||
# Runtime image, not CI tooling: upstream containernetworking/plugins binaries
|
||||
# baked in, consumed by the multus cni-plugins-installer DaemonSet. Replaces a
|
||||
@@ -295,7 +294,8 @@ steps:
|
||||
- AUTH=$(printf '%s:%s' "$GITEA_USER" "$GITEA_TOKEN" | base64 | tr -d '\n')
|
||||
- GH_AUTH=$(printf '%s:%s' "$GHCR_USER" "$GHCR_TOKEN" | base64 | tr -d '\n')
|
||||
- printf '{"auths":{"gitea.renner.uno":{"auth":"%s"},"ghcr.io":{"auth":"%s"}}}' "$AUTH" "$GH_AUTH" > /kaniko/.docker/config.json
|
||||
- TAG=$(date +%Y-%m-%d)
|
||||
# konairius/ gets one tag per build: Gitea's cleanup rule counts tags.
|
||||
- TAG=$(date +%Y-%m-%d-%H%M)
|
||||
- |
|
||||
/kaniko/executor \
|
||||
--context . \
|
||||
@@ -303,9 +303,7 @@ steps:
|
||||
--destination gitea.renner.uno/gitea_admin/cni-plugins:$TAG \
|
||||
--destination gitea.renner.uno/gitea_admin/cni-plugins:latest \
|
||||
--destination gitea.renner.uno/konairius/cni-plugins:$TAG \
|
||||
--destination gitea.renner.uno/konairius/cni-plugins:latest \
|
||||
--destination ghcr.io/konairius/cni-plugins:$TAG \
|
||||
--destination ghcr.io/konairius/cni-plugins:latest
|
||||
--destination ghcr.io/konairius/cni-plugins:$TAG
|
||||
|
||||
# Runtime image, not CI tooling: alpine + the openssl CLI, for shell scripts
|
||||
# needing s_client/x509. Named for what it is; first consumer is the paperless
|
||||
@@ -336,7 +334,8 @@ steps:
|
||||
- AUTH=$(printf '%s:%s' "$GITEA_USER" "$GITEA_TOKEN" | base64 | tr -d '\n')
|
||||
- GH_AUTH=$(printf '%s:%s' "$GHCR_USER" "$GHCR_TOKEN" | base64 | tr -d '\n')
|
||||
- printf '{"auths":{"gitea.renner.uno":{"auth":"%s"},"ghcr.io":{"auth":"%s"}}}' "$AUTH" "$GH_AUTH" > /kaniko/.docker/config.json
|
||||
- TAG=$(date +%Y-%m-%d)
|
||||
# konairius/ gets one tag per build: Gitea's cleanup rule counts tags.
|
||||
- TAG=$(date +%Y-%m-%d-%H%M)
|
||||
- |
|
||||
/kaniko/executor \
|
||||
--context . \
|
||||
@@ -344,9 +343,7 @@ steps:
|
||||
--destination gitea.renner.uno/gitea_admin/alpine-openssl:$TAG \
|
||||
--destination gitea.renner.uno/gitea_admin/alpine-openssl:latest \
|
||||
--destination gitea.renner.uno/konairius/alpine-openssl:$TAG \
|
||||
--destination gitea.renner.uno/konairius/alpine-openssl:latest \
|
||||
--destination ghcr.io/konairius/alpine-openssl:$TAG \
|
||||
--destination ghcr.io/konairius/alpine-openssl:latest
|
||||
--destination ghcr.io/konairius/alpine-openssl:$TAG
|
||||
|
||||
# Heating CAN readers: open3e (Vitocal/VX3) + E3onCAN (E380 meter), one image
|
||||
# run as two services on edge-1. Replaces pip-into-venv on the host, where an
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Builds ci/images/hermes (nousresearch/hermes-agent plus the agent's CLIs) as
|
||||
# gitea.renner.uno/konairius/hermes:build.<unix-ts> and :latest.
|
||||
# gitea.renner.uno/konairius/hermes:YYYY-MM-DD-HHMM, one tag per build.
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
@@ -20,10 +20,9 @@ steps:
|
||||
- mkdir -p /kaniko/.docker
|
||||
- AUTH=$(printf '%s:%s' registry-bot "$GITEA_TOKEN" | base64 | tr -d '\n')
|
||||
- printf '{"auths":{"gitea.renner.uno":{"auth":"%s"}}}' "$AUTH" > /kaniko/.docker/config.json
|
||||
- TAG="build.$(date +%s)"
|
||||
- TAG=$(date +%Y-%m-%d-%H%M)
|
||||
- |
|
||||
/kaniko/executor \
|
||||
--context . \
|
||||
--dockerfile Containerfile \
|
||||
--destination gitea.renner.uno/konairius/hermes:$TAG \
|
||||
--destination gitea.renner.uno/konairius/hermes:latest
|
||||
--destination gitea.renner.uno/konairius/hermes:$TAG
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
# DSM's own python3 is 3.8; plays/configure-synology-python.yml installs this.
|
||||
ansible_python_interpreter: /var/packages/Python3.9/target/usr/bin/python3.9
|
||||
# DSM offers no sftp/scp.
|
||||
ansible_ssh_transfer_method: piped
|
||||
|
||||
# claude-bot is a DSM administrator here; its password is unknown.
|
||||
ssh_ca_claude_sudo_all: true
|
||||
@@ -8,8 +8,6 @@
|
||||
# pve_cluster (jupiter/neptune/saturn) run Talos on bare metal; no SSH at all
|
||||
# authentik_db / authentik_app authentik runs in-cluster (CNPG)
|
||||
# grafana runs in-cluster
|
||||
# synology_nas (nas-1) powered off; Garage and the PBS client
|
||||
# it hosted are gone with it
|
||||
# Their hosts no longer resolve, except the three that resolve to Talos nodes.
|
||||
|
||||
[netbird_server]
|
||||
@@ -25,6 +23,11 @@ proxy-2.renner.uno ansible_host=proxy-2.nb.renner.uno ansible_user=root
|
||||
[edge_host]
|
||||
edge-1.iot.renner.uno ansible_user=root
|
||||
|
||||
# Synology NAS (DSM): nas-1 RS1221+ on site, nas-2 DS1522+ off site.
|
||||
[synology_nas]
|
||||
nas-1.renner.uno ansible_user=root
|
||||
nas-2.renner.uno ansible_user=root
|
||||
|
||||
# Capability groups. Membership states which hardware a host physically has,
|
||||
# and each service playbook targets the group for its own concern. No flags and
|
||||
# no `when:` on the roles -- moving a stick to another box is a one-line change
|
||||
@@ -70,6 +73,8 @@ proxy-1.renner.uno
|
||||
proxy-2.renner.uno
|
||||
netbird.renner.uno
|
||||
edge-1.iot.renner.uno
|
||||
nas-1.renner.uno
|
||||
nas-2.renner.uno
|
||||
|
||||
# Hosts whose admin logins accept only ssh_admin_authorized_keys (break-glass);
|
||||
# everything else needs a CA certificate. Members must be in [ssh_ca].
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# Ansible needs Python >= 3.9 on the target; DSM ships 3.8. Synology's
|
||||
# Python3.9 package installs beside it. raw, because the modules cannot run
|
||||
# before it is there; raw is skipped in check mode.
|
||||
- name: Install Python 3.9 on the Synology NAS
|
||||
hosts: synology_nas
|
||||
become: true
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: Install the Python3.9 package
|
||||
ansible.builtin.raw: >-
|
||||
if [ -x {{ ansible_python_interpreter }} ]; then echo present;
|
||||
else /usr/syno/bin/synopkg install_from_server Python3.9; fi
|
||||
register: synology_python_install
|
||||
changed_when: "'present' not in synology_python_install.stdout"
|
||||
|
||||
- name: Check the interpreter exists
|
||||
ansible.builtin.raw: test -x {{ ansible_python_interpreter }}
|
||||
changed_when: false
|
||||
@@ -8,12 +8,11 @@
|
||||
lineinfile:
|
||||
path: /etc/hosts
|
||||
regexp: '^127\.0\.1\.1\s+{{ inventory_hostname }}'
|
||||
line: "{{ ansible_default_ipv4.address }} {{ inventory_hostname }} {{ inventory_hostname_short | default(inventory_hostname.split('.')[0]) }}"
|
||||
line: "{{ ansible_facts['default_ipv4']['address'] }} {{ inventory_hostname }} {{ inventory_hostname_short | default(inventory_hostname.split('.')[0]) }}"
|
||||
state: present
|
||||
when:
|
||||
- ansible_virtualization_type is defined
|
||||
- ansible_virtualization_type == 'lxc'
|
||||
- ansible_default_ipv4.address is defined
|
||||
- ansible_facts['virtualization_type'] == 'lxc'
|
||||
- ansible_facts['default_ipv4']['address'] is defined
|
||||
|
||||
# -------------------------------------------------------
|
||||
# 1. Install Common Packages
|
||||
@@ -64,8 +63,8 @@
|
||||
enabled: no
|
||||
masked: yes
|
||||
when:
|
||||
- ansible_virtualization_type is defined
|
||||
- ansible_virtualization_type in ['kvm', 'lxc']
|
||||
- ansible_facts['virtualization_role'] == 'guest'
|
||||
- ansible_facts['virtualization_type'] in ['kvm', 'lxc']
|
||||
ignore_errors: yes
|
||||
|
||||
# -------------------------------------------------------
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# amd64 asset for exactly this tag.
|
||||
crowdsec_bouncer_version: v0.0.36
|
||||
crowdsec_bouncer_sha256: f86e4b72693549d99f40a9402abefb894108f047a3fbe6e72fada25ee17ce88b
|
||||
# Controller cache for the release archive; it outlives /tmp cleanups.
|
||||
crowdsec_bouncer_download_dir: "{{ lookup('ansible.builtin.env', 'HOME') }}/.cache/ansible/crowdsec"
|
||||
|
||||
# The LAPI over the public path these hosts already serve, see
|
||||
# kubernetes/infrastructure/crowdsec/lapi-ingress.yaml. The proxies cannot
|
||||
|
||||
@@ -32,13 +32,23 @@
|
||||
# The controller has v4, so it does the fetching. The checksum is verified
|
||||
# there, before anything is copied to a host. Also in check mode: the fetch
|
||||
# only writes on the controller, and the copy below needs the file.
|
||||
- name: Create the controller download directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ crowdsec_bouncer_download_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
run_once: true
|
||||
check_mode: false
|
||||
|
||||
- name: Fetch the pinned bouncer release on the controller
|
||||
check_mode: false
|
||||
ansible.builtin.get_url:
|
||||
url: >-
|
||||
https://github.com/crowdsecurity/cs-firewall-bouncer/releases/download/{{
|
||||
crowdsec_bouncer_version }}/crowdsec-firewall-bouncer-linux-amd64.tgz
|
||||
dest: /tmp/crowdsec-firewall-bouncer-{{ crowdsec_bouncer_version }}.tgz
|
||||
dest: "{{ crowdsec_bouncer_download_dir }}/crowdsec-firewall-bouncer-{{ crowdsec_bouncer_version }}.tgz"
|
||||
checksum: sha256:{{ crowdsec_bouncer_sha256 }}
|
||||
mode: "0644"
|
||||
delegate_to: localhost
|
||||
@@ -53,7 +63,7 @@
|
||||
# host.
|
||||
- name: Copy the bouncer archive to the host
|
||||
ansible.builtin.copy:
|
||||
src: /tmp/crowdsec-firewall-bouncer-{{ crowdsec_bouncer_version }}.tgz
|
||||
src: "{{ crowdsec_bouncer_download_dir }}/crowdsec-firewall-bouncer-{{ crowdsec_bouncer_version }}.tgz"
|
||||
dest: /opt/crowdsec-firewall-bouncer-{{ crowdsec_bouncer_version }}.tgz
|
||||
mode: "0644"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Managed by Ansible - Do not edit manually
|
||||
|
||||
global_defs {
|
||||
router_id {{ ansible_hostname }}_ntp
|
||||
router_id {{ ansible_facts['hostname'] }}_ntp
|
||||
enable_script_security
|
||||
script_user root
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
- curl
|
||||
- ca-certificates
|
||||
- gnupg
|
||||
# deb822_repository
|
||||
- python3-debian
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
@@ -21,10 +23,19 @@
|
||||
creates: /usr/share/keyrings/netbird-archive-keyring.gpg
|
||||
|
||||
- name: Add NetBird repository
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [signed-by=/usr/share/keyrings/netbird-archive-keyring.gpg] https://pkgs.netbird.io/debian stable main"
|
||||
state: present
|
||||
filename: netbird
|
||||
ansible.builtin.deb822_repository:
|
||||
name: netbird
|
||||
types: deb
|
||||
uris: https://pkgs.netbird.io/debian
|
||||
suites: stable
|
||||
components: main
|
||||
signed_by: /usr/share/keyrings/netbird-archive-keyring.gpg
|
||||
|
||||
# A one-line netbird.list beside netbird.sources lists the repository twice.
|
||||
- name: Remove the one-line NetBird repository
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/sources.list.d/netbird.list
|
||||
state: absent
|
||||
|
||||
- name: Install NetBird client
|
||||
ansible.builtin.apt:
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
apt:
|
||||
name: qemu-guest-agent
|
||||
state: present
|
||||
when:
|
||||
- ansible_os_family is defined and ansible_os_family == 'Debian'
|
||||
when:
|
||||
- ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Install QEMU Guest Agent (RHEL/Rocky VMs)
|
||||
dnf:
|
||||
name: qemu-guest-agent
|
||||
state: present
|
||||
when:
|
||||
- ansible_os_family is defined and ansible_os_family == 'RedHat'
|
||||
when:
|
||||
- ansible_facts['os_family'] == 'RedHat'
|
||||
|
||||
- name: Ensure qemu-guest-agent is started and enabled
|
||||
service:
|
||||
|
||||
@@ -16,3 +16,6 @@ ssh_ca_claude_principal: claude-bot
|
||||
|
||||
# Units claude-bot may restart/reload via sudo on this host, e.g. [netbird].
|
||||
ssh_ca_claude_sudo_units: []
|
||||
|
||||
# Full passwordless sudo for claude-bot on this host.
|
||||
ssh_ca_claude_sudo_all: false
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
---
|
||||
# Replaces the admin logins' authorized_keys with ssh_admin_authorized_keys
|
||||
# (break-glass + CI). Everything else logs in with a CA certificate.
|
||||
- name: Look up the admin logins
|
||||
ansible.builtin.getent:
|
||||
database: passwd
|
||||
key: "{{ item }}"
|
||||
fail_key: false
|
||||
loop: "{{ ssh_ca_owner_users }}"
|
||||
register: ssh_ca_admin_lookup
|
||||
|
||||
# ssh_ca_admin_logins comes from main.yml, which runs first in the same play.
|
||||
- name: Set the admin logins' authorized_keys to the managed list
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ item.item }}"
|
||||
user: "{{ item[0] }}"
|
||||
key: |
|
||||
{% for k in ssh_admin_authorized_keys %}
|
||||
{{ (k.options ~ ' ') if (k.options | default('')) else '' }}{{ k.key }}
|
||||
{% endfor %}
|
||||
exclusive: true
|
||||
loop: "{{ ssh_ca_admin_lookup.results }}"
|
||||
loop: "{{ ssh_ca_admin_logins }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: (item.ansible_facts.getent_passwd | default({})).get(item.item) is not none
|
||||
label: "{{ item[0] }}"
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
register: ssh_ca_trusted_file
|
||||
|
||||
# sshd refuses all public key logins when RevokedKeys is set but unreadable,
|
||||
# so the file is always deployed, even when empty.
|
||||
@@ -34,6 +35,26 @@
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
# /etc/passwd, not getent: DSM has no getent binary. The admin logins are
|
||||
# local accounts. Each entry is the split passwd line (0 name, 5 home).
|
||||
- name: Read the local accounts
|
||||
ansible.builtin.slurp:
|
||||
src: /etc/passwd
|
||||
register: ssh_ca_passwd_file
|
||||
|
||||
- name: Find the existing admin logins
|
||||
vars:
|
||||
ssh_ca_passwd: "{{ ssh_ca_passwd_file.content | b64decode | trim | split('\n') | map('split', ':') | list }}"
|
||||
ansible.builtin.set_fact:
|
||||
ssh_ca_admin_logins: "{{ ssh_ca_passwd | selectattr('0', 'in', ssh_ca_owner_users) | list }}"
|
||||
ssh_ca_claude_exists: "{{ ssh_ca_claude_user in ssh_ca_passwd | map('first') }}"
|
||||
|
||||
# DSM manages accounts with synouser/synogroup and has no useradd.
|
||||
- name: Detect DSM
|
||||
ansible.builtin.stat:
|
||||
path: /usr/syno/sbin/synouser
|
||||
register: ssh_ca_synouser
|
||||
|
||||
# systemd-journal: read every journal without sudo (see sudoers-claude.j2).
|
||||
- name: Create the Claude account
|
||||
ansible.builtin.user:
|
||||
@@ -43,14 +64,57 @@
|
||||
password: "!"
|
||||
groups: systemd-journal
|
||||
append: true
|
||||
when: not ssh_ca_synouser.stat.exists
|
||||
|
||||
- name: Look up the owner admin logins
|
||||
ansible.builtin.getent:
|
||||
database: passwd
|
||||
key: "{{ item }}"
|
||||
fail_key: false
|
||||
loop: "{{ ssh_ca_owner_users }}"
|
||||
register: ssh_ca_owner_lookup
|
||||
- name: Create the Claude account on DSM
|
||||
when: ssh_ca_synouser.stat.exists
|
||||
block:
|
||||
# synouser requires a password; nobody knows it and sshd refuses passwords.
|
||||
- name: Create the DSM account
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/syno/sbin/synouser
|
||||
- --add
|
||||
- "{{ ssh_ca_claude_user }}"
|
||||
- "{{ lookup('ansible.builtin.password', '/dev/null', length=40, chars=['ascii_letters', 'digits']) }}"
|
||||
- Claude Bot
|
||||
- "0"
|
||||
- ""
|
||||
- "0"
|
||||
# command runs in check mode here.
|
||||
when:
|
||||
- not ssh_ca_claude_exists
|
||||
- not ansible_check_mode
|
||||
no_log: true
|
||||
|
||||
# Members are listed as "<n>:[<name>]".
|
||||
- name: Read the administrators members
|
||||
ansible.builtin.command: /usr/syno/sbin/synogroup --get administrators
|
||||
register: ssh_ca_dsm_admins_out
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Find the administrators members
|
||||
ansible.builtin.set_fact:
|
||||
ssh_ca_dsm_admins: >-
|
||||
{{ ssh_ca_dsm_admins_out.stdout_lines | select('match', '^[0-9]+:\[.*\]$')
|
||||
| map('regex_replace', '^[0-9]+:\[(.*)\]$', '\1') | list }}
|
||||
|
||||
# synogroup --member replaces the member list; a parse that lost the owner
|
||||
# admin login would drop it.
|
||||
- name: Check the member list holds the owner admin login
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ssh_ca_dsm_admins | intersect(ssh_ca_owner_users) | length > 0
|
||||
fail_msg: "administrators members lack {{ ssh_ca_owner_users }}: {{ ssh_ca_dsm_admins }}"
|
||||
|
||||
# DSM allows SSH for administrators only.
|
||||
- name: Add the Claude account to administrators
|
||||
ansible.builtin.command:
|
||||
argv: "{{ ['/usr/syno/sbin/synogroup', '--member', 'administrators'] + ssh_ca_dsm_admins + [ssh_ca_claude_user] }}"
|
||||
when:
|
||||
- ssh_ca_claude_user not in ssh_ca_dsm_admins
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Create the principals directory
|
||||
ansible.builtin.file:
|
||||
@@ -63,14 +127,13 @@
|
||||
- name: Accept the owner principal for the existing admin logins
|
||||
ansible.builtin.copy:
|
||||
content: "{{ ssh_ca_owner_principal }}\n"
|
||||
dest: "/etc/ssh/auth_principals/{{ item.item }}"
|
||||
dest: "/etc/ssh/auth_principals/{{ item[0] }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop: "{{ ssh_ca_owner_lookup.results }}"
|
||||
loop: "{{ ssh_ca_admin_logins }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: (item.ansible_facts.getent_passwd | default({})).get(item.item) is not none
|
||||
label: "{{ item[0] }}"
|
||||
|
||||
- name: Accept the Claude principal for the Claude account
|
||||
ansible.builtin.copy:
|
||||
@@ -88,6 +151,48 @@
|
||||
group: root
|
||||
mode: "0440"
|
||||
validate: visudo -cf %s
|
||||
when: not ssh_ca_synouser.stat.exists
|
||||
|
||||
# DSM has no visudo, so the rules are checked on the controller.
|
||||
- name: Install the Claude sudo rules on DSM
|
||||
when: ssh_ca_synouser.stat.exists
|
||||
vars:
|
||||
ssh_ca_sudoers: "{{ lookup('ansible.builtin.template', 'sudoers-claude.j2') | trim }}"
|
||||
block:
|
||||
- name: Check the sudo rules on the controller
|
||||
ansible.builtin.command:
|
||||
cmd: visudo -c -f -
|
||||
stdin: "{{ ssh_ca_sudoers }}"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Install the checked sudo rules
|
||||
ansible.builtin.copy:
|
||||
content: "{{ ssh_ca_sudoers }}\n"
|
||||
dest: "/etc/sudoers.d/{{ ssh_ca_claude_user }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0440"
|
||||
|
||||
- name: Create the sshd drop-in directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/ssh/sshd_config.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
# DSM's sshd_config has no Include. sshd keeps the first value it reads, so
|
||||
# the Include goes first for the drop-ins to win.
|
||||
- name: Include the sshd drop-in directory
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
line: Include /etc/ssh/sshd_config.d/*.conf
|
||||
insertbefore: BOF
|
||||
validate: sshd -t -f %s
|
||||
register: ssh_ca_include_line
|
||||
|
||||
# A broken drop-in only bites on the next sshd restart, so it is tested here
|
||||
# and removed again if the full config does not parse.
|
||||
@@ -100,6 +205,7 @@
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
register: ssh_ca_dropin_file
|
||||
|
||||
- name: Test the full sshd config
|
||||
ansible.builtin.command: sshd -t
|
||||
@@ -136,10 +242,11 @@
|
||||
fail_msg: "sshd -T does not show the SSH CA settings; another config line takes precedence"
|
||||
|
||||
# The unit is sshd.service on Arch and RedHat, ssh.service on Debian/Ubuntu,
|
||||
# where sshd.service is an alias that resolves to it. sshd -T only parses the
|
||||
# files, so the running daemon is reloaded on every run; a reload keeps open
|
||||
# where sshd.service is an alias that resolves to it. The running daemon is
|
||||
# reloaded when a file it reads at start changed; a reload keeps open
|
||||
# sessions. None runs when sshd is socket activated and stopped, or per
|
||||
# connection (ssh-mesh-only role); the next start reads the config.
|
||||
# Principals and revoked keys are read per login and need no reload.
|
||||
# systemctl directly: service_facts took 3-12 s per host.
|
||||
- name: Find the running sshd unit
|
||||
ansible.builtin.command: systemctl list-units --state=running --plain --no-legend --type=service sshd.service ssh.service
|
||||
@@ -161,7 +268,9 @@
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ ssh_ca_sshd_units | first }}"
|
||||
state: reloaded
|
||||
when: ssh_ca_sshd_units | length == 1
|
||||
when:
|
||||
- ssh_ca_sshd_units | length == 1
|
||||
- ssh_ca_trusted_file is changed or ssh_ca_include_line is changed or ssh_ca_dropin_file is changed
|
||||
|
||||
- name: Report the admin logins' authorized keys
|
||||
ansible.builtin.include_tasks: report.yml
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
# [ssh_keys_managed] have this reduced to ssh_admin_authorized_keys.
|
||||
- name: Find the admin logins' authorized_keys
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item.ansible_facts.getent_passwd[item.item][4] }}/.ssh/authorized_keys"
|
||||
loop: "{{ ssh_ca_owner_lookup.results }}"
|
||||
path: "{{ item[5] }}/.ssh/authorized_keys"
|
||||
loop: "{{ ssh_ca_admin_logins }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: (item.ansible_facts.getent_passwd | default({})).get(item.item) is not none
|
||||
label: "{{ item[0] }}"
|
||||
register: ssh_ca_keyfiles
|
||||
|
||||
- name: Fingerprint the admin logins' authorized keys
|
||||
|
||||
@@ -10,3 +10,6 @@
|
||||
{% for unit in ssh_ca_claude_sudo_units %}
|
||||
{{ ssh_ca_claude_user }} ALL=(root) NOPASSWD:NOEXEC: /usr/bin/systemctl restart {{ unit }}, /usr/bin/systemctl reload {{ unit }}
|
||||
{% endfor %}
|
||||
{% if ssh_ca_claude_sudo_all %}
|
||||
{{ ssh_ca_claude_user }} ALL=(ALL) NOPASSWD: ALL
|
||||
{% endif %}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
# One-off actions are not state and stay separate: plays/update-os.yml,
|
||||
# plays/update-dist-upgrade.yml, plays/rotate-netbird-secrets.yml.
|
||||
|
||||
- name: Synology Python
|
||||
ansible.builtin.import_playbook: plays/configure-synology-python.yml
|
||||
- name: Base system
|
||||
ansible.builtin.import_playbook: plays/common.yml
|
||||
- name: VM time sync
|
||||
|
||||
@@ -705,8 +705,9 @@ data:
|
||||
# AND (&(sambaDomainName=...)(objectClass=sambaDomain))
|
||||
# return entries instead of short-circuiting. Built from
|
||||
# the samba-bridge repo at
|
||||
# gitea.renner.uno/konairius/authentik-samba-bridge .
|
||||
container_image: gitea.renner.uno/konairius/authentik-samba-bridge-ldap-outpost:2026.8.4-3c8f413
|
||||
# gitea.renner.uno/konairius/authentik-samba-bridge ; Flux
|
||||
# substitutes the image from clusters/homelab/authentik.yaml.
|
||||
container_image: ${SAMBA_BRIDGE_LDAP_OUTPOST_IMAGE}
|
||||
object_naming_template: "ak-outpost-%(name)s"
|
||||
kubernetes_namespace: authentik
|
||||
kubernetes_replicas: 1
|
||||
|
||||
@@ -105,7 +105,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: controller
|
||||
image: gitea.renner.uno/gitea_admin/authentik-gate-sync:2026-10-27-0801 # {"$imagepolicy": "flux-system:authentik-gate-sync"}
|
||||
image: gitea.renner.uno/gitea_admin/authentik-gate-sync:2026-10-28-0712 # {"$imagepolicy": "flux-system:authentik-gate-sync"}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
|
||||
@@ -50,15 +50,7 @@ spec:
|
||||
# https://gitea.renner.uno/konairius/authentik-samba-bridge .
|
||||
image:
|
||||
repository: gitea.renner.uno/konairius/authentik-samba-bridge-server
|
||||
# Pinned to a specific build for two reasons:
|
||||
# 1. K8s does not pull on every reschedule when tag=latest, so
|
||||
# a floating tag means we don't actually pick up rebuilds
|
||||
# without a manual rollout-restart (which is forbidden per
|
||||
# "no pod invasion" rule).
|
||||
# 2. Pinning means the deployed binary is traceable to a
|
||||
# specific repo commit. Bump the tag whenever a new build
|
||||
# is published from gitea.renner.uno/konairius/authentik-samba-bridge .
|
||||
tag: "2026.8.4-3c8f413"
|
||||
tag: "2026-10-28-0655" # {"$imagepolicy": "flux-system:authentik-samba-bridge-server:tag"}
|
||||
envFrom:
|
||||
# SAMBA_BRIDGE_DOMAIN_SID / _ENABLED_GROUP / _UID_OFFSET
|
||||
# consumed by the bridge module at Django app startup. Missing
|
||||
@@ -114,7 +106,6 @@ spec:
|
||||
# breaks. Comma-separated, no spaces (authentik list parsing).
|
||||
- name: AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS
|
||||
value: "10.244.0.0/16,fd10:244::/48,127.0.0.0/8,::1/128"
|
||||
|
||||
authentik:
|
||||
log_level: info
|
||||
error_reporting:
|
||||
@@ -130,7 +121,6 @@ spec:
|
||||
password: ""
|
||||
use_tls: true
|
||||
from: "olymp@renner.uno"
|
||||
|
||||
server:
|
||||
resources:
|
||||
requests:
|
||||
@@ -173,7 +163,6 @@ spec:
|
||||
- secretName: authentik-tls
|
||||
hosts:
|
||||
- auth.renner.uno
|
||||
|
||||
worker:
|
||||
resources:
|
||||
requests:
|
||||
@@ -190,13 +179,10 @@ spec:
|
||||
minReplicas: 2
|
||||
maxReplicas: 4
|
||||
targetCPUUtilizationPercentage: 70
|
||||
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
redis:
|
||||
enabled: false
|
||||
|
||||
# Mount the homelab blueprints (K8s outpost integration, SSO and
|
||||
# paperless flows) — every key of the ConfigMap is applied by
|
||||
# the worker on boot and on each apply cycle. See
|
||||
|
||||
@@ -30,7 +30,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: controller
|
||||
image: gitea.renner.uno/gitea_admin/cloudflare-lb-sync:2026-10-27-0807 # {"$imagepolicy": "flux-system:cloudflare-lb-sync"}
|
||||
image: gitea.renner.uno/gitea_admin/cloudflare-lb-sync:2026-10-28-0718 # {"$imagepolicy": "flux-system:cloudflare-lb-sync"}
|
||||
env:
|
||||
- name: CLOUDFLARE_API_TOKEN
|
||||
valueFrom:
|
||||
|
||||
@@ -47,7 +47,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: gatus
|
||||
image: twinproduction/gatus:v5.37.0 # {"$imagepolicy": "flux-system:gatus"}
|
||||
image: twinproduction/gatus:v5.38.0 # {"$imagepolicy": "flux-system:gatus"}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
|
||||
@@ -32,7 +32,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: sync
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
command:
|
||||
- /opt/hermes/.venv/bin/python
|
||||
- /opt/hermes-skills/gitea-issues/scripts/sync_webhook.py
|
||||
|
||||
@@ -51,7 +51,7 @@ spec:
|
||||
# connects from the pod network. No env equivalent exists, so it is
|
||||
# set in config.yaml on every start (idempotent).
|
||||
- name: config
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
command:
|
||||
- /opt/hermes/.venv/bin/hermes
|
||||
- config
|
||||
@@ -75,7 +75,7 @@ spec:
|
||||
mountPath: /opt/data
|
||||
# WebSocket keepalive ping interval in seconds (default 20).
|
||||
- name: config-ws-ping
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
command:
|
||||
- /opt/hermes/.venv/bin/hermes
|
||||
- config
|
||||
@@ -99,7 +99,7 @@ spec:
|
||||
mountPath: /opt/data
|
||||
# Skills from git (ConfigMap hermes-skills) beside the PVC's skills/.
|
||||
- name: skills-config
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
command:
|
||||
- /opt/hermes/.venv/bin/hermes
|
||||
- config
|
||||
@@ -130,7 +130,7 @@ spec:
|
||||
# - alert-monitor: hourly; the script wakes the agent only for
|
||||
# untriaged alerts.
|
||||
- name: cron-jobs
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
# Non-fatal: a broken jobs.json or a changed cron CLI after an image
|
||||
# bump logs a warning instead of keeping Hermes from starting.
|
||||
command:
|
||||
@@ -187,7 +187,7 @@ spec:
|
||||
readOnly: true
|
||||
# Home Assistant MCP server; token resolved from HA_TOKEN at connect, writes need approval.
|
||||
- name: config-mcp-homeassistant
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
command:
|
||||
- /opt/hermes/.venv/bin/hermes
|
||||
- config
|
||||
@@ -214,7 +214,7 @@ spec:
|
||||
# skills, the Home Assistant MCP and the tea login are re-applied on
|
||||
# every start. default gets the kanban toolset to hand cards to them.
|
||||
- name: experts
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
# Non-fatal like cron-jobs: a broken expert logs a warning.
|
||||
command:
|
||||
- sh
|
||||
@@ -305,7 +305,7 @@ spec:
|
||||
# Gitea issues -> kanban (skills/gitea-issues): route script, webhook route on default,
|
||||
# completion shell hook plus its allowlist entry on every profile.
|
||||
- name: gitea-issues
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
# Non-fatal like cron-jobs: a failure logs a warning.
|
||||
command:
|
||||
- sh
|
||||
@@ -368,7 +368,7 @@ spec:
|
||||
readOnly: true
|
||||
# Multiplexed default reads platform credentials only from .env (rationale: commit message).
|
||||
- name: default-env
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
# Fatal on a writer failure: without these Matrix and the API server stay down.
|
||||
command:
|
||||
- /opt/hermes/.venv/bin/python
|
||||
@@ -428,7 +428,7 @@ spec:
|
||||
# tea login hermes-bot, written from the mounted token into an
|
||||
# emptyDir at the agent's ~/.config/tea.
|
||||
- name: tea-login
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
command:
|
||||
- sh
|
||||
- -ec
|
||||
@@ -457,7 +457,7 @@ spec:
|
||||
readOnly: true
|
||||
containers:
|
||||
- name: hermes
|
||||
image: gitea.renner.uno/konairius/hermes:build.1793088058 # {"$imagepolicy": "flux-system:hermes"}
|
||||
image: gitea.renner.uno/konairius/hermes:2026-10-28-0704 # {"$imagepolicy": "flux-system:hermes"}
|
||||
args: ["gateway", "run"]
|
||||
env:
|
||||
- name: HERMES_DASHBOARD
|
||||
|
||||
@@ -84,7 +84,7 @@ missing permission and the one step that grants it.
|
||||
9. **After the owner says "merged", verify the effect, not the merge.** Check
|
||||
`GET /repos/<o>/<r>/commits/<merge_sha>/statuses` for the expected
|
||||
`ci/woodpecker/push/<pipeline>` context (only cron contexts = the push pipeline never
|
||||
triggered), the registry tag list for a new `build.<ts>`, `flux get images policy <name>`,
|
||||
triggered), the registry tag list for a new `YYYY-MM-DD-HHMM` tag, `flux get images policy <name>`,
|
||||
and the Deployment image. Report each as observed; say plainly what you couldn't see
|
||||
(e.g. Woodpecker UI) instead of guessing the cause.
|
||||
- With several PRs in flight, query the state of every one; "merged" may mean only one
|
||||
|
||||
@@ -7,8 +7,8 @@ Use when a homelab change adds or modifies an image built from `ci/images/<name>
|
||||
| Concern | Owner | How |
|
||||
|---|---|---|
|
||||
| Versions of base images and bundled tools | **Renovate** (`renovate.json`, `dockerfile` manager) | Express every version as an image reference: `FROM <img>:<tag>@<digest>` and `COPY --from=<img>:<tag>@<digest> <path> /usr/local/bin/<tool>`. Renovate opens the bump PR; merging it is the trigger. |
|
||||
| Building the image | **Woodpecker**, on push to the default branch of `ci/images/<name>/**` (+ `event: manual`) | kaniko, tag `build.$(date +%s)` + `latest`. |
|
||||
| Rolling a built tag out | **Flux image automation** | `ImageRepository` + `ImagePolicy` (`filterTags.pattern '^build\.(?P<ts>\d+)$'`, `extract '$ts'`, `numerical asc`) and the `# {"$imagepolicy": "flux-system:<name>"}` marker on every image line. |
|
||||
| Building the image | **Woodpecker**, on push to the default branch of `ci/images/<name>/**` (+ `event: manual`) | kaniko, exactly one tag `$(date +%Y-%m-%d-%H%M)`. No `latest`, `main` or sha tags: Gitea's konairius cleanup rule counts each tag as a version and keeps only the newest. |
|
||||
| Rolling a built tag out | **Flux image automation** | `ImageRepository` + `ImagePolicy` (`filterTags.pattern '^\d{4}-\d{2}-\d{2}(-\d{4})?$'`, `alphabetical asc`) and the `# {"$imagepolicy": "flux-system:<name>"}` marker on every image line. No hand-written pins. |
|
||||
|
||||
Never add cron polling / "decide" steps that compare upstream releases — that duplicates
|
||||
Renovate. Never curl binaries with hand-maintained sha256 lines when the tool ships an
|
||||
@@ -47,4 +47,4 @@ image: `COPY --from` a digest-pinned image is tracked and needs no checksums.
|
||||
- Check what the base image's last `USER` is before adding one (`grep -n '^USER'` in the
|
||||
upstream Dockerfile).
|
||||
- Rollout order is two PRs: image + pipeline first; the manifest switch only with a real
|
||||
`build.<ts>` (see SKILL.md, Multi-stage changes).
|
||||
date tag (see SKILL.md, Multi-stage changes).
|
||||
|
||||
@@ -44,6 +44,9 @@ spec:
|
||||
# reachable over net1. The mDNS resolver is a separate
|
||||
# ZeroconfResolver socket set and is unaffected.
|
||||
k8s.v1.cni.cncf.io/networks: shelly-manager-iot
|
||||
# Changing this rolls the pod, which is the only way a change to the
|
||||
# shelly-manager-iot attachment takes effect.
|
||||
net.renner.uno/iot-attachment-config: "tuning-no-ra"
|
||||
spec:
|
||||
# eth0 (Cilium-managed pod network) is still the primary — it
|
||||
# carries the ingress, mosquitto LB return path, etc. The macvlan
|
||||
@@ -61,11 +64,17 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: shelly-manager
|
||||
image: gitea.renner.uno/konairius/shelly-manager:build.1793145992 # {"$imagepolicy": "flux-system:shelly-manager"}
|
||||
image: gitea.renner.uno/konairius/shelly-manager:2026-10-29-0807 # {"$imagepolicy": "flux-system:shelly-manager"}
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
# Firmware images only (/fw/...), for the devices: Service shelly-manager-firmware.
|
||||
- name: firmware
|
||||
containerPort: 8082
|
||||
# /metrics only: Service shelly-manager-metrics.
|
||||
- name: metrics
|
||||
containerPort: 9090
|
||||
env:
|
||||
- name: TZ
|
||||
value: Europe/Berlin
|
||||
@@ -83,8 +92,20 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
# 8082 and 9090 are separate pipelines in the app that answer only
|
||||
# /fw/... and /metrics; the UI and API stay on 8080.
|
||||
- name: ASPNETCORE_URLS
|
||||
value: "http://$(POD_IP):8080"
|
||||
value: "http://$(POD_IP):8080;http://$(POD_IP):8082;http://$(POD_IP):9090"
|
||||
# Both pod addresses (IPv4,IPv6). When set, the app binds all three
|
||||
# ports to exactly these and ignores ASPNETCORE_URLS, so nothing
|
||||
# listens on net1 and the firmware LoadBalancer gets an IPv6 listener.
|
||||
- name: POD_IPS
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIPs
|
||||
# The URL devices download firmware from: Service shelly-manager-firmware.
|
||||
- name: Firmware__PublicBaseUrl
|
||||
value: http://firmware.shelly-manager.renner.uno
|
||||
- name: ShellyManager__DnsSuffix
|
||||
value: iot.renner.uno
|
||||
# BLE radios: ESP32 esphome bluetooth_proxy nodes (e.g. the M5 Atom
|
||||
|
||||
@@ -44,6 +44,12 @@ spec:
|
||||
# the pod goes NotReady and its ingress returns 504. Static IPAM used to
|
||||
# dodge this by simply never setting a gateway; leased addresses come with
|
||||
# one, so it has to be confined instead of omitted.
|
||||
#
|
||||
# sbr covers IPv4 only. With Router Advertisements net1 gets global IPv6
|
||||
# addresses plus on-link and default routes in the main table, and replies
|
||||
# to IPv6 clients on v30iot leave via net1, bypassing Cilium's LoadBalancer
|
||||
# reverse NAT. tuning turns RA processing off on net1; the link-local
|
||||
# address mDNS uses stays.
|
||||
config: |
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
@@ -57,6 +63,13 @@ spec:
|
||||
"type": "dhcp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tuning",
|
||||
"sysctl": {
|
||||
"net.ipv6.conf.IFNAME.accept_ra": "0",
|
||||
"net.ipv6.conf.IFNAME.autoconf": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "sbr"
|
||||
}
|
||||
|
||||
@@ -29,3 +29,56 @@ spec:
|
||||
port: 80
|
||||
targetPort: api
|
||||
protocol: TCP
|
||||
---
|
||||
# Firmware downloads for the Shelly fleet. Devices on the IoT VLAN reach it over
|
||||
# the UDM's BGP routes (like mosquitto-iot) by the name
|
||||
# firmware.shelly-manager.renner.uno, which shelly-manager hands them in the
|
||||
# update URL.
|
||||
# The app serves only /fw/... on this port. Ingress: shelly-manager-firmware-
|
||||
# world-ingress in network-policies/home-assistant-ns.yaml.
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: shelly-manager-firmware
|
||||
namespace: home-assistant
|
||||
labels:
|
||||
app.kubernetes.io/name: shelly-manager
|
||||
lb-pool: bgp
|
||||
dns.renner.uno/internal: "true"
|
||||
annotations:
|
||||
# Pinned like the other bgp-pool Services, which auto-assignment would give
|
||||
# the pool's base address 10.2.99.0 (see trmnl/device-service.yaml).
|
||||
lbipam.cilium.io/ips: "10.2.99.90,fd99::8a"
|
||||
external-dns.kubernetes.io/hostname: firmware.shelly-manager.renner.uno
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Local
|
||||
ipFamilyPolicy: PreferDualStack
|
||||
ipFamilies:
|
||||
- IPv4
|
||||
- IPv6
|
||||
selector:
|
||||
app.kubernetes.io/name: shelly-manager
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: firmware
|
||||
protocol: TCP
|
||||
---
|
||||
# Scrape target for monitoring-rules/shelly-manager-servicemonitor.yaml.
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: shelly-manager-metrics
|
||||
namespace: home-assistant
|
||||
labels:
|
||||
app.kubernetes.io/name: shelly-manager
|
||||
app.kubernetes.io/component: metrics
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: shelly-manager
|
||||
ports:
|
||||
- name: metrics
|
||||
port: 9090
|
||||
targetPort: metrics
|
||||
protocol: TCP
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
# Mosquitto broker LoadBalancer on the BGP-routed off-link pool. IoT
|
||||
# devices reach the broker via the UDM's BGP-learned routes — no
|
||||
# on-link L2 announce on v30iot. Companion Shelly fleet is updated via
|
||||
# shelly-manager's `/api/v1/mqtt/push` so each device's stored MQTT
|
||||
# server resolves to `mqtt.renner.uno`, not a bare IP — that way future
|
||||
# IP changes only need DNS updates, no reflash sweep.
|
||||
# on-link L2 announce on v30iot. The Shelly fleet's MQTT server is set
|
||||
# by shelly-manager's root group setting `mqtt.server` (Groups → All
|
||||
# Shellies → settings) and reconciled to every device, so each device's
|
||||
# stored MQTT server resolves to `mqtt.renner.uno`, not a bare IP — that
|
||||
# way future IP changes only need DNS updates, no reflash sweep.
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
|
||||
@@ -223,9 +223,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: controller
|
||||
# Woodpecker builds main-<sha> on every merge to main; no sortable
|
||||
# tag, so bumps are manual (same as ical-proxy).
|
||||
image: gitea.renner.uno/konairius/mqtt-account-controller:main-b1f294d@sha256:8081adfee171c36cfa83734e5e7ccc3aad3debf1c0154488de70143674cde85f
|
||||
image: gitea.renner.uno/konairius/mqtt-account-controller:2026-10-28-0657 # {"$imagepolicy": "flux-system:mqtt-account-controller"}
|
||||
env:
|
||||
- name: BROKER_URL
|
||||
value: ssl://mosquitto.home-assistant.svc.cluster.local:8884
|
||||
|
||||
@@ -66,7 +66,7 @@ spec:
|
||||
# (machine.registries.mirrors, applied by bootstrap.sh), and containerd
|
||||
# keeps the repository path when it falls through — so the path has to
|
||||
# exist on both registries. Steady state still pulls from Gitea.
|
||||
image: gitea.renner.uno/konairius/alpine-jq:2026-09-04
|
||||
image: gitea.renner.uno/konairius/alpine-jq:2026-10-28-0711 # {"$imagepolicy": "flux-system:alpine-jq"}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
|
||||
@@ -17,44 +17,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: ical-proxy
|
||||
# Pinned to a commit build rather than :latest.
|
||||
#
|
||||
# :latest never rolls. The tag moves when a build publishes, but
|
||||
# nothing tells the Deployment about it: the pod template is
|
||||
# unchanged, so Kubernetes has no reason to replace anything, and
|
||||
# this image is deliberately NOT enrolled in Flux image automation
|
||||
# — see kubernetes/infrastructure/image-automation/README.md, which
|
||||
# lists gitea.renner.uno/konairius/ical-proxy as having "only main, latest,
|
||||
# and main-<sha> commit builds; no release tags", so there is no
|
||||
# ImagePolicy watching it. The pods therefore sat on a 21-day-old
|
||||
# image while :latest moved underneath them, and the only way to
|
||||
# pick up a new build was a manual restart.
|
||||
#
|
||||
# A pinned tag makes the rollout a git change: Flux applies the new
|
||||
# pod template and Kubernetes rolls it, which is also what makes
|
||||
# what is RUNNING recoverable from this repo rather than "whatever
|
||||
# :latest meant the last time a pod happened to restart".
|
||||
#
|
||||
# The image comes from the Gitea package registry, built by
|
||||
# Woodpecker (.woodpecker.yml in konairius/ical-proxy) on every
|
||||
# push to main. It is amd64-only and pulls without credentials, so
|
||||
# no imagePullSecret. main-4783abe adds two hardening changes on
|
||||
# top of main-db90a59: an SSRF guard (upstreams and redirect hops
|
||||
# resolving to private/loopback/link-local addresses are refused;
|
||||
# public redirects are still followed, up to 5) and a cap of 20
|
||||
# concurrent /proxy upstream fetches (over the cap: 429 +
|
||||
# Retry-After). main-73b5d97 then raises the server WriteTimeout
|
||||
# from 10s to 40s (the 30s upstream fetch timeout + 10s headroom),
|
||||
# so a slow-but-legitimate upstream is no longer cut off before
|
||||
# /proxy can answer. main-926ead0 caps the upstream response body
|
||||
# at 2 MiB (larger feeds get 413 instead of being buffered whole,
|
||||
# REN-230) and logs upstream fetch failures with the host,
|
||||
# answering 502 instead of 500. The summary_match/summary_replace
|
||||
# parameters this deployment needs are unchanged.
|
||||
#
|
||||
# Bumping this is a manual, reviewed edit. The tag format is
|
||||
# main-<short-sha>, the first 7 characters of the main commit.
|
||||
image: gitea.renner.uno/konairius/ical-proxy:main-926ead0
|
||||
image: gitea.renner.uno/konairius/ical-proxy:2026-10-28-0651 # {"$imagepolicy": "flux-system:ical-proxy"}
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
|
||||
@@ -30,7 +30,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: controller
|
||||
image: gitea.renner.uno/gitea_admin/kuma-sync:2026-10-27-0801 # {"$imagepolicy": "flux-system:kuma-sync"}
|
||||
image: gitea.renner.uno/gitea_admin/kuma-sync:2026-10-28-0712 # {"$imagepolicy": "flux-system:kuma-sync"}
|
||||
env:
|
||||
- name: KUMA_URL
|
||||
value: https://kuma.renner.uno
|
||||
|
||||
@@ -63,6 +63,9 @@ resources:
|
||||
# deadlocked the whole apps layer. See the file for the cycle.
|
||||
- rook-servicemonitors.yaml
|
||||
- home-assistant-servicemonitor.yaml
|
||||
# shelly-manager firmware rollout: halts, index freshness, config.
|
||||
- shelly-manager-servicemonitor.yaml
|
||||
- prometheusrule-shelly-manager.yaml
|
||||
- immich-servicemonitor.yaml
|
||||
- loki-servicemonitor.yaml
|
||||
- smartctl-exporter-servicemonitor.yaml
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: shelly-manager
|
||||
namespace: monitoring
|
||||
labels:
|
||||
# kube-prometheus-stack's Prometheus resource selects on this label;
|
||||
# without it the rule is created but never loaded into Prometheus.
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
groups:
|
||||
- name: shelly-manager.rules
|
||||
rules:
|
||||
# Metrics from shelly-manager's /metrics (ServiceMonitor
|
||||
# shelly-manager-servicemonitor.yaml). Tests: tests/shelly-manager_test.yaml.
|
||||
|
||||
- alert: ShellyFirmwareRolloutHalted
|
||||
# 1 per device type while its rollout is halted; cleared by Resume or
|
||||
# Skip version on the Firmware page.
|
||||
expr: |
|
||||
max by (type) (shelly_manager_firmware_halted) == 1
|
||||
labels:
|
||||
severity: warning
|
||||
namespace: home-assistant
|
||||
annotations:
|
||||
summary: "Shelly firmware rollout halted for {{ $labels.type }}"
|
||||
description: "A firmware update of a {{ $labels.type }} device failed, so shelly-manager stopped rolling out to that type. The Firmware page names the device and the reason; Resume retries that device, Skip version leaves the version out."
|
||||
|
||||
- alert: ShellyFirmwareIndexStale
|
||||
# Set only by a refresh in which every index source (Gen1 and Gen2+)
|
||||
# succeeded, and restored from the stored releases on start. The
|
||||
# index is read every 6 h.
|
||||
expr: |
|
||||
time() - max(shelly_manager_firmware_index_last_success_timestamp_seconds) > 172800
|
||||
for: 30m
|
||||
labels:
|
||||
severity: warning
|
||||
namespace: home-assistant
|
||||
annotations:
|
||||
summary: "Shelly firmware index not read successfully for 48 h"
|
||||
description: "shelly-manager has not completed a refresh of all firmware index sources for 48 hours, so new firmware is not picked up. Check its logs for index errors (TLS pin, egress to *.shelly.cloud, HTTP errors); shelly_manager_firmware_index_errors_total shows which source fails."
|
||||
|
||||
- alert: ShellyFirmwareConfigInvalid
|
||||
# Firmware:Window or Firmware:TimeZone cannot be read; the rollout
|
||||
# does not start while this is 0.
|
||||
expr: |
|
||||
max(shelly_manager_firmware_config_valid) == 0
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
namespace: home-assistant
|
||||
annotations:
|
||||
summary: "Shelly firmware rollout disabled by invalid config"
|
||||
description: "Firmware__Window or Firmware__TimeZone in kubernetes/apps/home-assistant-iot/shelly-manager-deployment.yaml (or the image defaults) is invalid, so shelly-manager starts no automatic firmware updates."
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: shelly-manager
|
||||
namespace: home-assistant
|
||||
labels:
|
||||
app.kubernetes.io/name: shelly-manager
|
||||
# Not required for discovery — serviceMonitorSelector is empty — but kept
|
||||
# for consistency with the other monitors.
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: shelly-manager
|
||||
app.kubernetes.io/component: metrics
|
||||
endpoints:
|
||||
- port: metrics
|
||||
path: /metrics
|
||||
interval: 60s
|
||||
@@ -34,6 +34,7 @@ retired the depth rule in favour of one persistence rule (`> 5` for `20m`).
|
||||
| `gitea_test.yaml` | Hand-written suite for `GiteaDataVolumeLow` in `prometheusrule-gitea.yaml` (REN-583). |
|
||||
| `uptime-kuma_test.yaml` | Hand-written suite for `prometheusrule-uptime-kuma.yaml` and the kuma exclusion in `prometheusrule-blackbox.yaml`. |
|
||||
| `k8s-gateway_test.yaml` | Hand-written suite for `prometheusrule-k8s-gateway.yaml` (#1290). |
|
||||
| `shelly-manager_test.yaml` | Hand-written suite for `prometheusrule-shelly-manager.yaml`. |
|
||||
| `mutations.txt` | Negative controls. Each must make the suite fail. |
|
||||
|
||||
## Adding a suite
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# Suite for prometheusrule-shelly-manager.yaml.
|
||||
rule_files:
|
||||
- ../prometheusrule-shelly-manager.yaml
|
||||
|
||||
evaluation_interval: 1m
|
||||
|
||||
tests:
|
||||
# A halted type fires at once, per type; a type at 0 stays silent.
|
||||
- interval: 1m
|
||||
input_series:
|
||||
- series: 'shelly_manager_firmware_halted{type="rpc:Plus1PM",pod="shelly-manager-a"}'
|
||||
values: '0 0 1 1 1'
|
||||
- series: 'shelly_manager_firmware_halted{type="gen1:SHSW-1",pod="shelly-manager-a"}'
|
||||
values: '0 0 0 0 0'
|
||||
alert_rule_test:
|
||||
- eval_time: 1m
|
||||
alertname: ShellyFirmwareRolloutHalted
|
||||
exp_alerts: []
|
||||
- eval_time: 2m
|
||||
alertname: ShellyFirmwareRolloutHalted
|
||||
exp_alerts:
|
||||
- exp_labels:
|
||||
severity: warning
|
||||
namespace: home-assistant
|
||||
type: rpc:Plus1PM
|
||||
exp_annotations:
|
||||
summary: "Shelly firmware rollout halted for rpc:Plus1PM"
|
||||
description: "A firmware update of a rpc:Plus1PM device failed, so shelly-manager stopped rolling out to that type. The Firmware page names the device and the reason; Resume retries that device, Skip version leaves the version out."
|
||||
|
||||
# Last success at t=0: stale from 48h01m (first evaluation past 172800 s),
|
||||
# fires 30 m later.
|
||||
- interval: 1m
|
||||
input_series:
|
||||
- series: 'shelly_manager_firmware_index_last_success_timestamp_seconds{pod="shelly-manager-a"}'
|
||||
values: '0+0x3000'
|
||||
alert_rule_test:
|
||||
- eval_time: 48h
|
||||
alertname: ShellyFirmwareIndexStale
|
||||
exp_alerts: []
|
||||
- eval_time: 48h30m
|
||||
alertname: ShellyFirmwareIndexStale
|
||||
exp_alerts: []
|
||||
- eval_time: 48h31m
|
||||
alertname: ShellyFirmwareIndexStale
|
||||
exp_alerts:
|
||||
- exp_labels:
|
||||
severity: warning
|
||||
namespace: home-assistant
|
||||
exp_annotations:
|
||||
summary: "Shelly firmware index not read successfully for 48 h"
|
||||
description: "shelly-manager has not completed a refresh of all firmware index sources for 48 hours, so new firmware is not picked up. Check its logs for index errors (TLS pin, egress to *.shelly.cloud, HTTP errors); shelly_manager_firmware_index_errors_total shows which source fails."
|
||||
|
||||
# A success that keeps up with time stays silent.
|
||||
- interval: 1m
|
||||
input_series:
|
||||
- series: 'shelly_manager_firmware_index_last_success_timestamp_seconds{pod="shelly-manager-a"}'
|
||||
values: '0+60x6000'
|
||||
alert_rule_test:
|
||||
- eval_time: 100h
|
||||
alertname: ShellyFirmwareIndexStale
|
||||
exp_alerts: []
|
||||
|
||||
# Invalid config fires after 10 m; valid config stays silent.
|
||||
- interval: 1m
|
||||
input_series:
|
||||
- series: 'shelly_manager_firmware_config_valid{pod="shelly-manager-a"}'
|
||||
values: '1 1 0+0x20'
|
||||
alert_rule_test:
|
||||
- eval_time: 11m
|
||||
alertname: ShellyFirmwareConfigInvalid
|
||||
exp_alerts: []
|
||||
- eval_time: 12m
|
||||
alertname: ShellyFirmwareConfigInvalid
|
||||
exp_alerts:
|
||||
- exp_labels:
|
||||
severity: warning
|
||||
namespace: home-assistant
|
||||
exp_annotations:
|
||||
summary: "Shelly firmware rollout disabled by invalid config"
|
||||
description: "Firmware__Window or Firmware__TimeZone in kubernetes/apps/home-assistant-iot/shelly-manager-deployment.yaml (or the image defaults) is invalid, so shelly-manager starts no automatic firmware updates."
|
||||
@@ -32,6 +32,7 @@ from music_assistant_models.media_items import (
|
||||
Podcast,
|
||||
PodcastEpisode,
|
||||
ProviderMapping,
|
||||
RecommendationFolder,
|
||||
SearchResults,
|
||||
UniqueList,
|
||||
)
|
||||
@@ -62,6 +63,7 @@ UP_NEXT_PLAYLIST_ID = "up_next"
|
||||
SUPPORTED_FEATURES = {
|
||||
ProviderFeature.LIBRARY_PODCASTS,
|
||||
ProviderFeature.LIBRARY_PLAYLISTS,
|
||||
ProviderFeature.RECOMMENDATIONS,
|
||||
ProviderFeature.BROWSE,
|
||||
ProviderFeature.SEARCH,
|
||||
ProviderFeature.LIBRARY_PODCASTS_EDIT,
|
||||
@@ -282,6 +284,38 @@ class PocketCastsProvider(MusicProvider):
|
||||
)
|
||||
return self._get_up_next_playlist()
|
||||
|
||||
async def get_recommendations(self) -> list[RecommendationFolder]:
|
||||
"""Get the Up Next row, without items."""
|
||||
return [
|
||||
RecommendationFolder(
|
||||
item_id=UP_NEXT_PLAYLIST_ID,
|
||||
provider=self.instance_id,
|
||||
name="Up Next",
|
||||
icon="mdi-playlist-play",
|
||||
)
|
||||
]
|
||||
|
||||
async def get_recommendation_items(
|
||||
self, item_id: str
|
||||
) -> UniqueList[MediaItemType | ItemMapping | BrowseFolder]:
|
||||
"""
|
||||
Get the Up Next playlist, shown as the episode at the head of the queue.
|
||||
|
||||
:param item_id: The item_id of the row, as returned by get_recommendations.
|
||||
"""
|
||||
if item_id != UP_NEXT_PLAYLIST_ID:
|
||||
return UniqueList()
|
||||
episodes = await self._get_special_folder_episodes(UP_NEXT_PLAYLIST_ID)
|
||||
if not episodes:
|
||||
return UniqueList()
|
||||
first = episodes[0]
|
||||
playlist = self._get_up_next_playlist()
|
||||
playlist.name = first.name
|
||||
playlist.owner = first.podcast.name
|
||||
if first.metadata.images:
|
||||
playlist.metadata.images = first.metadata.images
|
||||
return UniqueList([playlist])
|
||||
|
||||
async def get_playlist_tracks(
|
||||
self, prov_playlist_id: str, page: int = 0
|
||||
) -> list[PodcastEpisode]:
|
||||
|
||||
@@ -43,7 +43,7 @@ spec:
|
||||
mountPath: /consume
|
||||
containers:
|
||||
- name: vsftpd
|
||||
image: gitea.renner.uno/konairius/vsftpd:build.1792310564 # {"$imagepolicy": "flux-system:vsftpd"}
|
||||
image: gitea.renner.uno/konairius/vsftpd:2026-10-28-0649 # {"$imagepolicy": "flux-system:vsftpd"}
|
||||
ports:
|
||||
- name: ftp-ctrl
|
||||
containerPort: 21
|
||||
|
||||
@@ -155,7 +155,7 @@ spec:
|
||||
# exist on both registries — hence konairius/, which matches the ghcr
|
||||
# namespace. Steady state still pulls from Gitea and never leaves the
|
||||
# network.
|
||||
image: gitea.renner.uno/konairius/alpine-openssl:2026-09-04
|
||||
image: gitea.renner.uno/konairius/alpine-openssl:2026-10-28-0711 # {"$imagepolicy": "flux-system:alpine-openssl"}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
|
||||
@@ -32,7 +32,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: review-bot
|
||||
image: gitea.renner.uno/gitea_admin/review-bot:2026-10-27-2231 # {"$imagepolicy": "flux-system:review-bot"}
|
||||
image: gitea.renner.uno/gitea_admin/review-bot:2026-10-28-0709 # {"$imagepolicy": "flux-system:review-bot"}
|
||||
env:
|
||||
- name: GITEA_URL
|
||||
value: https://gitea.renner.uno
|
||||
|
||||
@@ -184,7 +184,7 @@ spec:
|
||||
# (machine.registries.mirrors, applied by bootstrap.sh), and containerd
|
||||
# keeps the repository path, so it has to exist on both registries.
|
||||
# Steady state still pulls from Gitea.
|
||||
image: gitea.renner.uno/konairius/alpine-jq:2026-09-04
|
||||
image: gitea.renner.uno/konairius/alpine-jq:2026-10-28-0711 # {"$imagepolicy": "flux-system:alpine-jq"}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
|
||||
@@ -134,7 +134,7 @@ spec:
|
||||
# is a device-bricking class of risk. Bumps are a reviewed, manual
|
||||
# act — see kubernetes/infrastructure/image-automation/README.md.
|
||||
- name: terminus
|
||||
image: ghcr.io/usetrmnl/terminus:0.78.0 # {"$imagepolicy": "flux-system:terminus"}
|
||||
image: ghcr.io/usetrmnl/terminus:0.79.0 # {"$imagepolicy": "flux-system:terminus"}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 2345
|
||||
@@ -197,7 +197,7 @@ spec:
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
- name: sidekiq
|
||||
image: ghcr.io/usetrmnl/terminus:0.78.0 # {"$imagepolicy": "flux-system:terminus"}
|
||||
image: ghcr.io/usetrmnl/terminus:0.79.0 # {"$imagepolicy": "flux-system:terminus"}
|
||||
command: ["bundle", "exec", "sidekiq", "-r", "./config/sidekiq.rb"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
|
||||
@@ -53,7 +53,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: wiki
|
||||
image: gitea.renner.uno/konairius/wiki:build.1793094262 # {"$imagepolicy": "flux-system:wiki"}
|
||||
image: gitea.renner.uno/konairius/wiki:2026-10-28-0700 # {"$imagepolicy": "flux-system:wiki"}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
|
||||
@@ -34,7 +34,7 @@ spec:
|
||||
# Image automation, range 3.x (see image-automation/woodpecker.yaml).
|
||||
# MUST stay version-matched with woodpecker-server (gRPC parity) — both
|
||||
# track the same range/filter so they bump together on each release.
|
||||
image: woodpeckerci/woodpecker-agent:v3.19.0 # {"$imagepolicy": "flux-system:woodpecker-agent"}
|
||||
image: woodpeckerci/woodpecker-agent:v3.20.0 # {"$imagepolicy": "flux-system:woodpecker-agent"}
|
||||
env:
|
||||
- name: WOODPECKER_SERVER
|
||||
value: woodpecker.woodpecker.svc.cluster.local:9000 # in-cluster gRPC, replaces LXC :9000
|
||||
|
||||
@@ -40,7 +40,7 @@ spec:
|
||||
# minor + patch auto-update within 3.x; only the 4.0 major is held for
|
||||
# a reviewed bump. Safe now the DB is postgres (the old sqlite-surprise
|
||||
# -bump hazard is gone). Stays in lockstep with the agent.
|
||||
image: woodpeckerci/woodpecker-server:v3.19.0 # {"$imagepolicy": "flux-system:woodpecker-server"}
|
||||
image: woodpeckerci/woodpecker-server:v3.20.0 # {"$imagepolicy": "flux-system:woodpecker-server"}
|
||||
env:
|
||||
- name: WOODPECKER_HOST
|
||||
value: https://ci.renner.uno
|
||||
|
||||
@@ -16,6 +16,11 @@ spec:
|
||||
path: ./kubernetes/apps/authentik
|
||||
prune: true
|
||||
wait: true
|
||||
# Substitutes every ${VAR} under kubernetes/apps/authentik; that path has
|
||||
# no other ${...} literals. Flux image automation keeps the tag current.
|
||||
postBuild:
|
||||
substitute:
|
||||
SAMBA_BRIDGE_LDAP_OUTPOST_IMAGE: gitea.renner.uno/konairius/authentik-samba-bridge-ldap-outpost:2026-10-28-0655 # {"$imagepolicy": "flux-system:authentik-samba-bridge-ldap-outpost"}
|
||||
# decryption block dropped 2026-07-14: path is sops-free — secrets come
|
||||
# from OpenBao via ESO (MIGRATION-sops-openbao.md). This was the LAST
|
||||
# sops consumer; phase 4 (age-key retirement) may proceed after soak.
|
||||
|
||||
@@ -59,12 +59,23 @@ existing `flux-system` GitRepository whose Secret was created by
|
||||
| firefox | `1.x` | `-rolling-daily` suffix extract; rejects arch-prefixed duplicates |
|
||||
| mmwave-configurator | `2.x` | Plain 3-component filter (rejects `addon-`/`-armv7`/`-arm64`/`-amd64` variants) |
|
||||
| cnpg-barman-plugin | `0.x` | v-prefix filter; marker lives in upstream-imported manifest (see below) |
|
||||
| hermes, wiki, shelly-manager, vsftpd, ical-proxy, mqtt-account-controller, authentik-samba-bridge-{server,ldap-outpost} | `YYYY-MM-DD-HHMM` | In-house (konairius); newest tag wins |
|
||||
| alpine-jq, alpine-openssl, cni-plugins | `YYYY-MM-DD-HHMM` | In-house (build-ci-images.yml, konairius/ copy); newest tag wins |
|
||||
|
||||
## In-house konairius images: one tag per build
|
||||
|
||||
Gitea's package cleanup rule for konairius counts every tag as one
|
||||
version and keeps the most recent one. A build that pushes several tags
|
||||
(sha, `main`, `latest`) leaves only the last-pushed tag, so every
|
||||
konairius image is pushed with exactly one tag, `YYYY-MM-DD-HHMM` (UTC),
|
||||
and every consumer follows it through an ImagePolicy rather than a
|
||||
hand-written pin.
|
||||
|
||||
## Not enrolled (structurally impossible)
|
||||
|
||||
- `woodpeckerci/woodpecker-cli` — the lint image in
|
||||
`.woodpecker/ci-manifests-test.yml`. It publishes a perfectly good
|
||||
version stream (`v3.16.0-alpine`), so unlike the three below this is
|
||||
version stream (`v3.16.0-alpine`), so unlike the two below this is
|
||||
**not** a tag-format problem. It cannot be enrolled because
|
||||
**image-automation can only patch YAML that is a Kubernetes manifest** —
|
||||
a maintainer states it plainly in fluxcd/flux2#2826: *"Flux can only
|
||||
@@ -87,15 +98,13 @@ existing `flux-system` GitRepository whose Secret was created by
|
||||
|
||||
## Not enrolled (deliberate)
|
||||
|
||||
These three images publish no usable version stream — there is nothing
|
||||
These two images publish no usable version stream — there is nothing
|
||||
for image-policy to sort against:
|
||||
|
||||
- `fauria/vsftpd` — only `:latest` is published.
|
||||
- `openthread/otbr` — only Ubuntu codenames (`focal`, `jammy`,
|
||||
`hirsute`) and `:latest`/`:test`/`:reference-device`; no semver
|
||||
release stream.
|
||||
- `gitea.renner.uno/konairius/ical-proxy` — only `main`, `latest`, and
|
||||
`main-<sha>` commit builds; no release tags.
|
||||
|
||||
The security-scan agent will continue to surface vuln findings on
|
||||
these as "Skipped / Flux image-automation owns this" even without
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: alpine-jq
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: gitea.renner.uno/konairius/alpine-jq
|
||||
interval: 30m
|
||||
---
|
||||
# build-ci-images.yml tags each build YYYY-MM-DD-HHMM (older builds YYYY-MM-DD); newest wins.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: alpine-jq
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: alpine-jq
|
||||
filterTags:
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
alphabetical:
|
||||
order: asc
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: alpine-openssl
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: gitea.renner.uno/konairius/alpine-openssl
|
||||
interval: 30m
|
||||
---
|
||||
# build-ci-images.yml tags each build YYYY-MM-DD-HHMM (older builds YYYY-MM-DD); newest wins.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: alpine-openssl
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: alpine-openssl
|
||||
filterTags:
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
alphabetical:
|
||||
order: asc
|
||||
@@ -0,0 +1,46 @@
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: authentik-samba-bridge-server
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: gitea.renner.uno/konairius/authentik-samba-bridge-server
|
||||
interval: 30m
|
||||
---
|
||||
# konairius/authentik-samba-bridge CI tags each build YYYY-MM-DD-HHMM; newest wins.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: authentik-samba-bridge-server
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: authentik-samba-bridge-server
|
||||
filterTags:
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
alphabetical:
|
||||
order: asc
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: authentik-samba-bridge-ldap-outpost
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: gitea.renner.uno/konairius/authentik-samba-bridge-ldap-outpost
|
||||
interval: 30m
|
||||
---
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: authentik-samba-bridge-ldap-outpost
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: authentik-samba-bridge-ldap-outpost
|
||||
filterTags:
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
alphabetical:
|
||||
order: asc
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: cni-plugins
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: gitea.renner.uno/konairius/cni-plugins
|
||||
interval: 30m
|
||||
---
|
||||
# build-ci-images.yml tags each build YYYY-MM-DD-HHMM (older builds YYYY-MM-DD); newest wins.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: cni-plugins
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: cni-plugins
|
||||
filterTags:
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
alphabetical:
|
||||
order: asc
|
||||
@@ -7,7 +7,7 @@ spec:
|
||||
image: gitea.renner.uno/konairius/hermes
|
||||
interval: 10m
|
||||
---
|
||||
# .woodpecker/hermes-image.yml tags every build `build.<unix-ts>`; latest push wins.
|
||||
# .woodpecker/hermes-image.yml tags each build YYYY-MM-DD-HHMM; newest wins.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
@@ -17,8 +17,7 @@ spec:
|
||||
imageRepositoryRef:
|
||||
name: hermes
|
||||
filterTags:
|
||||
pattern: '^build\.(?P<ts>\d+)$'
|
||||
extract: '$ts'
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
numerical:
|
||||
alphabetical:
|
||||
order: asc
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: ical-proxy
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: gitea.renner.uno/konairius/ical-proxy
|
||||
interval: 30m
|
||||
---
|
||||
# konairius/ical-proxy CI tags each build YYYY-MM-DD-HHMM; newest wins.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: ical-proxy
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: ical-proxy
|
||||
filterTags:
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
alphabetical:
|
||||
order: asc
|
||||
@@ -2,22 +2,28 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- update-automation.yaml
|
||||
- alpine-jq.yaml
|
||||
- alpine-openssl.yaml
|
||||
- audiobookshelf.yaml
|
||||
- authentik-gate-sync.yaml
|
||||
- authentik-samba-bridge.yaml
|
||||
- backup-verify.yaml
|
||||
- ceph.yaml
|
||||
- cloudflare-lb-sync.yaml
|
||||
- cni-plugins.yaml
|
||||
- cnpg-barman-plugin.yaml
|
||||
- gatus.yaml
|
||||
- gitea-mcp.yaml
|
||||
- gotenberg.yaml
|
||||
- hermes.yaml
|
||||
- home-assistant.yaml
|
||||
- ical-proxy.yaml
|
||||
- immich.yaml
|
||||
- kuma-sync.yaml
|
||||
- lidarr.yaml
|
||||
- matter-server.yaml
|
||||
- mmwave-configurator.yaml
|
||||
- mqtt-account-controller.yaml
|
||||
- music-assistant.yaml
|
||||
- navidrome.yaml
|
||||
- netbird-ingress.yaml
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImageRepository
|
||||
metadata:
|
||||
name: mqtt-account-controller
|
||||
namespace: flux-system
|
||||
spec:
|
||||
image: gitea.renner.uno/konairius/mqtt-account-controller
|
||||
interval: 30m
|
||||
---
|
||||
# konairius/mqtt-account-controller CI tags each build YYYY-MM-DD-HHMM; newest wins.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
name: mqtt-account-controller
|
||||
namespace: flux-system
|
||||
spec:
|
||||
imageRepositoryRef:
|
||||
name: mqtt-account-controller
|
||||
filterTags:
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
alphabetical:
|
||||
order: asc
|
||||
@@ -7,9 +7,7 @@ spec:
|
||||
image: gitea.renner.uno/konairius/shelly-manager
|
||||
interval: 30m
|
||||
---
|
||||
# CI publishes `build.<unix-ts>` alongside SHA and `main` tags so the
|
||||
# ImagePolicy has a sortable tag stream to track. Numerical desc =
|
||||
# latest push wins. The SHA-only tags are filtered out by the pattern.
|
||||
# konairius/shelly-manager CI tags each build YYYY-MM-DD-HHMM; newest wins.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
@@ -19,8 +17,7 @@ spec:
|
||||
imageRepositoryRef:
|
||||
name: shelly-manager
|
||||
filterTags:
|
||||
pattern: '^build\.(?P<ts>\d+)$'
|
||||
extract: '$ts'
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
numerical:
|
||||
alphabetical:
|
||||
order: asc
|
||||
|
||||
@@ -7,9 +7,7 @@ spec:
|
||||
image: gitea.renner.uno/konairius/vsftpd
|
||||
interval: 30m
|
||||
---
|
||||
# CI publishes `build.<unix-ts>` alongside SHA and `main` tags so the
|
||||
# ImagePolicy has a sortable tag stream to track. Numerical desc =
|
||||
# latest push wins. The SHA-only tags are filtered out by the pattern.
|
||||
# konairius/vsftpd CI tags each build YYYY-MM-DD-HHMM; newest wins.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
@@ -19,8 +17,7 @@ spec:
|
||||
imageRepositoryRef:
|
||||
name: vsftpd
|
||||
filterTags:
|
||||
pattern: '^build\.(?P<ts>\d+)$'
|
||||
extract: '$ts'
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
numerical:
|
||||
alphabetical:
|
||||
order: asc
|
||||
|
||||
@@ -7,7 +7,7 @@ spec:
|
||||
image: gitea.renner.uno/konairius/wiki
|
||||
interval: 10m
|
||||
---
|
||||
# The Wiki release pipeline tags every main build `build.<unix-ts>`; latest push wins.
|
||||
# konairius/Wiki CI tags each build YYYY-MM-DD-HHMM; newest wins.
|
||||
apiVersion: image.toolkit.fluxcd.io/v1
|
||||
kind: ImagePolicy
|
||||
metadata:
|
||||
@@ -17,8 +17,7 @@ spec:
|
||||
imageRepositoryRef:
|
||||
name: wiki
|
||||
filterTags:
|
||||
pattern: '^build\.(?P<ts>\d+)$'
|
||||
extract: '$ts'
|
||||
pattern: '^\d{4}-\d{2}-\d{2}(-\d{4})?$'
|
||||
policy:
|
||||
numerical:
|
||||
alphabetical:
|
||||
order: asc
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
---
|
||||
# Installs the standard containernetworking/plugins binaries (macvlan,
|
||||
# static, host-local) into /opt/cni/bin on every node. Talos ships only
|
||||
# what Cilium installs there (cilium-cni, bandwidth, …); macvlan is
|
||||
@@ -74,7 +73,7 @@ spec:
|
||||
# No imagePullSecret: the ghcr namespace is private and its credential
|
||||
# is in machine.registries too, because a Secret would have to come
|
||||
# from OpenBao, which needs Ceph, which needs these binaries.
|
||||
image: gitea.renner.uno/konairius/cni-plugins:2026-10-05
|
||||
image: gitea.renner.uno/konairius/cni-plugins:2026-10-28-0711 # {"$imagepolicy": "flux-system:cni-plugins"}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
|
||||
@@ -212,6 +212,26 @@ spec:
|
||||
- port: "8123"
|
||||
protocol: TCP
|
||||
---
|
||||
# Shelly devices download firmware from shelly-manager through the
|
||||
# shelly-manager-firmware LoadBalancer. Only :8082, where the app serves
|
||||
# nothing but /fw/...; the UI on :8080 stays Traefik-only.
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: shelly-manager-firmware-world-ingress
|
||||
namespace: home-assistant
|
||||
spec:
|
||||
endpointSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: shelly-manager
|
||||
ingress:
|
||||
- fromEntities:
|
||||
- world
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "8082"
|
||||
protocol: TCP
|
||||
---
|
||||
# Secret rotation: the openbao-ns rotation CronJobs reach HA to roll its three
|
||||
# long-lived access tokens. HA mints/revokes LLATs only over its WebSocket API,
|
||||
# so the rotator must reach HA directly.
|
||||
@@ -360,8 +380,9 @@ spec:
|
||||
- toEntities:
|
||||
- world
|
||||
---
|
||||
# shelly-manager: HTTP RPC to Shelly devices on the IoT VLAN, and mDNS
|
||||
# queries for their *.local names (sent out the default interface eth0).
|
||||
# shelly-manager: HTTP RPC to Shelly devices on the IoT VLAN, mDNS queries
|
||||
# for their *.local names (sent out the default interface eth0), and Shelly's
|
||||
# firmware index and image hosts over HTTPS.
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
@@ -372,8 +393,10 @@ spec:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: shelly-manager
|
||||
egress:
|
||||
# v30iot over eth0: IPv4 and the VLAN's ULA prefix.
|
||||
- toCIDR:
|
||||
- 10.2.30.0/24
|
||||
- fd02:30::/64
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "80"
|
||||
@@ -384,6 +407,28 @@ spec:
|
||||
- ports:
|
||||
- port: "5353"
|
||||
protocol: UDP
|
||||
# rules.dns lets Cilium learn the toFQDNs mapping
|
||||
- toEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": kube-system
|
||||
k8s-app: kube-dns
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "53"
|
||||
protocol: UDP
|
||||
- port: "53"
|
||||
protocol: TCP
|
||||
rules:
|
||||
dns:
|
||||
- matchPattern: "*"
|
||||
# api.shelly.cloud (Gen1 index), updates.shelly.cloud (Gen2+ index),
|
||||
# firmware./fwcdn.shelly.cloud and other *.shelly.cloud image hosts.
|
||||
- toFQDNs:
|
||||
- matchPattern: "*.shelly.cloud"
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "443"
|
||||
protocol: TCP
|
||||
---
|
||||
# esphome: OTA/API to devices on the IoT VLAN; HTTPS for GitHub, PlatformIO and PyPI during builds.
|
||||
apiVersion: cilium.io/v2
|
||||
|
||||
@@ -71,7 +71,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: rotate
|
||||
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-27 # {"$imagepolicy": "flux-system:secret-rotator"}
|
||||
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-28 # {"$imagepolicy": "flux-system:secret-rotator"}
|
||||
args: ["ha-token"]
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -133,7 +133,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: rotate
|
||||
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-27 # {"$imagepolicy": "flux-system:secret-rotator"}
|
||||
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-28 # {"$imagepolicy": "flux-system:secret-rotator"}
|
||||
args: ["ha-token"]
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -195,7 +195,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: rotate
|
||||
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-27 # {"$imagepolicy": "flux-system:secret-rotator"}
|
||||
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-10-28 # {"$imagepolicy": "flux-system:secret-rotator"}
|
||||
args: ["ha-token"]
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
|
||||
@@ -32,7 +32,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: backup-verify
|
||||
image: gitea.renner.uno/gitea_admin/ci-backup-verify:2026-10-27 # {"$imagepolicy": "flux-system:backup-verify"}
|
||||
image: gitea.renner.uno/gitea_admin/ci-backup-verify:2026-10-28 # {"$imagepolicy": "flux-system:backup-verify"}
|
||||
command: ["bash", "/script/backup-verify.sh"]
|
||||
env:
|
||||
- name: HOME
|
||||
|
||||
@@ -45,7 +45,14 @@
|
||||
"ghcr.io/paperless-ngx/paperless-ngx",
|
||||
"ghcr.io/usetrmnl/terminus",
|
||||
"ghcr.io/usetrmnl/trmnl-ha-amd64",
|
||||
"gitea.renner.uno/konairius/alpine-jq",
|
||||
"gitea.renner.uno/konairius/alpine-openssl",
|
||||
"gitea.renner.uno/konairius/authentik-samba-bridge-ldap-outpost",
|
||||
"gitea.renner.uno/konairius/authentik-samba-bridge-server",
|
||||
"gitea.renner.uno/konairius/cni-plugins",
|
||||
"gitea.renner.uno/konairius/hermes",
|
||||
"gitea.renner.uno/konairius/ical-proxy",
|
||||
"gitea.renner.uno/konairius/mqtt-account-controller",
|
||||
"gitea.renner.uno/konairius/shelly-manager",
|
||||
"gitea.renner.uno/konairius/vsftpd",
|
||||
"gitea.renner.uno/konairius/wiki",
|
||||
|
||||
Reference in New Issue
Block a user