Compare commits

..

2 Commits

Author SHA1 Message Date
unkinben cf72d0c635 dns: dual-write toggles + drift fact
ci/woodpecker/pr/ruby-validate Pipeline was successful
ci/woodpecker/pr/puppet-lint Pipeline was successful
ci/woodpecker/pr/yamllint Pipeline was successful
ci/woodpecker/pr/bolt-validate Pipeline was successful
ci/woodpecker/pr/erb-validate Pipeline was successful
ci/woodpecker/pr/epp-validate Pipeline was successful
ci/woodpecker/pr/ruby-check Pipeline was successful
ci/woodpecker/pr/puppet-validate Pipeline was successful
Publish records both ways during the k8s cutover, and expose expected vs
deployed records for drift detection.

- profiles::dns::updater + ::record: manage_nsupdate and manage_export
  booleans (both default on); export keeps the legacy master flow, so
  disable it once k8s is authoritative
- dns_records fact: parses the expected records file and digs the
  authoritative server for each, reporting expected / in_sync / drift
  (plus dns_records_insync boolean); updater writes the server address
  to /var/lib/dns-updater/server for the fact
- hiera: manage_export/manage_nsupdate = true (cutover)
2026-07-11 01:02:02 +10:00
unkinben 4b8a720e0c dns: nsupdate host records to the authoritative server
Replaces the exported-resources -> puppet DNS master zone-file flow with
per-host RFC2136 dynamic updates against the k8s bind-authoritative write
endpoint (198.18.200.9), so the master no longer manages zone files.

- add profiles::dns::updater: assembles the host's records into a concat
  file and runs nsupdate via a systemd .path unit that watches it; the
  dns-update script sends only the delta and deletes removed records
- switch profiles::dns::record to write local concat fragments
  (zone|name|type|ttl|value) instead of exporting to the master
- include profiles::dns::updater from profiles::dns::base (all nodes)
- inert until profiles::dns::updater::key_secret (TSIG) is set in eyaml
- hiera: updater server/key_name/algorithm in common.yaml
2026-07-11 01:02:02 +10:00
72 changed files with 539 additions and 1415 deletions
+31 -8
View File
@@ -88,13 +88,36 @@
# SSH Hostkey Signing
The `sshca` ssh engine, its `signhost` role, the `sshca/sign/signhost` policy and
the `sshsigner` approle are managed in terraform-vault:
## create ssh engine, key, set ttl
vault secrets enable -path=ssh-host-signer ssh
vault write ssh-host-signer/config/ca generate_signing_key=true
vault secrets tune -max-lease-ttl=87600h ssh-host-signer
- `config/ssh_secret_backend/sshca.yaml`
- `config/ssh_secret_backend_role/sshca/signhost.yaml`
- `config/auth_approle_role/approle/sshsigner.yaml`
- `policies/sshca/sign/signhost.yaml`
## create role
vault write ssh-host-signer/roles/hostrole \
key_type=ca \
algorithm_signer=rsa-sha2-256 \
ttl=87600h \
allow_host_certificates=true \
allowed_domains="unkin.net" \
allow_subdomains=true \
allow_baredomains=true
## get the sshsigner approle id
vault read -field=role_id auth/approle/role/sshsigner/role-id
## create policy to use hostrole
cat <<EOF > sshsign-host.hcl
path "ssh-host-signer/sign/hostrole" {
capabilities = ["create", "update"]
}
EOF
vault policy write sshsign-host-policy sshsign-host.hcl
vault write auth/approle/role/sshsign-host-role \
bind_secret_id=false \
token_policies="sshsign-host-policy" \
token_ttl=30s \
token_max_ttl=30s \
token_bound_cidrs="198.18.17.3/32,198.18.13.32/32,198.18.13.33/32,198.18.13.34/32"
## get the sshsign-host-role approle id
vault read -field=role_id auth/approle/role/sshsign-host-role/role-id
+3 -53
View File
@@ -146,8 +146,6 @@ lookup_options:
strategy: deep
profiles::etcd::node::initial_cluster_token:
convert_to: Sensitive
profiles::dns::updater::key_secret:
convert_to: Sensitive
sysctl::base::values:
merge:
strategy: deep
@@ -178,8 +176,6 @@ lookup_options:
convert_to: Sensitive
stalwart::fallback_admin_password:
convert_to: Sensitive
wireguard::interfaces:
convert_to: Sensitive
facts_path: '/opt/puppetlabs/facter/facts.d'
@@ -210,7 +206,7 @@ vault::manage_config_dir: true
vault::disable_mlock: false
profiles::dns::base::nameservers:
- 198.18.200.7
- 198.18.19.16
profiles::dns::master::basedir: '/var/named/sources'
# dns record publishing. During the k8s cutover both methods run; set
@@ -221,13 +217,7 @@ profiles::dns::master::basedir: '/var/named/sources'
# profiles::dns::updater::key_secret: ENC[...]
# (must match the key the bind-authoritative zones allow-update
# with; algorithm hmac-sha256)
# k8s is now authoritative: clients resolve via the k8s bind-resolvers
# (profiles::dns::base::nameservers 198.18.200.7, since #490), which forward the
# unkin.net/main.unkin.net/*.18.198.in-addr.arpa zones to the k8s
# bind-authoritative cluster fed by the nsupdate path below. The legacy
# exported-resources -> VM-master /var/named/sources files are no longer in any
# client's resolution path, so stop exporting them. Rollback: set back to true.
profiles::dns::updater::manage_export: false
profiles::dns::updater::manage_export: true
profiles::dns::updater::manage_nsupdate: true
profiles::dns::updater::server: '198.18.200.9'
profiles::dns::updater::key_name: 'client-update'
@@ -369,7 +359,6 @@ ssh::server::options:
profiles::ssh::knownhosts::lines:
- '@cert-authority * ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC1HD97vYxLTniE4qNpGuftUlvmkEXIuX8+7nbENv/IzsGUghEDRtyThjQ7ojNKIsQ7f8wXr0gMcI+fAPfrbcOMHCAoYMomikwL0b3h95SZI40q3CyM+0DMnwiVVDX6C1QxkO2Rv9cszSkCa85NotJhXiUuTBI9BFcRPy+mAhbpAru+bfypYofI0wW97XNTl8Jgwmni5MgutBIQAokFIn5ux8iWxndCH3AqDtmkwC5DfQeQ+wZx7rkwqJEpJffQzrjb1gIM6P9hDCVBBVPh/3o80IJ69rFWrJAZUb+JpG4cXJH0NcSW+wqc3JCT/x3q8VlHwOTXSlNNKtOJCRx73mB8e1XTTy2a9FgpKDDg5XQXWHAViJDz1RTRL9gRefMylRgKz4bXoTuY9kJWM8hPTyUejtukbJThlBJc3OmDxBZBF7F0iqB11pHexok43OCEiANodVa36eWu9/5X032Vm48fZ1/akDPY/NSy3wAn7kwut+A0/JAHFHASrq+1mt9YurkJegI+YHXO6eEWpBIpmI7ORHJbGL4MhkHrxYzVamuP8CkU7tXzsv138+wpOcRHNp9yJY4PT40BZkRf/O3O+jt3pj9Dj8rvgywF2W6hFzywh3Y78upOprRkQlQtHfsI8EyrYI8/hUw2u3H+3yPXh3YjWfqvWVG1BRLRHBV7m90uaw=='
- '@cert-authority * ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDi80G0GtKMdRj4azPwxbJW46NG0y8seSVSnvFm2Ka/nckdUb/3lRlQuPYf1tkbqqFlcXjDM8+u0tzM2kLsgfn0Dpujm1fuoA66yGGweBjtxLFErH5+6+/KND5I9w8LMY2AtVztnBk/CVx8RwgrooABDRZiBH7OOAOpJqqFI7LvEsv61zC0nAqbYJ8uxfx+r4lJ9dUhK1woitEqC4npSRUn5KJK+KAEd7AzcUkZb6TO9A3oRPz1nQ/qU+QNMmVUi+wRj9kJR18mxErugyLLTNcFDBqSdHNun3eUNBUmoS2cAa8ZVscOLzbUGejVK9UY06Q7wu+J34Fzl8CN5tBqRHGZ3ykqGZ6gG+O0Egr9Rm3Obgkujpio2uTh27LhBy72vjgJ8kTFmPlzANTJFpKlsy91usSFPh8WZeIo6VpPLqnC32rjfNkfqHyPAeJ3+rdOkUZoDjMoZc3wxxLZTgMU5ud1w4LxQNRkNiaT/tRRNQF8o+pygkS7xxKduBBMzYdRS1OifaS4gyvP82oOyquWywhyFNtG7ph8FQwc34puM7FyxfaJ0XL/+zAfPMhDoOojvofADJo73R+FgVyer+mCJw4KtWJ4JzZrNTYz1Xl8WlhF8RDzOYfSH46qzJFa9FcRqgP4LUkgzdvcQ+1hBFpvpHtw3vl3DiqtZDDbK9SyrEtdnw=='
profiles::base::groups::local:
admins:
@@ -403,7 +392,7 @@ networking::route_defaults:
# logging:
victorialogs::client::journald::enable: true
victorialogs::client::journald::inserturl: https://logs-ingest.k8s.syd1.au.unkin.net:443/insert/journald
victorialogs::client::journald::inserturl: https://vlinsert.service.consul:9428/insert/journald
# FIXME these are for the proxmox ceph cluster
profiles::ceph::client::fsid: 7f7f00cb-95de-498c-8dcc-14b54e4e9ca8
@@ -412,45 +401,6 @@ profiles::ceph::client::mons:
- 10.18.15.2
- 10.18.15.3
# de96a98f cluster topology (prodnxsr0001-0019) - single source of truth for
# /etc/ceph/ceph.conf rendered by profiles::ceph::client on the k8s (osd) and
# incus (mon/mgr/mds) roles. fsid/mons are overridden per-role in the role hiera.
# public_network is the /32 of every ceph host's ceph-public loopback.
profiles::ceph::client::cluster_public_ips:
- 198.18.23.1
- 198.18.23.2
- 198.18.23.3
- 198.18.23.4
- 198.18.23.5
- 198.18.23.6
- 198.18.23.7
- 198.18.23.8
- 198.18.23.9
- 198.18.23.10
- 198.18.23.11
- 198.18.23.12
- 198.18.23.13
- 198.18.23.14
- 198.18.23.15
- 198.18.23.16
- 198.18.23.17
- 198.18.23.18
- 198.18.23.19
profiles::ceph::client::mon_initial_members:
- prodnxsr0009
- prodnxsr0010
- prodnxsr0011
- prodnxsr0012
- prodnxsr0013
# two mds daemon instances per mon/mgr/mds host (rendered only where
# render_mds_config is true, i.e. the incus node role).
profiles::ceph::client::mds_instances:
prodnxsr0009: 2
prodnxsr0010: 2
prodnxsr0011: 2
prodnxsr0012: 2
prodnxsr0013: 2
profiles::ceph::conf::config:
global:
auth_client_required: 'cephx'
@@ -2,6 +2,6 @@
profiles::consul::server::bootstrap_count: 3
profiles::consul::server::raft_multiplier: 10
profiles::consul::server::primary_datacenter: 'au-syd1'
profiles::consul::server::join_remote_regions: false
profiles::consul::server::join_remote_regions: true
profiles::consul::server::remote_regions:
- syd1
+1 -9
View File
@@ -2,16 +2,8 @@
timezone::timezone: 'Australia/Sydney'
certbot::client::webserver: ausyd1nxvm2057.main.unkin.net
profiles_dns_upstream_forwarder_unkin:
- 198.18.200.7
- 198.18.19.15
profiles_dns_upstream_forwarder_consul:
- 198.18.19.14
profiles_dns_upstream_forwarder_k8s:
- 198.18.19.20
profiles::consul::client::members_lookup: false
# static: k8s-compiled hosts can't see the servers in PuppetDB; update when servers change
profiles::consul::client::consul_servers:
- ausyd1nxvm2005.main.unkin.net
- ausyd1nxvm2006.main.unkin.net
- ausyd1nxvm2007.main.unkin.net
- ausyd1nxvm2008.main.unkin.net
- ausyd1nxvm2009.main.unkin.net
@@ -3,7 +3,23 @@ haproxy_server_k8s_syd1_traefik_internal: 'k8s-traefik-internal 198.18.200.4:443
haproxy_server_k8s_syd1_traefik_external: 'k8s-traefik-external 198.18.199.0:443 ssl verify none check inter 2s rise 3 fall 2'
profiles::haproxy::dns::ipaddr: "%{hiera('anycast_ip')}"
profiles::haproxy::dns::vrrp_cnames: []
profiles::haproxy::dns::vrrp_cnames:
- sonarr.main.unkin.net
- radarr.main.unkin.net
- lidarr.main.unkin.net
- readarr.main.unkin.net
- prowlarr.main.unkin.net
- nzbget.main.unkin.net
- git.unkin.net
- fafflix.unkin.net
- grafana.unkin.net
- dashboard.ceph.unkin.net
- mail-webadmin.main.unkin.net
- mail-in.main.unkin.net
- mail.main.unkin.net
- autoconfig.main.unkin.net
- autodiscover.main.unkin.net
- auth.unkin.net
profiles::haproxy::mappings:
fe_http:
@@ -416,7 +432,9 @@ profiles::pki::vault::alt_names:
- mail-webadmin.main.unkin.net
# additional cnames
profiles::haproxy::dns::cnames: []
profiles::haproxy::dns::cnames:
- au-syd1-pve.main.unkin.net
- au-syd1-pve-api.main.unkin.net
# letsencrypt certificates
certbot::client::service: haproxy
@@ -2,6 +2,6 @@
profiles::consul::server::bootstrap_count: 3
profiles::consul::server::raft_multiplier: 10
profiles::consul::server::primary_datacenter: 'au-syd1'
profiles::consul::server::join_remote_regions: false
profiles::consul::server::join_remote_regions: true
profiles::consul::server::remote_regions:
- drw1
@@ -1,3 +0,0 @@
---
# Canary for the puppet-on-k8s migration (roles::base test host).
profiles::puppet::migrate::enabled: true
@@ -1,13 +0,0 @@
---
networking_loopback0_ip: 198.18.19.21 # management loopback
networking_loopback1_ip: 198.18.22.14 # ceph-cluster loopback
networking_loopback2_ip: 198.18.23.14 # ceph-public loopback
networking_1000_ip: 198.18.15.14 # 1gbe network
networking_2500_ip: 198.18.21.14 # 2.5gbe network
networking_1000_iface: enp1s0
networking_2500_iface: enp2s0
networking::interfaces:
"%{hiera('networking_1000_iface')}":
mac: a4:bb:6d:a4:e5:c1
"%{hiera('networking_2500_iface')}":
mac: c4:62:37:0d:50:03
@@ -1,13 +0,0 @@
---
networking_loopback0_ip: 198.18.19.22 # management loopback
networking_loopback1_ip: 198.18.22.15 # ceph-cluster loopback
networking_loopback2_ip: 198.18.23.15 # ceph-public loopback
networking_1000_ip: 198.18.15.15 # 1gbe network
networking_2500_ip: 198.18.21.15 # 2.5gbe network
networking_1000_iface: enp1s0
networking_2500_iface: enp2s0
networking::interfaces:
"%{hiera('networking_1000_iface')}":
mac: a4:bb:6d:a6:30:c4
"%{hiera('networking_2500_iface')}":
mac: c4:62:37:0d:4f:f4
@@ -1,13 +0,0 @@
---
networking_loopback0_ip: 198.18.19.23 # management loopback
networking_loopback1_ip: 198.18.22.16 # ceph-cluster loopback
networking_loopback2_ip: 198.18.23.16 # ceph-public loopback
networking_1000_ip: 198.18.15.16 # 1gbe network
networking_2500_ip: 198.18.21.16 # 2.5gbe network
networking_1000_iface: enp1s0
networking_2500_iface: enp2s0
networking::interfaces:
"%{hiera('networking_1000_iface')}":
mac: a4:bb:6d:9f:22:13
"%{hiera('networking_2500_iface')}":
mac: c4:62:37:0d:50:0c
@@ -1,13 +0,0 @@
---
networking_loopback0_ip: 198.18.19.24 # management loopback
networking_loopback1_ip: 198.18.22.17 # ceph-cluster loopback
networking_loopback2_ip: 198.18.23.17 # ceph-public loopback
networking_1000_ip: 198.18.15.17 # 1gbe network
networking_2500_ip: 198.18.21.17 # 2.5gbe network
networking_1000_iface: enp1s0
networking_2500_iface: enp2s0
networking::interfaces:
"%{hiera('networking_1000_iface')}":
mac: 8c:04:ba:9c:b6:08
"%{hiera('networking_2500_iface')}":
mac: c4:62:37:0d:50:12
@@ -1,13 +0,0 @@
---
networking_loopback0_ip: 198.18.19.25 # management loopback
networking_loopback1_ip: 198.18.22.18 # ceph-cluster loopback
networking_loopback2_ip: 198.18.23.18 # ceph-public loopback
networking_1000_ip: 198.18.15.18 # 1gbe network
networking_2500_ip: 198.18.21.18 # 2.5gbe network
networking_1000_iface: enp1s0
networking_2500_iface: enp2s0
networking::interfaces:
"%{hiera('networking_1000_iface')}":
mac: a4:bb:6d:a4:db:94
"%{hiera('networking_2500_iface')}":
mac: c4:62:37:0d:4f:fa
@@ -1,13 +0,0 @@
---
networking_loopback0_ip: 198.18.19.26 # management loopback
networking_loopback1_ip: 198.18.22.19 # ceph-cluster loopback
networking_loopback2_ip: 198.18.23.19 # ceph-public loopback
networking_1000_ip: 198.18.15.19 # 1gbe network
networking_2500_ip: 198.18.21.19 # 2.5gbe network
networking_1000_iface: enp1s0
networking_2500_iface: enp2s0
networking::interfaces:
"%{hiera('networking_1000_iface')}":
mac: a4:bb:6d:a4:56:11
"%{hiera('networking_2500_iface')}":
mac: c4:62:37:0d:50:00
@@ -1,45 +0,0 @@
---
# primary interface is the WAN uplink; pin host identity to the dum0 loopback
networking_loopback0_ip: 198.18.2.160
networking_loopback1_ip: 198.18.21.160
# dns: keep the local dnsmasq resolver
profiles::dns::base::nameservers:
- 127.0.0.1
profiles::dns::base::search:
- main.unkin.net
profiles::dns::base::primary_interface: dum0
profiles::dns::updater::deny_ranges:
- 198.18.199.0/24
- 198.18.200.0/24
- 10.42.0.0/16
- 10.43.0.0/16
- 10.10.12.0/24 # wg0
- 103.216.190.0/23 # wan uplink
profiles::consul::client::host_addr: "%{hiera('networking_loopback0_ip')}"
# ssh: listen on localhost and dum0 only; knock out the common wan primary ip
lookup_options:
ssh::server::options:
merge:
strategy: deep
knockout_prefix: '--'
ssh::server::options:
ListenAddress:
- "--%{facts.networking.ip}"
- 127.0.0.1
- "%{hiera('networking_loopback0_ip')}"
profiles::ssh::sign::principals:
- "%{hiera('networking_loopback0_ip')}"
# frrouting
frrouting::ospfd_router_id: "%{hiera('networking_loopback0_ip')}"
frrouting::ospfd_interfaces:
dum0:
area: 0.0.0.0
dum1:
area: 0.0.0.0
bond0.201:
area: 0.0.0.0
frrouting::ospf_preferred_source_enable: true
frrouting::ospf_preferred_source: "%{hiera('networking_loopback1_ip')}"
+20 -31
View File
@@ -70,6 +70,14 @@ profiles::yum::global::repos:
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/epel/%{facts.os.release.major}/Everything/%{facts.os.architecture}
gpgkey: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/epel/RPM-GPG-KEY-EPEL-%{facts.os.release.major}
mirrorlist: absent
unkinben:
name: unkinben
descr: unkinben repository
target: /etc/yum.repos.d/unkin.repo
baseurl: https://git.query.consul/api/packages/unkinben/rpm/el%{facts.os.release.major}
gpgkey: https://git.query.consul/api/packages/unkinben/rpm/repository.key
gpgcheck: false
mirrorlist: absent
rpm-internal:
name: rpm-internal
descr: rpm-internal repository
@@ -77,33 +85,6 @@ profiles::yum::global::repos:
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/local/rpm-internal/
gpgcheck: false
mirrorlist: absent
metadata_expire: '60'
rpm-vendor:
name: rpm-vendor
descr: rpm-vendor repository
target: /etc/yum.repos.d/rpm-vendor.repo
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/local/rpm-vendor/
gpgcheck: false
mirrorlist: absent
metadata_expire: '60'
# Per-release variants, resolved from the host's EL major version so el8
# hosts pull rpm-internal-el8/rpm-vendor-el8, el9 hosts el9, etc.
rpm-internal-release:
name: rpm-internal-el%{facts.os.release.major}
descr: rpm-internal-el%{facts.os.release.major} repository
target: /etc/yum.repos.d/rpm-internal-release.repo
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/local/rpm-internal-el%{facts.os.release.major}/
gpgcheck: false
mirrorlist: absent
metadata_expire: '60'
rpm-vendor-release:
name: rpm-vendor-el%{facts.os.release.major}
descr: rpm-vendor-el%{facts.os.release.major} repository
target: /etc/yum.repos.d/rpm-vendor-release.repo
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/local/rpm-vendor-el%{facts.os.release.major}/
gpgcheck: false
mirrorlist: absent
metadata_expire: '60'
# Additional repositories - default to absent, roles can override with ensure: present
# FRRouting repositories
@@ -273,8 +254,8 @@ profiles::yum::global::repos:
name: rpmfusion-free
descr: rpmfusion-free repository
target: /etc/yum.repos.d/rpmfusion-free.repo
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/rpmfusion-free/el/updates/%{facts.os.release.major}/%{facts.os.architecture}/
gpgkey: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/rpmfusion-free/el/RPM-GPG-KEY-rpmfusion-free-el-%{facts.os.release.major}
baseurl: https://packagerepo.service.consul/rpmfusion-free-el%{facts.os.release.major}-%{facts.os.architecture}/
gpgkey: https://packagerepo.service.consul/rpmfusion-free-el%{facts.os.release.major}-%{facts.os.architecture}/repodata/repomd.xml.key
gpgcheck: 1
mirrorlist: absent
rpmfusion-nonfree:
@@ -282,7 +263,15 @@ profiles::yum::global::repos:
name: rpmfusion-nonfree
descr: rpmfusion-nonfree repository
target: /etc/yum.repos.d/rpmfusion-nonfree.repo
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/rpmfusion-nonfree/el/updates/%{facts.os.release.major}/%{facts.os.architecture}/
gpgkey: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/rpmfusion-nonfree/el/RPM-GPG-KEY-rpmfusion-nonfree-el-%{facts.os.release.major}
baseurl: https://packagerepo.service.consul/rpmfusion-nonfree-el%{facts.os.release.major}-%{facts.os.architecture}/
gpgkey: https://packagerepo.service.consul/rpmfusion-nonfree-el%{facts.os.release.major}-%{facts.os.architecture}/repodata/repomd.xml.key
gpgcheck: 1
mirrorlist: absent
unkin:
name: unkin
descr: unkin repository
target: /etc/yum.repos.d/unkin.repo
baseurl: https://git.query.consul/api/packages/unkin/rpm/almalinux/el%{facts.os.release.major}
gpgkey: https://git.query.consul/api/packages/unkin/rpm/repository.key
gpgcheck: false
mirrorlist: absent
-71
View File
@@ -1,71 +0,0 @@
# hieradata/os/Fedora/all_releases.yaml
---
# Fedora reuses the RedHat osfamily code paths (dnf/yum, crypto-policies,
# firewalld, openvox). Everything here keys off facts.os.release.major so a
# future Fedora release only needs its artifactapi local repos created, not new
# hieradata.
# crypto-policies: use the distro default. Kept here (not per-release) so newer
# Fedora releases inherit it for free.
crypto_policies::policy: 'DEFAULT'
# Puppet agent via OpenVox. RedHat-family builds for Fedora live under
# openvox7/fedora/<major> (see profiles::puppet::agent).
profiles::puppet::agent::openvox_enable: true
lm-sensors::package: lm_sensors
# Fedora-specific base setup (NetworkManager, future workstation/laptop hooks).
hiera_include:
- profiles::fedora::base
# Base package set adjustments for Fedora:
# - p7zip was dropped from Fedora; the real 7-Zip ships as "7zip"
# - dstat was removed from Fedora with no drop-in successor in the base set
# - iotop is provided by the C rewrite package "iotop-c"
profiles::packages::exclude:
- p7zip
- dstat
- iotop
profiles::packages::include:
7zip: {}
iotop-c: {}
crypto-policies-scripts: {}
lzo: {}
policycoreutils: {}
unar: {}
xz: {}
profiles::yum::global::repos:
fedora:
name: fedora
descr: Fedora %{facts.os.release.major} - %{facts.os.architecture}
target: /etc/yum.repos.d/fedora.repo
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/fedora/releases/%{facts.os.release.major}/Everything/%{facts.os.architecture}/os/
gpgkey: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-%{facts.os.release.major}-%{facts.os.architecture}
metalink: absent
mirrorlist: absent
updates:
name: updates
descr: Fedora %{facts.os.release.major} - %{facts.os.architecture} - Updates
target: /etc/yum.repos.d/fedora-updates.repo
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/fedora/updates/%{facts.os.release.major}/Everything/%{facts.os.architecture}/
gpgkey: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-%{facts.os.release.major}-%{facts.os.architecture}
metalink: absent
mirrorlist: absent
rpm-internal:
name: rpm-internal-f%{facts.os.release.major}
descr: rpm-internal-f%{facts.os.release.major} repository
target: /etc/yum.repos.d/rpm-internal.repo
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/local/rpm-internal-f%{facts.os.release.major}/
gpgcheck: false
mirrorlist: absent
metadata_expire: '60'
rpm-vendor:
name: rpm-vendor-f%{facts.os.release.major}
descr: rpm-vendor-f%{facts.os.release.major} repository
target: /etc/yum.repos.d/rpm-vendor.repo
baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/local/rpm-vendor-f%{facts.os.release.major}/
gpgcheck: false
mirrorlist: absent
metadata_expire: '60'
-5
View File
@@ -2,8 +2,6 @@
hiera_include:
- profiles::nginx::simpleproxy
profiles::puppet::migrate::enabled: true
profiles::yum::global::repos:
ceph:
ensure: present
@@ -67,9 +65,6 @@ profiles::nginx::simpleproxy::locations:
- 127.0.0.1
- "%{facts.networking.ip}"
- 198.18.24.0/24
- 198.18.21.0/24
- 198.18.15.0/24
- 198.18.19.0/24
location_deny:
- all
# authorised access from external
+2
View File
@@ -57,3 +57,5 @@ profiles::yum::global::repos:
ensure: present
rpmfusion-nonfree:
ensure: present
unkinben:
ensure: present
-5
View File
@@ -14,9 +14,6 @@ profiles::pki::vault::alt_names:
- radosgw.service.consul
- radosgw.query.consul
- "radosgw.service.%{facts.country}-%{facts.region}.consul"
# S3 endpoint name (s3.ceph.unkin.net CNAME -> radosgw.service.consul); needed
# on the cert so TLS validates for that hostname.
- s3.ceph.unkin.net
# additional repos
profiles::yum::global::repos:
@@ -29,8 +26,6 @@ profiles::yum::global::repos:
profiles::nginx::simpleproxy::nginx_vhost: 'radosgw.service.consul'
profiles::nginx::simpleproxy::nginx_aliases:
- radosgw.service.au-syd1.consul
# nginx (443 -> radosgw:7480) must answer for the S3 endpoint name too.
- s3.ceph.unkin.net
profiles::nginx::simpleproxy::proxy_port: 7480
profiles::nginx::simpleproxy::proxy_path: '/'
nginx::client_max_body_size: 5000M
+1 -5
View File
@@ -14,8 +14,6 @@ hiera_include:
- profiles::storage::cephfsvols
- exporters::frr_exporter
profiles::puppet::migrate::enabled: true
# FIXME: puppet-python wants to try manage python-dev, which is required by the ceph package
python::manage_dev_package: false
@@ -205,9 +203,7 @@ profiles::accounts::sysadmin::extra_groups:
- incus-admin
# manage cephfs mounts
profiles::ceph::client::manage_ceph_conf: true
# mon/mgr/mds host: render the [mds] + [mds.*] sections
profiles::ceph::client::render_mds_config: true
profiles::ceph::client::manage_ceph_conf: false
profiles::ceph::client::manage_ceph_package: false
profiles::ceph::client::manage_ceph_paths: false
profiles::ceph::client::fsid: 'de96a98f-3d23-465a-a899-86d3d67edab8'
+1 -5
View File
@@ -8,13 +8,9 @@ hiera_include:
- frrouting
- rke2
profiles::puppet::migrate::enabled: true
# manage rke2
rke2::bootstrap_node: prodnxsr0001.main.unkin.net
rke2::join_url: https://join-k8s.service.consul:9345
# pull the airgap image bundle via artifactapi (host-reachable pre-CNI), not github directly
rke2::container_archive_source: 'https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/github/rancher/rke2/releases/download'
rke2::manage_registries: true
rke2::registries:
docker.io:
@@ -94,7 +90,7 @@ profiles::packages::include:
profiles::selinux::setenforce::mode: disabled
profiles::ceph::client::manage_ceph_conf: true
profiles::ceph::client::manage_ceph_conf: false
profiles::ceph::client::manage_ceph_package: false
profiles::ceph::client::manage_ceph_paths: false
profiles::ceph::client::fsid: 'de96a98f-3d23-465a-a899-86d3d67edab8'
-3
View File
@@ -30,9 +30,6 @@ rke2::config_hash:
- '--node-monitor-period=4s'
protect-kernel-defaults: true
disable-kube-proxy: false
# taint the control-plane/etcd nodes so only tolerating workloads schedule here
node-taint:
- "node-role.kubernetes.io/control-plane=true:NoSchedule"
# configure consul service
consul::services:
+1 -1
View File
@@ -33,7 +33,7 @@ profiles::packages::include:
profiles::selinux::setenforce::mode: disabled
profiles::ceph::client::manage_ceph_conf: true
profiles::ceph::client::manage_ceph_conf: false
profiles::ceph::client::manage_ceph_package: false
profiles::ceph::client::manage_ceph_paths: false
profiles::ceph::client::fsid: 'de96a98f-3d23-465a-a899-86d3d67edab8'
-30
View File
@@ -1,30 +0,0 @@
---
hiera_include:
- frrouting
- exporters::frr_exporter
# routing
sysctl::base::values:
net.ipv4.ip_forward:
value: '1'
net.ipv4.conf.all.rp_filter:
value: '0'
net.ipv4.conf.default.rp_filter:
value: '0'
# overrides 50-redhat.conf's per-interface rp_filter=1 (applied by udev on link add); sysctl -n can't glob, so no enforce
net.ipv4.conf.*.rp_filter:
value: '0'
enforce: false
# frrouting
exporters::frr_exporter::enable: true
frrouting::ospfd_redistribute:
- connected
frrouting::daemons:
ospfd: true
# consul
profiles::consul::client::node_rules:
- resource: service
segment: frr_exporter
disposition: write
+3 -16
View File
@@ -1,6 +1,5 @@
---
profiles::puppet::autosign::subnet_ranges:
- '198.18.2.0/24'
- '198.18.13.0/24'
- '198.18.14.0/24'
- '198.18.15.0/24'
@@ -27,19 +26,6 @@ profiles::puppet::cobbler_enc::packages:
- 'requests'
- 'PyYAML'
profiles::puppet::enc::repo: https://git.service.au-syd1.consul/unkinben/puppet-enc.git
# Deep-merged with the entries in roles/infra/puppet.yaml.
profiles::packages::include:
encapic:
ensure: '0.2.0'
certmanager:
ensure: '0.2.0'
sshsignhost:
ensure: '0.1.0'
profiles::puppet::encapic::encapi_url: https://encapi.k8s.syd1.au.unkin.net
profiles::puppet::server::external_nodes: '/usr/bin/encapic-enc'
profiles::puppet::r10k::r10k_repo: https://git.unkin.net/unkin/puppet-r10k.git
profiles::puppet::g10k::bin_path: '/usr/bin/g10k'
profiles::puppet::g10k::cfg_path: '/etc/puppetlabs/r10k/r10k.yaml'
@@ -61,9 +47,10 @@ profiles::helpers::certmanager::vault_config:
profiles::helpers::sshsignhost::vault_config:
addr: 'https://vault.service.consul:8200'
mount_point: 'sshca'
mount_point: 'ssh-host-signer'
approle_path: 'approle'
role_name: 'signhost'
role_name: 'hostrole'
output_path: '/tmp/sshsignhost'
role_id: "%{lookup('sshsignhost::role_id')}"
profiles::puppet::server::agent_server: 'puppet.query.consul'
-24
View File
@@ -13,19 +13,9 @@ profiles::consul::server::addresses:
grpc_tls: "%{::networking.ip}"
profiles::consul::server::ports:
grpc: 8502
grpc_tls: 8503
dns: 8600
http: 8500
https: -1
profiles::consul::server::tls:
defaults:
ca_file: /etc/pki/ca-trust/source/anchors/vaultcaroot.pem
cert_file: /etc/pki/tls/vault/full_chain.crt
key_file: /etc/pki/tls/vault/private.key
internal_rpc:
verify_incoming: false
verify_outgoing: false
verify_server_hostname: false
profiles::consul::server::acl:
enabled: true
default_policy: 'deny'
@@ -41,8 +31,6 @@ profiles::pki::vault::alt_names:
- consul.service.consul
- "consul.service.%{facts.country}-%{facts.region}.consul"
- consul
- "server.%{facts.country}-%{facts.region}.consul"
- "%{facts.networking.fqdn}.server.%{facts.country}-%{facts.region}.consul"
# manage a simple nginx reverse proxy
profiles::nginx::simpleproxy::nginx_vhost: 'consul.service.consul'
@@ -58,18 +46,6 @@ profiles::consul::client::node_rules:
- resource: service
segment: frr_exporter
disposition: write
- resource: service
segment: mesh-gateway
disposition: write
- resource: service_prefix
segment: ''
disposition: read
- resource: node_prefix
segment: ''
disposition: read
- resource: agent_prefix
segment: ''
disposition: read
profiles::consul::prepared_query::rules:
vault:
ensure: 'present'
+1 -20
View File
@@ -27,24 +27,5 @@ profiles::nginx::simpleproxy::proxy_port: 8200
profiles::nginx::simpleproxy::proxy_path: '/'
profiles::packages::include:
# openbao-plugins (base bundle) left unpinned; it tracks the openbao package.
openbao-plugins: {}
# Secrets plugins pinned to the exact version whose binary matches the sha256
# registered in terraform-vault (config/plugins/*.yaml). Bump both in lockstep
# on upgrade, or OpenBao refuses to launch the plugin after a restart.
openbao-plugin-secrets-litellm:
ensure: '0.1.1'
openbao-plugin-secrets-gpg:
ensure: '0.1.0'
openbao-plugin-secrets-rancher:
ensure: '0.1.1'
openbao-plugin-secrets-gitea:
ensure: '0.1.0'
openbao-plugin-secrets-apptoken:
ensure: '0.1.0'
openbao-plugin-secrets-netbox:
ensure: '0.1.0'
openbao-plugin-secrets-ghp:
ensure: '0.1.0'
openbao-plugin-secrets-arrstack:
ensure: '0.2.0'
openbao-plugin-secrets-litellm: {}
-5
View File
@@ -1,8 +1,3 @@
---
# physical hosts only (facts.virtual == 'physical'); merged 'unique' with the
# common hiera_include in profiles::base.
hiera_include:
- profiles::lldpd
profiles::packages::include:
"%{hiera('lm-sensors::package')}": {}
-51
View File
@@ -1,51 +0,0 @@
# manage dnsmasq as a dns forwarder and dhcp relay
class dnsmasq (
Boolean $manage_package = true,
Boolean $manage_service = true,
String $package_name = 'dnsmasq',
String $service_name = 'dnsmasq',
Stdlib::Absolutepath $config_file = '/etc/dnsmasq.conf',
Stdlib::Absolutepath $config_dir = '/etc/dnsmasq.d',
Boolean $purge_config_dir = false,
Array[String] $interfaces = [],
Array[Stdlib::IP::Address] $listen_addresses = ['127.0.0.1'],
Enum['bind-interfaces', 'bind-dynamic', 'none'] $bind_mode = 'bind-interfaces',
Boolean $no_resolv = false,
Array[String] $servers = [],
Hash[String, Array[String]] $forwards = {},
Optional[Integer[0]] $cache_size = undef,
Boolean $domain_needed = true,
Boolean $bogus_priv = true,
Array[String] $dhcp_relays = [],
Array[String] $options = [],
) {
if $manage_package {
package { $package_name:
ensure => installed,
before => File[$config_file, $config_dir],
}
}
file { $config_dir:
ensure => directory,
recurse => $purge_config_dir,
purge => $purge_config_dir,
}
file { $config_file:
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
content => template('dnsmasq/dnsmasq.conf.erb'),
}
if $manage_service {
service { $service_name:
ensure => running,
enable => true,
subscribe => File[$config_file, $config_dir],
}
}
}
@@ -1,40 +0,0 @@
# THIS FILE IS MANAGED BY PUPPET
user=dnsmasq
group=dnsmasq
conf-dir=<%= @config_dir %>,.rpmnew,.rpmsave,.rpmorig
<% @interfaces.each do |iface| -%>
interface=<%= iface %>
<% end -%>
<% unless @listen_addresses.empty? -%>
listen-address=<%= @listen_addresses.join(',') %>
<% end -%>
<% unless @bind_mode == 'none' -%>
<%= @bind_mode %>
<% end -%>
<% if @no_resolv -%>
no-resolv
<% end -%>
<% if @domain_needed -%>
domain-needed
<% end -%>
<% if @bogus_priv -%>
bogus-priv
<% end -%>
<% if @cache_size -%>
cache-size=<%= @cache_size %>
<% end -%>
<% @servers.each do |server| -%>
server=<%= server %>
<% end -%>
<% @forwards.keys.sort.each do |domain| -%>
<% @forwards[domain].each do |server| -%>
server=/<%= domain %>/<%= server %>
<% end -%>
<% end -%>
<% @dhcp_relays.each do |relay| -%>
dhcp-relay=<%= relay %>
<% end -%>
<% @options.each do |line| -%>
<%= line %>
<% end -%>
@@ -1,23 +0,0 @@
# frozen_string_literal: true
require 'facter'
Facter.add(:ceph_osd_devices) do
confine kernel: 'Linux'
confine { Facter.value(:is_virtual) == false }
confine { Facter::Core::Execution.which('pvs') }
setcode do
devices = []
output = Facter::Core::Execution.execute(
'pvs --noheadings -o pv_name,vg_name 2>/dev/null',
on_fail: nil
)
output&.each_line do |line|
pv_name, vg_name = line.split
next unless pv_name && vg_name
devices << pv_name if vg_name.start_with?('ceph-')
end
devices.sort
end
end
+12 -33
View File
@@ -3,21 +3,13 @@
require 'facter'
require 'yaml'
require 'net/http'
require 'openssl'
require 'uri'
require 'fileutils'
# EncapiENC module: Fetches ENC data from encapi, caches it, and provides structured facts.
module EncapiENC
# CobblerENC module: Fetches ENC data from Cobbler, caches it, and provides structured facts.
module CobblerENC
CACHE_FILE = '/var/cache/puppet_enc.yaml'
CACHE_TTL = 7 * 24 * 60 * 60 # 7 days in seconds
# Facter runs under Puppet's vendored ruby, whose OpenSSL trusts only
# /opt/puppetlabs/puppet/ssl/cert.pem and never the system trust store, so the
# estate CA anchor profiles::pki::vaultca installs has to be named explicitly.
CA_BUNDLE_PATHS = [
'/etc/pki/ca-trust/source/anchors/vaultcaroot.pem',
'/usr/local/share/ca-certificates/vaultcaroot.pem'
].freeze
@enc_data = nil # In-memory cache for the ENC response
def self.read_cache
@@ -37,22 +29,9 @@ module EncapiENC
File.write(CACHE_FILE, cache_data.to_yaml)
end
def self.ca_bundle
CA_BUNDLE_PATHS.find { |path| File.exist?(path) }
end
def self.http_client(uri)
client = Net::HTTP.new(uri.host, uri.port)
client.use_ssl = true
client.verify_mode = OpenSSL::SSL::VERIFY_PEER
bundle = ca_bundle
client.ca_file = bundle if bundle
client
end
def self.fetch_from_encapi
uri = URI("https://encapi.k8s.syd1.au.unkin.net/cblr/svc/op/puppet/hostname/#{Facter.value(:fqdn) || Facter.value(:hostname)}")
response = http_client(uri).request(Net::HTTP::Get.new(uri))
def self.fetch_from_cobbler
uri = URI("http://cobbler.main.unkin.net/cblr/svc/op/puppet/hostname/#{Facter.value(:fqdn) || Facter.value(:hostname)}")
response = Net::HTTP.get_response(uri)
raise "Failed to fetch ENC data. HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
@@ -62,7 +41,7 @@ module EncapiENC
def self.retrieve_enc_data
return @enc_data if @enc_data
@enc_data = fetch_from_encapi
@enc_data = fetch_from_cobbler
write_cache(@enc_data)
@enc_data
end
@@ -70,26 +49,26 @@ module EncapiENC
def self.fetch_enc_data
retrieve_enc_data
rescue StandardError => e
Facter.warn("Error retrieving encapi ENC data: #{e.message}")
Facter.warn("Error retrieving Cobbler ENC data: #{e.message}")
@enc_data = read_cache
return @enc_data unless @enc_data.empty?
raise 'No cached ENC data available and encapi is unreachable.'
raise 'No cached ENC data available and Cobbler is down.'
end
def self.enc_role
fetch_enc_data.fetch('classes', {}).keys.first || raise('ENC Role not found in encapi ENC response')
fetch_enc_data.fetch('classes', {}).keys.first || raise('ENC Role not found in Cobbler ENC response')
end
def self.enc_env
fetch_enc_data.fetch('environment', nil) || raise('ENC Environment not found in encapi ENC response')
fetch_enc_data.fetch('environment', nil) || raise('ENC Environment not found in Cobbler ENC response')
end
end
Facter.add('enc_role') do
setcode { EncapiENC.enc_role }
setcode { CobblerENC.enc_role }
end
Facter.add('enc_env') do
setcode { EncapiENC.enc_env }
setcode { CobblerENC.enc_env }
end
-104
View File
@@ -1,104 +0,0 @@
# frozen_string_literal: true
require 'facter'
require 'json'
# Exposes LLDP neighbour topology (switch/port each interface is cabled to) as
# the structured `lldp` fact, keyed by local interface. This is the only source
# of physical switch/port topology in the estate and feeds NetBox. Uses
# `lldpctl -f json0`: json0 wraps every node in an array regardless of
# cardinality, so one neighbour and many neighbours parse identically (plain
# `keyvalue` folds the neighbour's sysname into the key path, and plain `json`
# collapses single-element arrays into objects). Never raises: any error or a
# down daemon yields an empty hash so a puppet run can never break.
module LldpFact
SOCKETS = ['/run/lldpd.socket', '/var/run/lldpd.socket'].freeze
module_function
# First element of a json0 node (everything is array-wrapped), or the value
# itself if it is not an array; nil when absent.
def first(node)
node.is_a?(Array) ? node[0] : node
end
# Array form of a json0 node whatever its cardinality.
def list(node)
node.is_a?(Array) ? node : [node].compact
end
# Value string of a json0 leaf like [{ 'value' => 'x' }].
def leaf(node)
entry = first(node)
entry.is_a?(Hash) ? entry['value'] : entry
end
# Chassis MAC from its id list, preferring the entry typed 'mac'.
def chassis_mac(chassis)
ids = list(chassis['id'])
mac = ids.find { |id| id.is_a?(Hash) && id['type'] == 'mac' } || ids.first
mac.is_a?(Hash) ? mac['value'] : nil
end
# Topology record for one local interface, or nil when it has no neighbour.
def neighbour(iface)
chassis = first(iface['chassis'])
port = first(iface['port'])
return nil unless chassis && port
chassis_fields(chassis).merge(port_fields(port, first(iface['vlan'])))
end
def chassis_fields(chassis)
{
'neighbor_chassis_name' => leaf(chassis['name']),
'neighbor_chassis_mac' => chassis_mac(chassis),
'neighbor_chassis_descr' => leaf(chassis['descr'])
}
end
def port_fields(port, vlan)
port_id = first(port['id'])
vlan_h = vlan.is_a?(Hash) ? vlan : {}
{
'neighbor_port_id' => port_id.is_a?(Hash) ? port_id['value'] : port_id,
'neighbor_port_descr' => leaf(port['descr']),
'vlan_id' => vlan_h['vlan-id'],
'vlan_name' => vlan_h['value']
}
end
def interfaces(output)
lldp = first(JSON.parse(output)['lldp']) || {}
list(lldp['interface'])
end
# Map of local interface => topology record, skipping interfaces with no
# neighbour.
def collect(ifaces)
ifaces.each_with_object({}) do |iface, acc|
next unless iface.is_a?(Hash)
name = iface['name']
data = neighbour(iface)
acc[name] = data if name && data
end
end
def resolve
output = Facter::Core::Execution.execute('lldpctl -f json0 2>/dev/null', on_fail: nil)
return {} if output.to_s.empty?
collect(interfaces(output))
rescue StandardError
{}
end
end
Facter.add(:lldp) do
confine kernel: 'Linux'
confine { Facter.value(:is_virtual) == false }
confine { Facter::Core::Execution.which('lldpctl') }
confine { LldpFact::SOCKETS.any? { |path| File.exist?(path) } }
setcode { LldpFact.resolve }
end
-1
View File
@@ -5,7 +5,6 @@ require 'ipaddr'
# a class that creates facts based on the subnet
class SubnetAttributes
SUBNET_TO_ATTRIBUTES = {
'198.18.2.0/24' => { environment: 'prod', region: 'syd1', country: 'au', zone: 'common' }, # router loopbacks
'198.18.13.0/24' => { environment: 'prod', region: 'syd1', country: 'au', zone: 'common' },
'198.18.14.0/24' => { environment: 'prod', region: 'syd1', country: 'au', zone: 'common' },
'198.18.15.0/24' => { environment: 'prod', region: 'syd1', country: 'au', zone: 'common' },
+2 -2
View File
@@ -6,8 +6,8 @@ class nzbget (
$manage_group = $nzbget::params::manage_group,
$service_enable = $nzbget::params::service_enable,
$service_name = $nzbget::params::service_name,
$bind_address = $nzbget::params::bind_address,
$port = $nzbget::params::port,
$bind_address = $sonarr::params::bind_address,
$port = $sonarr::params::port,
) inherits nzbget::params {
include nzbget::install
+3 -10
View File
@@ -6,17 +6,10 @@ class rke2::install (
Stdlib::HTTPUrl $container_archive_source = $rke2::container_archive_source,
){
# versionlock rke2 before install so the lock exists before any upgrade is attempted
# versionlock rke2
yum::versionlock{"rke2-${node_type}":
ensure => present,
version => "${rke2_version}~${rke2_release}",
before => Package["rke2-${node_type}"],
}
# lock rke2-common (a strict = version dep) so the rolling latest channel can't drift it ahead of the pinned server/agent
yum::versionlock{'rke2-common':
ensure => present,
version => "${rke2_version}~${rke2_release}",
}
# install rke2
@@ -34,10 +27,10 @@ class rke2::install (
before => Service["rke2-${node_type}"],
}
# preload the airgap bundle (has the default canal CNI images) so canal starts from disk, not the mirror VIP that needs flannel first
# download required archive of containers
archive { '/var/lib/rancher/rke2/agent/images/rke2-images.linux-amd64.tar.zst':
ensure => present,
source => "${container_archive_source}/v${rke2_version}%2B${rke2_release}/rke2-images.linux-amd64.tar.zst",
source => "https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/github/rancher/rke2/releases/download/v${rke2_version}%2B${rke2_release}/rke2-images.linux-amd64.tar.zst",
require => [
Package["rke2-${node_type}"],
File['/var/lib/rancher/rke2/agent/images'],
+2 -2
View File
@@ -1,8 +1,8 @@
# rke2 params
class rke2::params (
Enum['server', 'agent'] $node_type = 'agent',
String $rke2_version = '1.33.13',
String $rke2_release = 'rke2r2',
String $rke2_version = '1.33.4',
String $rke2_release = 'rke2r1',
Stdlib::Absolutepath $config_file = '/etc/rancher/rke2/config.yaml',
Hash $config_hash = {},
Stdlib::HTTPSUrl $join_url = 'https://127.0.0.1:9345',
-44
View File
@@ -1,44 +0,0 @@
# manage wireguard interfaces via wg-quick
class wireguard (
Boolean $manage_package = true,
String $package_name = 'wireguard-tools',
Variant[Hash, Sensitive[Hash]] $interfaces = {},
) {
if $manage_package {
package { $package_name:
ensure => installed,
before => File['/etc/wireguard'],
}
}
file { '/etc/wireguard':
ensure => directory,
owner => 'root',
group => 'root',
mode => '0700',
}
# hiera hands eyaml secrets over as plain strings inside the (Sensitive) hash; re-wrap them per resource
$raw = $interfaces ? {
Sensitive => $interfaces.unwrap,
default => $interfaces,
}
$raw.each |String $iface, Hash $data| {
$peers = $data.get('peers', []).map |Hash $peer| {
$peer['preshared_key'] =~ String ? {
true => $peer + { 'preshared_key' => Sensitive($peer['preshared_key']) },
default => $peer,
}
}
$private_key = $data['private_key'] =~ String ? {
true => Sensitive($data['private_key']),
default => $data['private_key'],
}
wireguard::interface { $iface:
* => $data + { 'peers' => $peers, 'private_key' => $private_key },
}
}
}
-63
View File
@@ -1,63 +0,0 @@
# manage one wg-quick interface; without private_key, /etc/wireguard/<iface>.key is generated once and loaded via PostUp
define wireguard::interface (
Array[Stdlib::IP::Address] $addresses,
Optional[Stdlib::Port] $listen_port = undef,
Optional[Integer[1280, 9000]] $mtu = undef,
Optional[Sensitive[String[1]]] $private_key = undef,
Array[Struct[{
public_key => String[1],
allowed_ips => Variant[String[1], Array[String[1], 1]],
preshared_key => Optional[Sensitive[String[1]]],
endpoint => Optional[String[1]],
persistent_keepalive => Optional[Integer[0, 65535]],
}]] $peers = [],
) {
$conf = "/etc/wireguard/${name}.conf"
$key = $private_key.then |$k| { $k.unwrap }
if $private_key =~ Undef {
$keyfile = "/etc/wireguard/${name}.key"
exec { "wireguard_genkey_${name}":
command => "/bin/sh -c 'umask 077; wg genkey > ${keyfile}'",
creates => $keyfile,
path => ['/usr/bin', '/usr/sbin', '/bin', '/sbin'],
require => File['/etc/wireguard'],
}
file { $keyfile:
ensure => file,
owner => 'root',
group => 'root',
mode => '0600',
require => Exec["wireguard_genkey_${name}"],
before => [File[$conf], Service["wg-quick@${name}"]],
}
}
file { $conf:
ensure => file,
owner => 'root',
group => 'root',
mode => '0600',
content => Sensitive(template('wireguard/wg.conf.erb')),
show_diff => false,
notify => Exec["wireguard_syncconf_${name}"],
}
service { "wg-quick@${name}":
ensure => running,
enable => true,
require => File[$conf],
}
# syncconf applies peer/key changes without bouncing the tunnel; address/mtu changes need a manual restart
exec { "wireguard_syncconf_${name}":
command => "/bin/bash -c 'wg syncconf ${name} <(wg-quick strip ${name})'",
onlyif => "/usr/sbin/ip link show ${name}",
path => ['/usr/bin', '/usr/sbin', '/bin', '/sbin'],
refreshonly => true,
require => Service["wg-quick@${name}"],
}
}
-31
View File
@@ -1,31 +0,0 @@
# THIS FILE IS MANAGED BY PUPPET
[Interface]
<% @addresses.each do |addr| -%>
Address = <%= addr %>
<% end -%>
<% if @listen_port -%>
ListenPort = <%= @listen_port %>
<% end -%>
<% if @mtu -%>
MTU = <%= @mtu %>
<% end -%>
<% if @key -%>
PrivateKey = <%= @key %>
<% else -%>
PostUp = wg set %i private-key /etc/wireguard/%i.key
<% end -%>
<% @peers.each do |peer| -%>
[Peer]
PublicKey = <%= peer['public_key'] %>
<% if peer['preshared_key'] -%>
PresharedKey = <%= peer['preshared_key'].unwrap %>
<% end -%>
AllowedIPs = <%= Array(peer['allowed_ips']).join(', ') %>
<% if peer['endpoint'] -%>
Endpoint = <%= peer['endpoint'] %>
<% end -%>
<% if peer['persistent_keepalive'] -%>
PersistentKeepalive = <%= peer['persistent_keepalive'] %>
<% end -%>
<% end -%>
-3
View File
@@ -12,9 +12,6 @@ class profiles::base () {
# manage the puppet agent
include profiles::puppet::agent
include profiles::puppet::client
# k8s migration switch; ships disabled, flip its ::enabled boolean in
# hiera (node/role/common) to repoint a target onto puppet-on-k8s.
include profiles::puppet::migrate
# include the base profiles
include profiles::base::repos
+3 -31
View File
@@ -2,9 +2,6 @@
class profiles::ceph::client (
String $fsid,
Array[Stdlib::Host] $mons,
# cluster topology (single source of truth: hieradata/common.yaml)
Array[Stdlib::Host] $cluster_public_ips,
Array[Stdlib::Host] $mon_initial_members,
Stdlib::Absolutepath $config_file = '/etc/ceph/ceph.conf',
Boolean $manage_ceph_conf = true,
Boolean $manage_ceph_package = true,
@@ -13,24 +10,6 @@ class profiles::ceph::client (
String $group = 'ceph',
Stdlib::Filemode $mode = '0644',
Hash $keyrings = {},
# [global] tunables (defaults match the live hand-maintained ceph.conf)
String $auth_client_required = 'cephx',
String $auth_cluster_required = 'cephx',
String $auth_service_required = 'cephx',
Boolean $mon_allow_pool_delete = true,
Boolean $ms_bind_ipv4 = true,
Boolean $ms_bind_ipv6 = false,
Integer $osd_crush_chooseleaf_type = 1,
Integer $osd_pool_default_min_size = 2,
Integer $osd_pool_default_size = 3,
Integer $osd_pool_default_pg_num = 128,
# mds config sections; only rendered on mon/mgr/mds hosts (render_mds_config)
Boolean $render_mds_config = false,
Hash[String, Integer] $mds_instances = {},
Hash $mds_common = {
'keyring' => '/var/lib/ceph/mds/ceph-$id/keyring',
'mds_standby_replay' => true,
},
) {
# dont run this on proxmox nodes
@@ -43,13 +22,6 @@ class profiles::ceph::client (
}
}
# only depend on the package when this class manages it; on the ceph
# hosts the package is delivered by cephadm / profiles::packages instead.
$config_require = $manage_ceph_package ? {
true => Package['ceph-common'],
default => undef,
}
# manage the ceph directory
if $manage_ceph_paths {
file { '/etc/ceph':
@@ -57,11 +29,11 @@ class profiles::ceph::client (
owner => $owner,
group => $group,
mode => $mode,
require => $config_require,
require => Package['ceph-common'],
}
}
# render /etc/ceph/ceph.conf from cluster topology in hiera
# create a basic client config
if $manage_ceph_conf {
file { $config_file:
ensure => file,
@@ -69,7 +41,7 @@ class profiles::ceph::client (
group => $group,
mode => $mode,
content => template('profiles/ceph/client.conf.erb'),
require => $config_require,
require => Package['ceph-common'],
}
}
-3
View File
@@ -2,9 +2,6 @@ class profiles::ceph::osd (
Boolean $ensure_running = true,
) {
# tune the I/O scheduler on the disks backing ceph OSDs
include profiles::ceph::osd_scheduler
if $ensure_running and $facts['is_ceph_osd'] {
$facts['ceph_services']['osd'].each |String $svc| {
service { $svc:
@@ -1,32 +0,0 @@
class profiles::ceph::osd_scheduler (
String[1] $scheduler = 'none',
) {
$devices = $facts['ceph_osd_devices']
# no-op where the fact is absent/empty (VMs, non-OSD hosts have no ceph PVs)
if $devices =~ Array[String[1], 1] {
# strip /dev/ so the rule matches the udev KERNEL sysname (e.g. sda)
$kernel_names = $devices.map |$dev| { regsubst($dev, '^.*/', '') }
$sysname_matches = $kernel_names.map |$name| { "--sysname-match=${name}" }
file { '/etc/udev/rules.d/60-ceph-osd-scheduler.rules':
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
content => template('profiles/ceph/osd-scheduler.rules.erb'),
notify => Exec['ceph-osd-scheduler-reload'],
}
# apply immediately; udev re-applies on reboot and device re-add
$trigger = "udevadm trigger --subsystem-match=block --action=change ${join($sysname_matches, ' ')}"
exec { 'ceph-osd-scheduler-reload':
command => "udevadm control --reload-rules && ${trigger}",
path => ['/usr/bin', '/bin', '/usr/sbin', '/sbin'],
refreshonly => true,
}
}
}
+1 -4
View File
@@ -68,10 +68,7 @@ class profiles::cobbler::config {
# export cnames for cobbler
if $is_cobbler_master {
profiles::dns::record { "${::facts['networking']['fqdn']}_${service_cname}_CNAME":
# CNAME target must be a fully-qualified name (trailing dot): dns-updater
# parses the value with no $ORIGIN, so a bare label becomes root-absolute
# ("ausyd1nxvm2098.") and dead-ends in NXDOMAIN.
value => "${::facts['networking']['fqdn']}.",
value => $::facts['networking']['hostname'],
type => 'CNAME',
record => "${service_cname}.",
zone => $::facts['networking']['domain'],
@@ -1,52 +0,0 @@
# profiles::consul::mesh_gateway
class profiles::consul::mesh_gateway (
Stdlib::IP::Address $address = $facts['networking']['ip'],
Stdlib::Port $port = 8443,
Stdlib::Port $http_port = 8500,
Stdlib::Port $grpc_port = 8502,
Stdlib::Absolutepath $token_file = '/etc/consul.d/mesh-gateway.token',
) {
include profiles::consul::client
# include after profiles::consul::server, which declares Class['consul'] only once /data is mounted
if defined(Class['consul']) {
package { 'envoy':
ensure => installed,
}
file { $token_file:
ensure => file,
owner => 'consul',
group => 'consul',
mode => '0600',
require => Class['consul'],
content => Sensitive(fqdn_uuid("${facts['networking']['fqdn']}-${profiles::consul::client::secret_id_salt}")),
}
systemd::unit_file { 'consul-mesh-gateway.service':
enable => true,
active => true,
content => @("UNIT"),
[Unit]
Description=Consul mesh gateway (envoy)
Requires=consul.service
After=consul.service
[Service]
User=consul
Group=consul
ExecStart=/usr/bin/consul connect envoy -gateway=mesh -register -service=mesh-gateway -expose-servers \
-address=${address}:${port} -wan-address=${address}:${port} \
-http-addr=http://${address}:${http_port} -grpc-addr=http://${address}:${grpc_port} \
-token-file=${token_file}
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
| UNIT
require => [Package['envoy'], Class['consul']],
subscribe => File[$token_file],
}
}
}
+1 -4
View File
@@ -11,7 +11,6 @@ class profiles::consul::server (
Hash $acl = {},
Hash $ports = {},
Hash $addresses = {},
Hash $tls = {},
Boolean $members_lookup = false,
String $members_role = undef,
Array $consul_servers = [],
@@ -113,8 +112,6 @@ class profiles::consul::server (
'acl' => $acl,
'ports' => $ports,
'addresses' => $addresses,
'tls' => $tls,
'auto_reload_config' => true,
'disable_remote_exec' => $disable_remote_exec,
'disable_update_check' => $disable_update_check,
'domain' => $domain,
@@ -132,7 +129,7 @@ class profiles::consul::server (
'advertise_addr' => $advertise_addr,
'retry_join' => $servers_array,
'retry_join_wan' => $remote_servers_array,
'connect' => { 'enabled' => true, 'enable_mesh_gateway_wan_federation' => true },
'connect' => { 'enabled' => true },
'recursors' => ['198.18.19.16'],
},
}
+21 -66
View File
@@ -23,21 +23,6 @@ class profiles::dns::updater (
Stdlib::AbsolutePath $state_dir = '/var/lib/dns-updater',
Stdlib::AbsolutePath $config_dir = '/etc/dns-updater',
Stdlib::AbsolutePath $master_basedir = lookup('profiles::dns::master::basedir'),
# dns-updater daemon (replaces the dns-update shell script). 'latest' so hosts
# pick up new releases (e.g. the record filter).
String $package_ensure = 'latest',
Stdlib::AbsolutePath $api_socket = '/run/dns-updater/api.sock',
String $resync = '10m',
Enum['debug', 'info', 'warn', 'error'] $log_level = 'info',
Boolean $watch_interfaces = true,
# Never publish records whose address falls in these ranges: the k8s pod CIDR
# (10.42.0.0/16) and service CIDR (10.43.0.0/16), the LB VIP pool
# (198.18.200.0/24) and 198.18.199.0/24. Keeps kube-lb0/flannel/etc. out of
# the authoritative zones and stops NOTAUTH updates for zones we do not host.
Array[String] $deny_ranges = ['198.18.199.0/24', '198.18.200.0/24', '10.42.0.0/16', '10.43.0.0/16'],
Array[String] $allow_ranges = [],
Array[String] $deny_domains = [],
Array[String] $allow_domains = [],
) {
$state_file = "${state_dir}/applied"
@@ -106,66 +91,36 @@ class profiles::dns::updater (
})),
}
# dns-updater daemon: watches the records file (inotify) and network
# interfaces, pushes TSIG-signed RFC2136 updates to $server natively.
package { 'dns-updater':
ensure => $package_ensure,
}
$deny_ranges_str = join($deny_ranges, ',')
$allow_ranges_str = join($allow_ranges, ',')
$deny_domains_str = join($deny_domains, ',')
$allow_domains_str = join($allow_domains, ',')
$env_content = @("ENV")
# Managed by puppet (profiles::dns::updater).
DNS_UPDATER_SERVER=${server}
DNS_UPDATER_KEY_FILE=${key_file}
DNS_UPDATER_RECORDS_FILE=${records_file}
DNS_UPDATER_STATE_FILE=${state_file}
DNS_UPDATER_API=${api_socket}
DNS_UPDATER_RESYNC=${resync}
DNS_UPDATER_WATCH_INTERFACES=${watch_interfaces}
DNS_UPDATER_LOG_LEVEL=${log_level}
DNS_UPDATER_DENY_RANGES=${deny_ranges_str}
DNS_UPDATER_ALLOW_RANGES=${allow_ranges_str}
DNS_UPDATER_DENY_DOMAINS=${deny_domains_str}
DNS_UPDATER_ALLOW_DOMAINS=${allow_domains_str}
| ENV
file { "${config_dir}/env":
file { '/usr/local/bin/dns-update':
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
content => $env_content,
require => [File[$config_dir], Package['dns-updater']],
}
# Restart only on config/key change; records-file changes are picked up by
# the daemon's own inotify watch, so no service churn on record edits.
service { 'dns-updater':
ensure => running,
enable => true,
subscribe => [File["${config_dir}/env"], File[$key_file]],
require => [Package['dns-updater'], Concat[$records_file], File[$key_file]],
}
# Retire the previous shell-based mechanism.
file { '/usr/local/bin/dns-update':
ensure => absent,
mode => '0755',
content => epp('profiles/dns/dns-update.sh.epp', {
'server' => $server,
'key_file' => $key_file,
'records_file' => $records_file,
'state_file' => $state_file,
}),
}
systemd::unit_file { 'dns-update.service':
ensure => absent,
active => false,
enable => false,
content => epp('profiles/dns/dns-update.service.epp', { 'script' => '/usr/local/bin/dns-update' }),
}
# The .path unit watches the records file and triggers the service.
systemd::unit_file { 'dns-update.path':
ensure => absent,
active => false,
enable => false,
content => epp('profiles/dns/dns-update.path.epp', { 'records_file' => $records_file }),
active => true,
enable => true,
}
# Also apply within the puppet run whenever the records change.
exec { 'dns-update-apply':
command => '/usr/local/bin/dns-update',
refreshonly => true,
subscribe => Concat[$records_file],
require => [File['/usr/local/bin/dns-update'], File[$key_file]],
}
}
}
-14
View File
@@ -1,14 +0,0 @@
# base fedora settings
#
# Fedora hosts share the RedHat-family base profiles (yum/dnf, crypto-policies,
# firewalld); this class carries the handful of Fedora-specific bits. Kept
# deliberately small so it can grow into workstation/laptop (e.g. sway) support
# later without disturbing servers.
class profiles::fedora::base {
# Fedora manages interfaces through NetworkManager. Ensure it is enabled and
# running (some minimal and cloud images ship it disabled).
service { 'NetworkManager':
ensure => running,
enable => true,
}
}
+2 -8
View File
@@ -21,10 +21,7 @@ class profiles::haproxy::dns (
$parts = split($cname, '\.')
$domain = join($parts[1, $parts.length], '.')
profiles::dns::record { "${::facts['networking']['fqdn']}_${cname}_CNAME":
# CNAME target must be a fully-qualified name (trailing dot): dns-updater
# parses the value with no $ORIGIN, so a bare label becomes root-absolute
# ("au-syd1-prod-halb.") and dead-ends in NXDOMAIN.
value => "${location_environment}-halb.${facts['networking']['domain']}.",
value => "${location_environment}-halb",
type => 'CNAME',
record => "${cname}.",
zone => $domain,
@@ -61,10 +58,7 @@ class profiles::haproxy::dns (
$parts = split($cname, '\.')
$domain = join($parts[1, $parts.length], '.')
profiles::dns::record { "${::facts['networking']['fqdn']}_${cname}_CNAME":
# Fully-qualified target (trailing dot); see the note on the non-vrrp
# cnames above. The matching A record is published in main.unkin.net
# and unkin.net just above.
value => "${location_environment}-halb-vrrp.${facts['networking']['domain']}.",
value => "${location_environment}-halb-vrrp",
type => 'CNAME',
record => "${cname}.",
zone => $domain,
+63 -14
View File
@@ -1,28 +1,77 @@
# profiles::helpers::certmanager
#
# renders the config.yaml read by the certmanager binary (RPM-installed)
# wrapper class for python, pip and venv
class profiles::helpers::certmanager (
String $script_name = 'certmanager',
Stdlib::AbsolutePath $base_path = "/opt/${script_name}",
Stdlib::AbsolutePath $venv_path = "${base_path}/venv",
Stdlib::AbsolutePath $config_path = "${base_path}/config.yaml",
Hash $vault_config = {},
String $owner = 'root',
String $group = 'root',
Boolean $systempkgs = false,
String $version = 'system',
Array[String[1]] $packages = ['requests', 'pyyaml'],
){
file { $base_path:
ensure => directory,
mode => '0755',
owner => $owner,
group => $group,
}
if $::facts['python3_version'] {
file { $config_path:
ensure => file,
mode => '0660',
owner => 'puppet',
group => 'root',
content => Sensitive(template("profiles/helpers/${script_name}_config.yaml.erb")),
require => File[$base_path],
$python_version = $version ? {
'system' => $::facts['python3_version'],
default => $version,
}
# ensure the base_path exists
file { $base_path:
ensure => directory,
mode => '0755',
owner => $owner,
group => $group,
}
# create a venv
python::pyvenv { $venv_path :
ensure => present,
version => $python_version,
systempkgs => $systempkgs,
venv_dir => $venv_path,
owner => $owner,
group => $group,
require => File[$base_path],
}
# install the required pip packages
$packages.each |String $package| {
python::pip { "${venv_path}_${package}":
ensure => present,
pkgname => $package,
virtualenv => $venv_path,
}
}
# create the script from a template
file { "${base_path}/${script_name}":
ensure => file,
mode => '0755',
content => template("profiles/helpers/${script_name}.erb"),
require => Python::Pyvenv[$venv_path],
}
# create the config from a template
file { $config_path:
ensure => file,
mode => '0660',
owner => 'puppet',
group => 'root',
content => Sensitive(template("profiles/helpers/${script_name}_config.yaml.erb")),
require => Python::Pyvenv[$venv_path],
}
# create symbolic link in $PATH
file { "/usr/local/bin/${script_name}":
ensure => 'link',
target => "${base_path}/${script_name}",
require => File["${base_path}/${script_name}"],
}
}
}
+63 -14
View File
@@ -1,28 +1,77 @@
# profiles::helpers::sshsignhost
#
# renders the config.yaml read by the sshsignhost binary (RPM-installed)
# wrapper class for python, pip and venv
class profiles::helpers::sshsignhost (
String $script_name = 'sshsignhost',
Stdlib::AbsolutePath $base_path = "/opt/${script_name}",
Stdlib::AbsolutePath $venv_path = "${base_path}/venv",
Stdlib::AbsolutePath $config_path = "${base_path}/config.yaml",
Hash $vault_config = {},
String $owner = 'root',
String $group = 'root',
Boolean $systempkgs = false,
String $version = 'system',
Array[String[1]] $packages = ['requests', 'pyyaml'],
){
file { $base_path:
ensure => directory,
mode => '0755',
owner => $owner,
group => $group,
}
if $::facts['python3_version'] {
file { $config_path:
ensure => file,
mode => '0660',
owner => 'puppet',
group => 'root',
content => Sensitive(template("profiles/helpers/${script_name}_config.yaml.erb")),
require => File[$base_path],
$python_version = $version ? {
'system' => $::facts['python3_version'],
default => $version,
}
# ensure the base_path exists
file { $base_path:
ensure => directory,
mode => '0755',
owner => $owner,
group => $group,
}
# create a venv
python::pyvenv { $venv_path :
ensure => present,
version => $python_version,
systempkgs => $systempkgs,
venv_dir => $venv_path,
owner => $owner,
group => $group,
require => File[$base_path],
}
# install the required pip packages
$packages.each |String $package| {
python::pip { "${venv_path}_${package}":
ensure => present,
pkgname => $package,
virtualenv => $venv_path,
}
}
# create the script from a template
file { "${base_path}/${script_name}":
ensure => file,
mode => '0755',
content => template("profiles/helpers/${script_name}.erb"),
require => Python::Pyvenv[$venv_path],
}
# create the config from a template
file { $config_path:
ensure => file,
mode => '0660',
owner => 'puppet',
group => 'root',
content => Sensitive(template("profiles/helpers/${script_name}_config.yaml.erb")),
require => Python::Pyvenv[$venv_path],
}
# create symbolic link in $PATH
file { "/usr/local/bin/${script_name}":
ensure => 'link',
target => "${base_path}/${script_name}",
require => File["${base_path}/${script_name}"],
}
}
}
-33
View File
@@ -1,33 +0,0 @@
# profiles::lldpd
#
# Runs lldpd on physical hosts so each machine learns its switch/port topology
# via LLDP. The `lldp` fact exposes that neighbour data for NetBox. Assigned
# via hiera_include from hieradata/virtual/physical.yaml (physicals only); the
# lldpd.service ships disabled, so it is explicitly enabled and started here.
class profiles::lldpd (
Boolean $enabled = true,
String $package = 'lldpd',
String $service = 'lldpd',
){
if $enabled {
package { $package:
ensure => installed,
}
service { $service:
ensure => running,
enable => true,
subscribe => Package[$package],
}
} else {
service { $service:
ensure => stopped,
enable => false,
}
package { $package:
ensure => absent,
}
}
}
+2 -10
View File
@@ -16,18 +16,11 @@ class profiles::puppet::agent (
$use_service = 'puppet'
}
# OpenVox publishes RedHat-family builds per distribution: enterprise-linux
# under el/<major>, Fedora under fedora/<major>.
$openvox_dist = $facts['os']['name'] ? {
'Fedora' => 'fedora',
default => 'el',
}
# manage the yumrepo for the given package
if $openvox_enable and $facts['os']['family'] == 'RedHat' {
yumrepo { 'openvox':
ensure => 'present',
baseurl => "https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/openvox/openvox7/${openvox_dist}/${facts['os']['release']['major']}/${facts['os']['architecture']}/",
baseurl => "https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/openvox/openvox7/el/${facts['os']['release']['major']}/${facts['os']['architecture']}/",
descr => 'openvox repository',
gpgkey => 'https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/openvox/GPG-KEY-openvox.pub',
notify => Exec['dnf_makecache'],
@@ -60,11 +53,10 @@ class profiles::puppet::agent (
require => Yumrepo[$use_yumrepo],
}
# versionlock puppet-agent before install so the lock exists before any upgrade is attempted
# versionlock puppet-agent
yum::versionlock{$use_package:
ensure => $agent_versionlock_ensure,
version => $agent_versionlock_version,
before => Package[$use_package],
}
}
'Debian': {
+11 -56
View File
@@ -4,64 +4,19 @@
#
# site/profile/manifests/puppet/client.pp
class profiles::puppet::client (
Array $dns_alt_names = [$trusted['certname']],
String $server = 'puppetmaster',
String $ca_server = 'puppetca',
Optional[String] $report_server = undef,
String $environment = 'develop',
Integer $runinterval = 1800,
Integer $runtimeout = 3600,
Boolean $show_diff = true,
Boolean $usecacheonfailure = false,
Integer $facts_soft_limit = 4096,
Boolean $splay = true,
Integer $splaylimit = 600,
Optional[Stdlib::Absolutepath] $ssldir = undef,
Array $dns_alt_names = [$trusted['certname']],
String $server = 'puppetmaster',
String $ca_server = 'puppetca',
String $environment = 'develop',
Integer $runinterval = 1800,
Integer $runtimeout = 3600,
Boolean $show_diff = true,
Boolean $usecacheonfailure = false,
Integer $facts_soft_limit = 4096,
Boolean $splay = true,
Integer $splaylimit = 600,
) {
# Pull in the k8s-migration switch. When
# profiles::puppet::migrate::enabled is true at ANY hiera layer
# (node/role/common) the effective endpoints below flip to the k8s
# servers and a fresh ssldir -- no other hiera keys required. This class
# never includes client.pp, so there is no include cycle.
include profiles::puppet::migrate
# Effective values fed to the template. Precedence differs by param type:
#
# ssldir / report_server (Optional, default undef): an explicit client
# param wins; else the migrate-derived value when enabled; else the
# legacy default (undef -> template omits ssldir; report_server -> server).
#
# server / ca_server (String, no undef sentinel): the migrate value wins
# when migrate::enabled, otherwise the client param (which is the legacy
# default unless an operator set it explicitly in hiera). To pin a
# bespoke server while enabled, leave enabled => false for that target
# and set profiles::puppet::client::server directly.
$migrate_on = $profiles::puppet::migrate::enabled
$effective_server = $migrate_on ? {
true => $profiles::puppet::migrate::new_server,
default => $server,
}
$effective_ca_server = $migrate_on ? {
true => $profiles::puppet::migrate::new_ca_server,
default => $ca_server,
}
# report_server: explicit param wins; else follow the effective server.
$effective_report_server = $report_server ? {
undef => $effective_server,
default => $report_server,
}
# ssldir: explicit param wins; else the fresh k8s ssldir when migrating;
# else undef (template omits the key, i.e. puppet's built-in default).
$effective_ssldir = $ssldir ? {
undef => $migrate_on ? {
true => $profiles::puppet::migrate::new_ssldir,
default => undef,
},
default => $ssldir,
}
# dont manage puppet.conf if this is a puppetmaster
if $facts['enc_role'] != 'roles::infra::puppet::master' {
-32
View File
@@ -1,32 +0,0 @@
# Class: profiles::puppet::encapic
#
# Manages the configuration for the encapic ENC client. The package itself is
# installed through profiles::packages (pinned in hiera); this class owns the
# config so the encapi endpoint can change without repackaging.
class profiles::puppet::encapic (
Stdlib::HTTPUrl $encapi_url,
Stdlib::AbsolutePath $config_dir = '/etc/encapic',
String $config_name = 'encapic.conf',
String $owner = 'root',
String $group = 'root',
) {
# The RPM ships this file as %config(noreplace), so puppet must write it only
# once the package is present or the install overwrites it.
file { $config_dir:
ensure => directory,
mode => '0755',
owner => $owner,
group => $group,
require => Package['encapic'],
}
file { "${config_dir}/${config_name}":
ensure => file,
mode => '0644',
owner => $owner,
group => $group,
content => "ENCAPI_URL=${encapi_url}\n",
require => File[$config_dir],
}
}
-87
View File
@@ -1,87 +0,0 @@
# Class: profiles::puppet::migrate
#
# Repoints a VM puppet agent from the legacy VM puppetmasters onto the new
# puppet-on-kubernetes servers.
#
# The agent's existing ssldir holds a client certificate signed by the OLD CA;
# the new k8s CA will neither trust that cert nor recognise the agent. Rather
# than revoke/clean the old creds (which would burn the rollback path), this
# class switches the agent to a FRESH ssldir. On the next run the agent finds
# an empty ssldir, generates a new key + CSR, submits it to the new CA
# (autosign = true on the k8s side signs it immediately), and then pulls its
# catalog from the new compilers. The OLD ssldir is left untouched on disk, so
# reverting the hiera below restores the node to the legacy masters with no
# re-enrolment.
#
# The catalog that performs the switch is compiled by the OLD server (the node
# still points at it when it applies this change). profiles::puppet::client
# rewrites puppet.conf with the new server / ca_server / report_server / ssldir
# and notifies Service['puppet'], so the very next agent run lands on k8s.
#
# NOTE: this is deliberately hiera-driven per node/role so waves can be rolled
# out and rolled back one target at a time. The class only manages the fresh
# ssldir; the actual puppet.conf changes ride on profiles::puppet::client's
# existing template. client.pp `include`s this class and, when enabled, folds
# new_server / new_ca_server / new_ssldir into the effective server /
# ca_server / report_server / ssldir it renders. Do NOT add a second File
# resource for puppet.conf here -- client.pp already owns it.
#
# == Wiring (single boolean)
#
# This class ships wired into profiles::base with enabled => false, so every
# node evaluates it but nothing changes until the boolean is flipped. Because
# client.pp includes it and reads its params, setting the ONE key below at any
# hiera layer repoints a target -- no client.pp param overrides needed.
#
# The endpoints (new_server / new_ca_server / new_ssldir) are param defaults
# on this class; override them in hiera too if the k8s names ever change.
#
# --- Canary: one node -> hieradata/nodes/<certname>.yaml -------------------
# profiles::puppet::migrate::enabled: true
# ---------------------------------------------------------------------------
#
# --- Wave: a whole role -> hieradata/roles/<t1>/<t2>[/<t3>].yaml -----------
# profiles::puppet::migrate::enabled: true
# ---------------------------------------------------------------------------
#
# --- Estate flip -> hieradata/common.yaml (retires the legacy masters) -----
# profiles::puppet::migrate::enabled: true
# ---------------------------------------------------------------------------
#
# Manual override: profiles::puppet::client::ssldir and ::report_server are
# Optional and, when set explicitly in hiera, win over the migrate-derived
# value. server / ca_server have no undef sentinel, so while enabled => true
# the migrate endpoints win; to point a target at a bespoke server leave
# enabled => false for it and set profiles::puppet::client::server directly.
#
# == Rollback (per target)
#
# Remove the profiles::puppet::migrate::enabled key (or set it back to false)
# at the node/role/common layer. The next run rewrites puppet.conf back to the
# legacy server + legacy ssldir, which still contains the original CA-signed
# cert, and the node is back on the VM masters.
#
# site/profiles/manifests/puppet/migrate.pp
class profiles::puppet::migrate (
Boolean $enabled = false,
String $new_server = 'puppet.k8s.syd1.au.unkin.net',
String $new_ca_server = 'puppetca.k8s.syd1.au.unkin.net',
Stdlib::Absolutepath $new_ssldir = '/etc/puppetlabs/puppet/ssl-k8s',
) {
# never touch the puppet masters themselves (same guard as client.pp)
if $enabled and $facts['enc_role'] != 'roles::infra::puppet::master' {
# Ensure the fresh ssldir exists with agent-correct ownership/perms. The
# puppet agent will create it on demand, but pre-creating it keeps the very
# first post-switch run from racing directory creation and makes the
# migration state auditable. Contents (keys, certs, crl) are populated by
# the agent against the new CA -- we only own the directory itself.
file { $new_ssldir:
ensure => 'directory',
owner => 'root',
group => 'root',
mode => '0771',
}
}
}
@@ -12,7 +12,6 @@ class profiles::puppet::puppetmaster (
include profiles::puppet::g10k
include profiles::puppet::enc
include profiles::puppet::cobbler_enc
include profiles::puppet::encapic
include profiles::puppet::autosign
include profiles::puppet::gems
include profiles::helpers::certmanager
-60
View File
@@ -134,64 +134,4 @@ class profiles::puppet::server (
enable => true,
require => File['/usr/local/bin/puppet_generate_types.sh'],
}
# Guard against an OpenJDK (or other JVM library) package upgrade landing
# underneath a long-running puppetserver. When the java package is replaced
# out-of-band (e.g. a yum repo/version bump), the old versioned JAVA_HOME is
# removed while the JVM keeps running against the now-deleted files. The JVM
# still execs jspawnhelper from its original (deleted) JAVA_HOME on every
# subprocess spawn, so ProcessBuilder fails with "error=2, No such file or
# directory" -- which breaks the exec ENC (/opt/cobbler-enc/cobbler-enc) and
# 500s every catalog compile fleet-wide. A restart re-binds to the new JVM.
# This timer detects the running JVM executing from deleted binaries and
# restarts the service to recover automatically.
file { '/usr/local/bin/puppetserver_jvm_guard.sh':
ensure => file,
mode => '0755',
content => @(EOF),
#!/bin/bash
# Restart puppetserver if its running JVM is executing deleted binaries
# (e.g. an OpenJDK package upgrade replaced the versioned JAVA_HOME),
# which breaks subprocess spawning and thus ENC / catalog compilation.
for pid in $(pgrep -f puppet-server-release.jar); do
exe=$(readlink "/proc/${pid}/exe" 2>/dev/null)
case "${exe}" in
*'(deleted)'*)
logger -t puppetserver-jvm-guard "puppetserver JVM (pid ${pid}) running on deleted binaries; restarting"
systemctl restart puppetserver
exit 0
;;
esac
done
exit 0
| EOF
}
$_guard_timer = @(EOT)
[Unit]
Description=puppetserver JVM guard timer
[Timer]
OnCalendar=*:0/5
RandomizedDelaySec=30s
[Install]
WantedBy=timers.target
EOT
$_guard_service = @(EOT)
[Unit]
Description=puppetserver JVM guard service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/puppetserver_jvm_guard.sh
User=root
Group=root
EOT
systemd::timer { 'puppetserver-jvm-guard.timer':
timer_content => $_guard_timer,
service_content => $_guard_service,
active => true,
enable => true,
require => File['/usr/local/bin/puppetserver_jvm_guard.sh'],
}
}
-14
View File
@@ -130,20 +130,6 @@ class profiles::vault::server (
mode => '0600',
}
# go-plugin creates each secrets plugin's control socket under TMPDIR
# (/tmp/pluginNNN by default); systemd-tmpfiles-clean reaps aged /tmp files
# and severs the socket of a long-lived plugin, orphaning the process. Point
# TMPDIR at a per-start RuntimeDirectory on /run (tmpfs, no age cleanup).
systemd::manage_dropin { 'plugin-tmpdir.conf':
unit => 'vault.service',
service_entry => {
'RuntimeDirectory' => 'vault-plugins',
'RuntimeDirectoryMode' => '0700',
'Environment' => 'TMPDIR=/run/vault-plugins',
},
notify => Service['vault'],
}
service { 'vault':
ensure => true,
enable => true,
+2 -27
View File
@@ -1,28 +1,3 @@
[global]
auth_client_required = <%= @auth_client_required %>
auth_cluster_required = <%= @auth_cluster_required %>
auth_service_required = <%= @auth_service_required %>
fsid = <%= @fsid %>
mon_allow_pool_delete = <%= @mon_allow_pool_delete %>
mon_initial_members = <%= @mon_initial_members.join(',') %>
mon_host = <%= @mons.join(',') %>
ms_bind_ipv4 = <%= @ms_bind_ipv4 %>
ms_bind_ipv6 = <%= @ms_bind_ipv6 %>
osd_crush_chooseleaf_type = <%= @osd_crush_chooseleaf_type %>
osd_pool_default_min_size = <%= @osd_pool_default_min_size %>
osd_pool_default_size = <%= @osd_pool_default_size %>
osd_pool_default_pg_num = <%= @osd_pool_default_pg_num %>
public_network = <%= @cluster_public_ips.map { |ip| "#{ip}/32" }.join(',') %>
<% if @render_mds_config -%>
[mds]
keyring = <%= @mds_common['keyring'] %>
mds_standby_replay = <%= @mds_common['mds_standby_replay'] %>
<% @mds_instances.sort.each do |host, count| -%>
<% (1..count).each do |instance| -%>
[mds.<%= host %>-<%= instance %>]
host = <%= host %>
<% end -%>
<% end -%>
<% end -%>
fsid = <%= @fsid %>
mon_host = <%= @mons.join(' ') %>
@@ -1,5 +0,0 @@
# Managed by puppet (profiles::ceph::osd_scheduler).
# Set the I/O scheduler to <%= @scheduler %> on ceph OSD block devices.
<% @kernel_names.sort.each do |dev| -%>
ACTION=="add|change", SUBSYSTEM=="block", KERNEL=="<%= dev %>", ATTR{queue/scheduler}="<%= @scheduler %>"
<% end -%>
@@ -0,0 +1,10 @@
<%- | String $records_file | -%>
[Unit]
Description=Watch the DNS records file and apply changes
[Path]
PathModified=<%= $records_file %>
Unit=dns-update.service
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,9 @@
<%- | String $script | -%>
[Unit]
Description=Apply host DNS records via nsupdate
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=<%= $script %>
@@ -0,0 +1,56 @@
<%- | String $server, String $key_file, String $records_file, String $state_file | -%>
#!/bin/bash
# Managed by puppet (profiles::dns::updater). Applies this host's records to the
# authoritative DNS server via TSIG nsupdate. Only the delta since the last
# successful run is sent; removed records are deleted.
set -euo pipefail
SERVER="<%= $server %>"
KEYFILE="<%= $key_file %>"
RECORDS="<%= $records_file %>"
STATE="<%= $state_file %>"
[ -f "$RECORDS" ] || exit 0
touch "$STATE"
# Format per line: zone|name|type|ttl|value (name is relative to zone, or @).
desired="$(grep -vE '^[[:space:]]*(#|$)' "$RECORDS" | sort -u || true)"
applied="$(grep -vE '^[[:space:]]*(#|$)' "$STATE" 2>/dev/null | sort -u || true)"
[ "$desired" = "$applied" ] && exit 0
fqdn() { # name zone
if [ -z "$1" ] || [ "$1" = "@" ]; then printf '%s.' "$2"; else printf '%s.%s.' "$1" "$2"; fi
}
msg="$(mktemp)"
trap 'rm -f "$msg"' EXIT
printf 'server %s\n' "$SERVER" >> "$msg"
# Process per zone so each UPDATE message targets a single zone.
zones="$(printf '%s\n%s\n' "$desired" "$applied" | cut -d'|' -f1 | sort -u | grep -v '^$' || true)"
for zone in $zones; do
printf 'zone %s.\n' "$zone" >> "$msg"
# Additions/updates: replace the RRset for every desired record in this zone.
printf '%s\n' "$desired" | awk -F'|' -v z="$zone" 'NF>=5 && $1==z' | \
while IFS='|' read -r z name type ttl value; do
f="$(fqdn "$name" "$z")"
printf 'update delete %s %s\n' "$f" "$type" >> "$msg"
printf 'update add %s %s %s %s\n' "$f" "$ttl" "$type" "$value" >> "$msg"
done
# Deletions: records present last run but gone now.
comm -23 <(printf '%s\n' "$applied") <(printf '%s\n' "$desired") | \
awk -F'|' -v z="$zone" 'NF>=5 && $1==z' | \
while IFS='|' read -r z name type ttl value; do
f="$(fqdn "$name" "$z")"
printf 'update delete %s %s %s\n' "$f" "$type" "$value" >> "$msg"
done
printf 'send\n' >> "$msg"
done
if nsupdate -k "$KEYFILE" "$msg"; then
printf '%s\n' "$desired" > "$STATE"
else
echo "dns-update: nsupdate to ${SERVER} failed" >&2
exit 1
fi
@@ -0,0 +1,102 @@
#!<%= @venv_path %>/bin/python
import argparse
import requests
import json
import os
import yaml
from zipfile import ZipFile
# remove this after certs are generated everywhere
requests.packages.urllib3.disable_warnings()
def load_config(config_path):
with open(config_path, 'r') as file:
config = yaml.safe_load(file)
return config['vault']
def authenticate_approle(vault_config):
url = f"{vault_config['addr']}/v1/auth/{vault_config['approle_path']}/login"
payload = {
"role_id": vault_config['role_id'],
}
response = requests.post(url, json=payload, verify=False)
if response.status_code == 200:
auth_response = response.json()
return auth_response['auth']['client_token']
else:
print(f"Error authenticating with AppRole: {response.text}")
return None
def request_certificate(common_name, alt_names, ip_sans, expiry_days, vault_config):
# Authenticate using AppRole and get a token
client_token = authenticate_approle(vault_config)
if not client_token:
print("Failed to authenticate with Vault using AppRole.")
return None
url = f"{vault_config['addr']}/v1/{vault_config['mount_point']}/issue/{vault_config['role_name']}"
headers = {'X-Vault-Token': client_token}
payload = {
"common_name": common_name,
"alt_names": ",".join(alt_names),
"ip_sans": ",".join(ip_sans),
"ttl": f"{expiry_days}d"
}
response = requests.post(url, headers=headers, json=payload, verify=False)
if response.status_code == 200:
return response.json()
else:
print(f"Error requesting certificate: {response.text}")
return None
def save_cert_files(certificate_response, common_name, compress, config, json_output):
base_path = config.get('output_path', '.')
cert_dir = os.path.join(base_path, common_name)
if json_output:
import json
output = {
'certificate': certificate_response['data']['certificate'],
'private_key': certificate_response['data']['private_key'],
'full_chain': certificate_response['data']['issuing_ca'] + "\n" + certificate_response['data']['certificate'],
}
print(json.dumps(output))
elif not compress:
os.makedirs(cert_dir, exist_ok=True)
with open(os.path.join(cert_dir, "certificate.crt"), "w") as cert_file:
cert_file.write(certificate_response['data']['certificate'])
with open(os.path.join(cert_dir, "private.key"), "w") as key_file:
key_file.write(certificate_response['data']['private_key'])
with open(os.path.join(cert_dir, "full_chain.crt"), "w") as full_chain_file:
full_chain_file.write(certificate_response['data']['issuing_ca'] + "\n" + certificate_response['data']['certificate'])
else:
zip_name = f"{os.path.join(base_path, common_name)}.zip"
with ZipFile(zip_name, 'w') as zipf:
zipf.writestr("certificate.crt", certificate_response['data']['certificate'])
zipf.writestr("private.key", certificate_response['data']['private_key'])
zipf.writestr("full_chain.crt", certificate_response['data']['issuing_ca'] + "\n" + certificate_response['data']['certificate'])
def main(config_file):
config = load_config(config_file)
parser = argparse.ArgumentParser(description='Request and retrieve a certificate from Vault.')
parser.add_argument('common_name', type=str, help='Common Name for the certificate')
parser.add_argument('-a', '--alt-names', type=str, default='', help='Comma-separated alternative names for the certificate')
parser.add_argument('-i', '--ip-sans', type=str, default='', help='Comma-separated IP Subject Alternative Names for the certificate')
parser.add_argument('-e', '--expiry-days', type=int, default=365, help='Validity of the certificate in days (default: 365)')
parser.add_argument('-c', '--compress', action='store_true', help='Compress the certificate, key, and full chain into a zip file')
parser.add_argument('--json', action='store_true', help='Output results in JSON format')
args = parser.parse_args()
alt_names = [name.strip() for name in args.alt_names.split(',') if name]
ip_sans = [ip.strip() for ip in args.ip_sans.split(',') if ip]
certificate_response = request_certificate(args.common_name, alt_names, ip_sans, args.expiry_days, config)
if certificate_response:
if args.json:
save_cert_files(certificate_response, args.common_name, args.compress, config, True)
else:
save_cert_files(certificate_response, args.common_name, args.compress, config, False)
else:
print("Failed to obtain certificate.")
if __name__ == "__main__":
config_file = '<%= @config_path %>'
main(config_file)
@@ -4,4 +4,4 @@ vault:
approle_path: '<%= @vault_config['approle_path'] %>'
mount_point: '<%= @vault_config['mount_point'] %>'
role_name: '<%= @vault_config['role_name'] %>'
output_path: '<%= @vault_config['output_path'] %>'
output_path: '<%= @vault_config['output_path'] %>'
@@ -0,0 +1,83 @@
#!<%= @venv_path %>/bin/python
import argparse
import requests
import json
import yaml
# remove this after certs are generated everywhere
requests.packages.urllib3.disable_warnings()
def load_config(config_path):
with open(config_path, 'r') as file:
config = yaml.safe_load(file)
return config['vault']
def authenticate_approle(vault_config):
url = f"{vault_config['addr']}/v1/auth/{vault_config['approle_path']}/login"
payload = {
"role_id": vault_config['role_id'],
}
response = requests.post(url, json=payload, verify=False)
if response.status_code == 200:
auth_response = response.json()
return auth_response['auth']['client_token']
else:
print(f"Error authenticating with AppRole: {response.text}")
return None
def sign_ssh_certificate(vault_config, public_key, valid_principals, ttl):
# Authenticate using AppRole and get a token
client_token = authenticate_approle(vault_config)
if not client_token:
print("Failed to authenticate with Vault using AppRole.")
return None
# Prepare the SSH certificate signing request
url = f"{vault_config['addr']}/v1/{vault_config['mount_point']}/sign/{vault_config['role_name']}"
headers = {'X-Vault-Token': client_token}
payload = {
"cert_type": "host",
"public_key": public_key,
"valid_principals": valid_principals,
"ttl": ttl
}
# Request the SSH certificate signing
response = requests.post(url, headers=headers, json=payload, verify=False)
if response.status_code == 200:
return response.json()
else:
print(f"Error requesting certificate: {response.text}")
return None
def main(config_file):
config = load_config(config_file)
parser = argparse.ArgumentParser(description='Sign SSH host certificate using Vault.')
parser.add_argument('--public_key', required=True, help='SSH public key as a string')
parser.add_argument('--valid_principals', required=True, help='Comma-separated list of valid principals')
parser.add_argument('--ttl', default='87600h', help='Time-to-live for the certificate (default: 87600h)')
parser.add_argument('--json', action='store_true', help='Output the resulting certificate as JSON')
args = parser.parse_args()
# Load configuration
config = load_config(config_file)
# Sign SSH certificate
response = sign_ssh_certificate(config, args.public_key, args.valid_principals, args.ttl)
if response and 'data' in response and 'signed_key' in response['data']:
if args.json:
output = {
'signed_key': response['data']['signed_key'],
}
print(json.dumps(output))
else:
print(response['data']['signed_key'])
else:
print("Error: The response does not contain the expected data.")
exit(1)
if __name__ == "__main__":
config_file = '<%= @config_path %>'
main(config_file)
@@ -4,3 +4,4 @@ vault:
approle_path: '<%= @vault_config['approle_path'] %>'
mount_point: '<%= @vault_config['mount_point'] %>'
role_name: '<%= @vault_config['role_name'] %>'
output_path: '<%= @vault_config['output_path'] %>'
@@ -1,15 +1,12 @@
[main]
dns_alt_names = <%= @dns_alt_names_string %>
<% unless @effective_ssldir.nil? -%>
ssldir = <%= @effective_ssldir %>
<% end -%>
[agent]
server = <%= @effective_server %>
ca_server = <%= @effective_ca_server %>
server = <%= @server %>
ca_server = <%= @ca_server %>
environment = <%= @environment %>
report = true
report_server = <%= @effective_report_server.nil? ? @effective_server : @effective_report_server %>
report_server = <%= @server %>
runinterval = <%= @runinterval %>
runtimeout = <%= @runtimeout %>
show_diff = <%= @show_diff %>
@@ -1,12 +0,0 @@
# roles::infra::network::router
# an ospf router; frr only, interfaces and firewall are managed outside puppet
#
class roles::infra::network::router {
if $facts['firstrun'] {
include profiles::defaults
include profiles::firstrun::init
}else{
include profiles::defaults
include profiles::base
}
}
@@ -8,6 +8,5 @@ class roles::infra::storage::consul {
include profiles::base
include profiles::base::datavol
include profiles::consul::server
include profiles::consul::mesh_gateway
}
}