Compare commits

..
Author SHA1 Message Date
kurogeek 3612bb2d22 ramus, tangra, flake: apply php backport overlay
Covers every PHP consumer: think/think-backend (php83) on ramus,
wordpress pools (pkgs.php) on tangra, and the perSystem think packages.
2026-10-23 02:30:28 +00:00
kurogeek d13c64f314 overlays: backport nixpkgs#576853 (php 8.3.36/8.4.27/8.5.12)
Temporary overlay until the PR reaches nixpkgs-unstable. Fixes
CVE-2026-109618 and CVE-2026-109619. Becomes a no-op and warns at eval
time once the locked nixpkgs ships these versions; delete it then.
2026-10-23 02:30:27 +00:00
kurogeek 32b1d76dbf flake.lock: bump clan-core to a566329
Fixes devShell build on nixpkgs 5adc508: makeWrapper no longer word-splits
makeWrapperArgs, so clan-cli's "--unset LD_LIBRARY_PATH" single-string
args failed with "makeWrapper doesn't understand the arg". Upstream
clan-core main splits the tokens.
2026-10-22 11:12:07 +00:00
kurogeek 51d7b7eef0 Revert "personal-computer: vendor ipp-usb module with fixed avahi options"
This reverts commit d41fa2210a.
2026-10-22 17:32:15 +07:00
kurogeek a48f1c75c5 personal-computer: vendor ipp-usb module with fixed avahi options
nixpkgs' services/printing/ipp-usb.nix still sets the obsolete
services.avahi.publish.{enable,userServices}, warning on every machine
that enables ipp-usb. Disable the upstream module and carry a copy that
uses services.avahi.settings.publish; only that block differs.
2026-10-22 17:32:15 +07:00
kurogeek 8769b83d42 prometheus: write README
manifest.readme read an empty file, triggering a missing-readme warning.
2026-10-22 17:32:15 +07:00
kurogeek d19cd6c51e emmie: fix hunspell and firefox deprecation warnings
hunspellWithDicts -> hunspell.withDicts; pin programs.firefox.configPath
to the legacy .mozilla/firefox so the profile stays put when stateVersion
crosses 26.05.
2026-10-22 17:32:15 +07:00
kurogeek 862d46b157 samba: migrate obsolete services.avahi.publish options
Use services.avahi.settings.publish as nixpkgs now expects.
2026-10-22 17:32:15 +07:00
kurogeek 7d3dc6c1ee think-gtcm: pin to php83
Default php is now 8.5; composer.lock pins nette/* to php <= 8.4.
Matches think-backend-gtcm and the runtime module.
2026-10-22 17:32:15 +07:00
kurogeek 57e03a0557 emmie: remove wasabiwallet
Removed from nixpkgs (vendored ancient openssl, unmaintained).
2026-10-22 17:32:15 +07:00
kurogeek 47ce38ab4b stellio: drop image.repart.enable shim
nixpkgs declares the option again.
2026-10-22 17:32:15 +07:00
kurogeek e1e4687168 nextcloud: create admin user via occ
nixpkgs removed services.nextcloud.config.{adminuser,adminpassFile} and
now installs with --disable-admin-user. Add a nextcloud-admin-user oneshot
that creates the admin account from the vars secret after nextcloud-setup.
2026-10-22 17:32:15 +07:00
kurogeek 78d4960301 flake.lock: bump nixpkgs to 5adc508, clan-core to 86d72f3
clan-core bumped alongside: the previous rev used lib.licenses.apsl20,
which nixpkgs renamed to apple-psl20.
2026-10-22 17:32:15 +07:00
kurogeek ea9529aef6 gtcm-file-uploader: bump to 895092d (1.1.2) 2026-10-22 09:45:30 +00:00
kurogeek 9a2f3e1ada think-gtcm, think-backend-gtcm: bump to 895092d (1.1.2) 2026-10-22 09:45:30 +00:00
kurogeek a460f345d8 gtcm-file-uploader: bump to 421ed1f (1.1.1) 2026-10-21 09:45:39 +00:00
kurogeek e4133ad242 think-gtcm, think-backend-gtcm: bump to 421ed1f (1.1.1) 2026-10-21 09:45:39 +00:00
kurogeek 759e3ec61b default_thai_company: bump to c8fb983 2026-10-21 03:42:43 +00:00
kurogeek b99cb381c4 gtcm-file-uploader: bump to b67934a (1.1.0) 2026-10-20 08:38:30 +00:00
kurogeek 5540557413 think-gtcm, think-backend-gtcm: bump to b67934a (1.1.0) 2026-10-20 08:33:59 +00:00
kurogeek 50d923442a gtcm-file-uploader: bump to 0658ed2 2026-10-20 03:48:00 +00:00
kurogeek 243c66e0ab think-gtcm, think-backend-gtcm: bump to 0658ed2 2026-10-19 10:57:43 +00:00
kurogeek 72a17fea6e yggdrasil: replace dead public peers, add Asia/US peers
ygg.jjolly.dev:3443 has been offline 21 weeks and
[2602:fc24:18:7a42::1]:993 is unreachable (dropped from the public
peer list); every node was retrying both. Add measured-reachable
peers near the Asian (SG/JP/HK) and US nodes so tree parents are no
longer forced through two US-Midwest relays.
2026-10-09 09:35:15 +00:00
kurogeek 81f4e393e3 vars: update via generator zerotier-ip-mirach-b4l-network (shared) 2026-10-09 14:45:19 +07:00
kurogeek ff88a93280 vars: update via generator zerotier-ip-alpheratz-b4l-network (shared) 2026-10-09 14:44:45 +07:00
kurogeek 9abafc50aa vars: update via generator zerotier-ip-almach-b4l-network (shared) 2026-10-09 14:44:45 +07:00
kurogeek 08f87d630a vars: update via generator zerotier-identity-mirach (shared) 2026-10-09 14:44:44 +07:00
kurogeek b848f709fa vars: update via generator zerotier-ip-adhil-b4l-network (shared) 2026-10-09 14:44:44 +07:00
kurogeek 5174234e95 vars: update via generator zerotier-identity-mirach (shared) 2026-10-09 14:44:44 +07:00
kurogeek 3dcc71de4b vars: update via generator zerotier-identity-buna (shared) 2026-10-09 14:44:44 +07:00
kurogeek 04e1fc2f8e vars: update via generator zerotier-identity-alpheratz (shared) 2026-10-09 14:44:43 +07:00
kurogeek 6350dff658 vars: update via generator zerotier-identity-buna (shared) 2026-10-09 14:44:43 +07:00
kurogeek 5bad8fba97 vars: update via generator zerotier-identity-almach (shared) 2026-10-09 14:44:42 +07:00
kurogeek 9a861fa294 vars: update via generator zerotier-identity-alpheratz (shared) 2026-10-09 14:44:42 +07:00
kurogeek 4536b97366 vars: update via generator zerotier-identity-adhil (shared) 2026-10-09 14:44:42 +07:00
kurogeek 2d13588f77 vars: update via generator zerotier-identity-almach (shared) 2026-10-09 14:44:41 +07:00
kurogeek 46ad94c4b9 vars: update via generator zerotier-identity-adhil (shared) 2026-10-09 14:44:40 +07:00
kurogeek c915103634 phonebox: open port 80 on the yggdrasil interface so the ATA web UI proxy is reachable over ygg too 2026-10-09 07:02:35 +00:00
kurogeek 597f0df6b3 phonebox: move hello-world test extension from 999 to 99 so it is reachable cross-node (e.g. 2099 → rigel) 2026-10-09 03:27:17 +00:00
kurogeek 7f3457859e machines/stellio: stop deprecating a PD prefix the ISP re-delegates (sync with cnx-network 45643d6)
The ISP modem restart at 08:19 re-delegated the same /64 five seconds
after the PPPoE drop. networkd re-announced it at once, but the fixed
7 s radvd run kept sending preferred-lifetime-0 RAs for it, so clients
sat on a deprecated prefix until networkd's next periodic RA and IPv6
stayed broken for minutes.

Send one RA per radvd start, up to three 3 s apart, and skip each one if
the /64 is already back on the interface. Verified on stellio with the
rendered script: a prefix gone for good -> three RAs; the PD address
removed and re-added 4 s later -> two RAs, then "is back, leaving it
announced", client preferred again and pinging over v6.
2026-10-08 08:36:59 +00:00
kurogeek 31ebf2d081 machines/stellio: deprecate withdrawn DHCPv6-PD prefixes (interim copy of cnx-network fa11327)
After the reboot vlan-lan got its /64 at boot, but IPv6 still failed for
clients: the first PPPoE session lands on another AIS BNG, is torn down
minutes later, and the reconnect brings a different /64 (07:53
2405:9800:b911:91e3 -> 07:57 b910:1f5d). networkd announces the new
prefix at once but never deprecates the old one (systemd#29651), so
clients keep it preferred for up to 30 minutes and source selection
sends every connection from a dead address.

router-radv-deprecate.service watches vlan-* for networkd removing a
dynamic address and sends RAs with that /64 at preferred lifetime 0 via
a short-lived radvd (SIGKILLed: no shutdown RA). Identical script to the
upstream commit; drop once the cnx-network input is bumped past it.

Verified on stellio: the stale prefix flipped to deprecated on the
client at the first RA and pings resumed; a synthetic dynamic address
removed from vlan-lan produced the same RA end to end.
2026-10-08 08:13:23 +00:00
kurogeek b5077983ea machines/stellio: give the AIS /64 to vlan-lan, not whichever VLAN came up first
Clients on stellio's lan had no global IPv6. AIS delegates a single /64
over PPPoE; cnx-network's router service carves the VLAN prefixes with
SubnetId=auto, so vlan-mgmt took the whole prefix and vlan-lan logged
"Couldn't find a suitable prefix. Ran out of address space" -- no RA
prefix on lan.

Pin SubnetId: 0x0 on vlan-lan (the whole /64), 0x1 on vlan-mgmt (out of
range on a /64, harmless; works unchanged if AIS ever delegates wider).
Verified live via runtime drop-ins: vlan-lan got 2405:9800:b910:1f5d::/64
and a LAN client pinged 2606:4700:4700::1111 over v6.

Drop once cnx-network carries the per-VLAN `ipv6SubnetId` option
(cnx-network-clan commit 27c697a) and the input is bumped.
2026-10-08 07:54:39 +00:00
kurogeek 0a87664afd default_thai_company: bump version 2026-10-05 15:49:34 +07:00
kurogeek 7469976750 stellio: allow ssh over yggdrasil
The router service only opens SSH to the mgmt VLAN, the staging port
and the ZeroTier mesh. Also admit it on the yggdrasil interface (`ygg`,
as named by clan-core's service) so the box stays reachable when
ZeroTier is down. Interface-scoped, so a spoofed 200::/7 source on the
WAN never matches.
2026-10-02 07:53:47 +00:00
kurogeek c95f74be25 default_thai_company: e7d0a54 -> c6e6c12
Default Standard Sales Order as Sales Order default print format;
position line under signatures on print formats.
2026-10-01 04:51:37 +00:00
kurogeek 2e56bfc54c default_thai_company: 68f0817 -> e7d0a54
Picks up print formats for Purchase Invoice/Order and Sales Order,
Withholding Tax Certificate print format and button, DRAFT watermark
on unsubmitted certificates, Type of Income Paid on Tax Withholding
Category.
2026-10-01 04:19:20 +00:00
kurogeek b4c197e64c w-inventory: allow phone to access 2026-09-30 13:56:52 +07:00
kurogeek 0dc7595f43 poy-inventory -> w-inventory 2026-09-29 18:25:07 +07:00
kurogeek 92a3676b83 remove break 2026-09-29 15:42:11 +07:00
51 changed files with 520 additions and 1254 deletions

No files matched your search

Generated
+23 -23
View File
@@ -151,11 +151,11 @@
]
},
"locked": {
"lastModified": 1788346295,
"narHash": "sha256-k9Ol++FFhQuPya6ZNQwVhqZZkMvE1ytLtbdrbH5zkrI=",
"lastModified": 1792664877,
"narHash": "sha256-yl7J8TPJ9GCG0T5KreuquOpyP2yzBHHaK/IlkO8x8z0=",
"ref": "refs/heads/main",
"rev": "b15797faeca7494a7fe5fde2691d93affa3d3e37",
"revCount": 15239,
"rev": "a566329bbcf0f915c4e6f42c2aee0354e1cd2c12",
"revCount": 15401,
"type": "git",
"url": "https://git.clan.lol/clan/clan-core"
},
@@ -210,11 +210,11 @@
]
},
"locked": {
"lastModified": 1788308203,
"narHash": "sha256-dCOb9YIJv9I2udjqukvjlGiTyOvGnO7zVbot0PxjBGk=",
"rev": "644c49bbf121b3e256bc83ce10b5d97813d9181d",
"lastModified": 1791886485,
"narHash": "sha256-/+7ayxD7PeW6wMQiHw20UJHmSTGN8Y5HXbn0zG/EgoA=",
"rev": "8d4d36437f9fff6ecaa34872bd53c3ed09ef5464",
"type": "tarball",
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/644c49bbf121b3e256bc83ce10b5d97813d9181d.tar.gz"
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/8d4d36437f9fff6ecaa34872bd53c3ed09ef5464.tar.gz"
},
"original": {
"type": "tarball",
@@ -249,11 +249,11 @@
]
},
"locked": {
"lastModified": 1781152676,
"narHash": "sha256-RxWs5ND31KzTG7wvMM+PMfUjyNpmIEr999lqNARaM5o=",
"lastModified": 1789770686,
"narHash": "sha256-uZkBR7yHdIKUFB5SZdfgh1qkGfI3XmYmI/lTiquxbck=",
"owner": "nix-community",
"repo": "disko",
"rev": "ff8702b4de27f72b4c78573dfb89ec74e36abdf1",
"rev": "725ea35e410ad83be4931d1bff7e090eacaf3563",
"type": "github"
},
"original": {
@@ -689,11 +689,11 @@
]
},
"locked": {
"lastModified": 1786845137,
"narHash": "sha256-oQFip+v0luP8NIxJzmiW4Wu8bILsbFWom5l0zonl8hQ=",
"lastModified": 1792366139,
"narHash": "sha256-jto4pdsl0bgErQ5vaMc3gOyj//PILLRJuqX3I7fUk6s=",
"owner": "nix-darwin",
"repo": "nix-darwin",
"rev": "4cff07de74b50e64bdd68cd4e722ab5b6b35ee48",
"rev": "f0ec2b3fae4df92ea7788359aef9e07bb88d720f",
"type": "github"
},
"original": {
@@ -890,11 +890,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1788775869,
"narHash": "sha256-JRf1lSypbvKj6AzUZHpUA6YC1DirVuitZWOnRwcm+Ww=",
"lastModified": 1792506237,
"narHash": "sha256-Q/8KLB6jNfpaUN1HAedE/u/Et9feE/zYSsutBLKrCFk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "58973d74f1893afe13f5902919803a0e99da0ca4",
"rev": "5adc50831c259f87faeb09ddf930d43597197473",
"type": "github"
},
"original": {
@@ -1014,11 +1014,11 @@
]
},
"locked": {
"lastModified": 1788337237,
"narHash": "sha256-gkSH8VUtCo6hnysNmb9DbTuDepH2t5pv+QWjP75xKAk=",
"lastModified": 1792056300,
"narHash": "sha256-YU1LjDe1o2Ymg5zEiCHbmMXLkDCtxHQRiVqZ3nC5GGk=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "fbf759290e0cb0a98dfc813a4eb7d53ad1dacb57",
"rev": "44931708c62937c2b1bf7953902989fa5bb9ee1b",
"type": "github"
},
"original": {
@@ -1105,11 +1105,11 @@
},
"systems": {
"locked": {
"lastModified": 1774449309,
"narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=",
"lastModified": 1791244623,
"narHash": "sha256-uuanbkFk8a313Zfrxdcz8Us/yQtaxNnKJtX8v0ZPpd8=",
"owner": "nix-systems",
"repo": "default",
"rev": "c29398b59d2048c4ab79345812849c9bd15e9150",
"rev": "d36eeed142f88b5a636f2e3856a53c61654cf29b",
"type": "github"
},
"original": {
+1
View File
@@ -100,6 +100,7 @@
inherit system;
overlays = [
inputs.self.overlays.packagesOverlay
inputs.self.overlays.phpPr576853Overlay # TEMPORARY, see overlays/nixpkgs-pr-576853-php.nix
];
config = { };
};
+9 -2
View File
@@ -310,10 +310,17 @@
"200:c8db:ea9b:5bdc:44ed:ad87:462a:6bd0"
];
roles.default.settings.extraPeers = [
"tls://ygg.jjolly.dev:3443"
"tls://[2602:fc24:18:7a42::1]:993"
"tls://133.18.201.69:54232"
"tls://153.120.42.137:54232"
"tcp://yg-sin.magicum.net:23901"
"tls://asia.deinfra.org:15015"
"tls://ygg5.mk16.de:1338?key=0000009611ae5391dc0aceea9f3fa6a0dc1279f4306059339e84bfb8b74d2f9b"
"tcp://leo.node.3dt.net:9002"
"tcp://ygg-kcmo.incognet.io:8883"
"tls://23.184.48.86:1443"
"tls://192.3.13.16:8118"
"tls://ygg-pa.incognet.io:8884"
"tls://mo.us.ygg.triplebit.org:993"
];
};
-11
View File
@@ -1,12 +1,7 @@
{
self,
...
}:
{
imports = [
self.nixosModules.sd-card-image
];
clan.core.settings.machine.description =
"VM machine for collecting prometheus metrics and fire alerts";
@@ -16,10 +11,4 @@
system.stateVersion = "25.11";
clan.core.sops.defaultGroups = [ "admins" ];
image.modules.sd-card = {
disabledModules = [
./disko.nix
];
};
}
+2 -1
View File
@@ -5,7 +5,7 @@
...
}:
let
domain = "poy-inventory.newedge.house";
domain = "w-inventory.newedge.house";
in
{
clan.core.settings.machine.description = "VM machine that host Inventree system for Poy";
@@ -79,6 +79,7 @@ in
secretKeyFile = config.clan.core.vars.generators.inventree.files.secret-key.path;
adminPasswordFile = config.clan.core.vars.generators.inventree.files.admin-password.path;
settings.INVENTREE_SITE_URL = "https://${domain}";
settings.INVENTREE_FRONTEND_SETTINGS = ''{"mobile_mode": "allow-ignore"}'';
};
system.stateVersion = "25.11";
+4 -1
View File
@@ -54,7 +54,10 @@ in
self.nixosModules.think-gtcm
self.nixosModules.think-backend-gtcm
];
nixpkgs.overlays = [ self.overlays.packagesOverlay ];
nixpkgs.overlays = [
self.overlays.packagesOverlay
self.overlays.phpPr576853Overlay # TEMPORARY, see overlays/nixpkgs-pr-576853-php.nix
];
clan.core.vars.generators.greaterchiangmai = {
files = {
+102 -10
View File
@@ -10,16 +10,6 @@
inputs.matthew-hardware.nixosModules.mt7986a-glinet-gl-mt6000
];
# matthew-hardware's common/generic-uefi-image.nix still sets
# `image.repart.enable = true`; nixpkgs removed that option (importing
# image/repart.nix now always defines system.build.image). Declare it so the
# definition has somewhere to land. Drop once upstream stops setting it.
options.image.repart.enable = lib.mkOption {
type = lib.types.bool;
default = true;
internal = true;
};
config = {
hardware.mt7986a-glinet-gl-mt6000.enable = true;
hardware.mt7986a-glinet-gl-mt6000.zealous = true;
@@ -83,6 +73,108 @@
'')
];
# The router service opens SSH only to the mgmt VLAN, the staging port
# and the ZeroTier mesh (inventories/router.nix). Also admit it over
# Yggdrasil (fleet-wide instance in inventories/default.nix) so the box
# stays reachable when ZeroTier's controller/relays are down. Scoped to
# the tunnel interface (clan-core's yggdrasil service names it `ygg`):
# only decrypted 200::/7 traffic arrives there, so a spoofed source on
# the WAN never matches.
networking.firewall.interfaces.${config.services.yggdrasil.settings.IfName}.allowedTCPPorts = [
22
];
# AIS delegates a single /64 over PPPoE (seen 2026-10-02: "ppp0: DHCP:
# received delegated prefix 2405:9800:b910:1f5d::/64"). The router
# service hands out sub-prefixes with SubnetId=auto, so whichever VLAN
# interface comes up first takes the whole /64 -- on this box vlan-mgmt
# did, and vlan-lan logged "Couldn't find a suitable prefix. Ran out of
# address space": no RA prefix on lan, so clients had no global IPv6.
# Pin the ids: lan (where the clients are) always gets subnet 0, i.e.
# the entire prefix when it is a /64; mgmt asks for subnet 1, which
# fails harmlessly on a /64 (mgmt is reached over v4 and the meshes) and
# starts working unchanged if AIS ever delegates wider. Drop once
# cnx-network's router service exposes a per-VLAN subnet id.
systemd.network.networks."40-vlan-lan".dhcpPrefixDelegationConfig.SubnetId = lib.mkForce "0x0";
systemd.network.networks."40-vlan-mgmt".dhcpPrefixDelegationConfig.SubnetId = lib.mkForce "0x1";
# Interim copy of cnx-network-clan 45643d6 (router/ipv6: deprecate
# withdrawn DHCPv6-PD prefixes); drop when the input is bumped past it.
# networkd never deprecates a prefix it stops announcing (systemd#29651):
# on every reboot the first PPPoE session lands on another AIS BNG, is
# torn down minutes later, and the reconnect brings a different /64.
# networkd announces the new prefix at once but clients keep the old
# one preferred for the rest of its 1800 s lifetime, so RFC 6724 source
# selection picks the dead prefix and every v6 connection fails for up
# to 30 minutes (2026-10-08 07:57). Watch vlan-* for networkd removing a
# dynamic (PD) address and send up to three RAs with that /64 at
# preferred lifetime 0, one short-lived radvd per RA (SIGKILLed: no
# shutdown RA with router lifetime 0), stopping as soon as the prefix is
# back on the interface -- a plain flap re-delegates the same /64 within
# seconds and a deprecating RA sent after networkd re-announced it would
# leave clients deprecated until the next periodic RA (08:19 today).
systemd.services.router-radv-deprecate = {
description = "Deprecate withdrawn DHCPv6-PD prefixes on the VLANs";
wantedBy = [ "multi-user.target" ];
after = [ "systemd-networkd.service" ];
path = [
pkgs.iproute2
pkgs.ipcalc
pkgs.radvd
];
serviceConfig = {
Restart = "always";
RestartSec = 2;
RuntimeDirectory = "router-radv-deprecate";
};
script = ''
conf=$RUNTIME_DIRECTORY/radvd.conf
pid=$RUNTIME_DIRECTORY/radvd.pid
# /64s of the global addresses currently on an interface.
prefixes_on() {
ip -o -6 addr show dev "$1" scope global | while read -r _ _ _ a _; do
echo "$(ipcalc --no-decorate -n "$a")/''${a##*/}"
done
}
# "Deleted 4: vlan-lan inet6 2001:db8::1/64 scope global dynamic mngtmpaddr \ valid_lft ..."
ip -o -6 monitor address | while read -r event _ dev _ addr _ scope rest; do
[[ $event == Deleted && $dev == vlan-* && $scope == global && $rest == *dynamic* ]] || continue
prefix="$(ipcalc --no-decorate -n "$addr")/''${addr##*/}"
cat > "$conf" <<EOF
interface $dev {
AdvSendAdvert on;
MinRtrAdvInterval 3;
MaxRtrAdvInterval 4;
AdvDefaultLifetime 1800;
AdvCurHopLimit 0;
prefix $prefix {
AdvOnLink on;
AdvAutonomous on;
AdvPreferredLifetime 0;
AdvValidLifetime 7200;
};
};
EOF
for _ in 1 2 3; do
if prefixes_on "$dev" | grep -qxF "$prefix"; then
echo "$dev: $prefix is back, leaving it announced"
break
fi
echo "$dev: deprecating withdrawn prefix $prefix"
rm -f "$pid"
# radvd sends its first RA on start; the next would follow 3-4 s later.
radvd -C "$conf" -p "$pid" -n -m stderr &
sleep 1
kill -KILL $! 2>/dev/null || true
wait $! || true
sleep 2
done
done
'';
};
systemd.services.heartbeat-push = {
description = "Heartbeat push to uptime monitor";
serviceConfig = {
+4
View File
@@ -1,3 +1,4 @@
{ self, ... }:
{
system.stateVersion = "25.11";
nixpkgs.hostPlatform = {
@@ -29,4 +30,7 @@
security.acme.acceptTerms = true;
imports = [ ];
# TEMPORARY: wordpress php-fpm pools use pkgs.php; see overlays/nixpkgs-pr-576853-php.nix
nixpkgs.overlays = [ self.overlays.phpPr576853Overlay ];
}
+5 -2
View File
@@ -82,8 +82,11 @@ in
};
services.avahi = {
publish.enable = true;
publish.userServices = true;
settings.publish = {
disable-publishing = false;
disable-user-service-publishing = false;
publish-addresses = true;
};
# ^^ Needed to allow samba to automatically register mDNS records (without the need for an `extraServiceFile`
nssmdns4 = true;
# ^^ Not one hundred percent sure if this is needed- if it aint broke, don't fix it
+8 -10
View File
@@ -80,15 +80,12 @@
...
}:
let
dictionaries =
with pkgs;
(hunspellWithDicts (
with hunspellDicts;
[
en-us-large
th-th
]
));
dictionaries = pkgs.hunspell.withDicts (
d: with d; [
en-us-large
th-th
]
);
in
{
imports = [ inputs.plasma-manager.homeModules.plasma-manager ];
@@ -111,7 +108,6 @@
keepassxc
vlc
thunderbird
wasabiwallet
protonmail-desktop
];
};
@@ -119,6 +115,8 @@
programs.firefox = {
enable = true;
# Keep the pre-26.05 profile location; moving it would orphan the existing profile.
configPath = ".mozilla/firefox";
};
programs.home-manager.enable = true;
+19 -2
View File
@@ -42,10 +42,27 @@
dbhost = lib.mkDefault "/run/postgresql";
dbuser = lib.mkDefault "nextcloud";
dbname = lib.mkDefault "nextcloud";
adminuser = lib.mkDefault "admin";
adminpassFile = lib.mkDefault config.clan.core.vars.generators.nextcloud.files.adminpassFile.path;
};
};
# nixpkgs installs Nextcloud with --disable-admin-user; create the
# admin account ourselves once the instance is installed.
systemd.services.nextcloud-admin-user = {
wantedBy = [ "multi-user.target" ];
after = [ "nextcloud-setup.service" ];
requires = [ "nextcloud-setup.service" ];
before = [ "phpfpm-nextcloud.service" ];
path = [ config.services.nextcloud.occ ];
serviceConfig = {
Type = "oneshot";
User = "nextcloud";
};
script = ''
if ! nextcloud-occ user:info admin >/dev/null 2>&1; then
NC_PASS="$(<${config.clan.core.vars.generators.nextcloud.files.adminpassFile.path})" \
nextcloud-occ user:add admin --password-from-env --group admin
fi
'';
};
clan.core.state.nextcloud.folders = [
+2 -2
View File
@@ -31,7 +31,7 @@
server.wait_for_unit("phpfpm-nextcloud.service")
# Check that garage is running
server.succeed("systemctl status phpfpm-nextcloud.service")
# Admin account exists and is in the admin group
server.succeed("nextcloud-occ user:info admin --output=json | grep -q '\"groups\":\\[\"admin\"\\]'")
'';
}
+3 -2
View File
@@ -224,7 +224,7 @@
throw "clanService/yggdrasil is required";
in
{
clan.core.vars.generators.phonebox = builtins.break {
clan.core.vars.generators.phonebox = {
files = {
server-prefix-number.secret = false;
ata-local-number.secret = false;
@@ -330,6 +330,7 @@
in
{
"${matchAll}".allowedTCPPorts = [ 80 ];
"ygg".allowedTCPPorts = [ 80 ];
};
services.asterisk = {
@@ -403,7 +404,7 @@
in
''
[from-internal]
exten => 999,1,Answer()
exten => 99,1,Answer()
same => n,Playback(hello-world)
same => n,Hangup()
+8
View File
@@ -0,0 +1,8 @@
# Prometheus
Monitoring over the Yggdrasil network. Requires the `yggdrasil` clan service on every machine in the instance.
Roles:
- `server`: runs Prometheus and Alertmanager, scraping every `nodes` machine by its Yggdrasil address. Optional `matrix-alertmanager` forwards alerts to Matrix rooms; the bot token and webhook secret are generated as clan vars.
- `nodes`: enables the `services.prometheus.exporters` listed in `exporters` (with `openFirewall`) so the server can harvest them.
+5 -2
View File
@@ -239,8 +239,11 @@
};
services.avahi = {
publish.enable = true;
publish.userServices = true;
settings.publish = {
disable-publishing = false;
disable-user-service-publishing = false;
publish-addresses = true;
};
# ^^ Needed to allow samba to automatically register mDNS records (without the need for an `extraServiceFile`
nssmdns4 = true;
# ^^ Not one hundred percent sure if this is needed- if it aint broke, don't fix it
-3
View File
@@ -9,8 +9,5 @@
think-backend-gtcm = {
imports = [ ./think-backend-gtcm.nix ];
};
sd-card-image = {
imports = [ ./sd-card-image.nix ];
};
};
}
-180
View File
@@ -1,180 +0,0 @@
# Local copy of the clan-core module in patches/upstream-sd-card-secret-injection.patch.
# Delete once clan-core ships clan.core.image.sd-card.addFilesScript
# (the option declaration would conflict).
{
inputs,
lib,
pkgs,
...
}:
let
parseAddFilesArgs = "${inputs.clan-core}/nixosModules/clanCore/image/parse-add-files-args.sh";
# Writes extra files straight into the ext4 root partition of an SD image
# with debugfs, so no boot-time hook is needed: the files exist on the root
# filesystem from the first boot. Runs unprivileged and without mounting.
sdCardAddFilesScript = pkgs.writeShellApplication {
name = "add-files";
runtimeInputs = with pkgs; [
coreutils
diffutils
e2fsprogs
findutils
util-linux
zstd
];
extraShellCheckFlags = [ "-x" ];
text = ''
# shellcheck source=${parseAddFilesArgs}
source ${parseAddFilesArgs}
# If no extra paths, just copy source to output
if [[ ''${#extra_paths[@]} -eq 0 ]]; then
cp -- "$source_image" "$output_image"
exit 0
fi
# The injection sandbox mounts /tmp as a RAM-backed tmpfs and a raw SD
# image is several GiB, so stage it next to the output, the only
# writable on-disk path.
workdir=$(dirname "$output_image")
img="$workdir/.$(basename "$output_image").raw"
cmds=$(mktemp)
trap 'rm -f -- "$img" "$cmds"' EXIT
case "$source_image" in
*.zst)
zstd -d -q --no-progress -o "$img" -- "$source_image"
compress=1
;;
*)
cp --no-preserve=mode -- "$source_image" "$img"
compress=0
;;
esac
chmod u+w -- "$img"
# sd-image.nix writes an MBR whose ext4 root is the (only) type-83 partition.
root_start=""
while read -r _ start type; do
if [[ "$type" == "0x83" ]]; then
root_start=$start
fi
done < <(partx --show -g -o NR,START,TYPE -- "$img")
if [[ -z "$root_start" ]]; then
echo "No Linux (type 83) root partition found in $source_image" >&2
exit 1
fi
# e2fsprogs opens a partition inside a disk image via "<file>?offset=<bytes>".
rootfs="$img?offset=$((root_start * 512))"
# Build a debugfs command script. debugfs has neither mkdir -p nor a
# recursive write, so directories are created ancestor-first ("already
# exists" errors are harmless). write copies the source mode; ownership
# is forced to root since the script runs as an unprivileged user.
declare -A made_dirs=()
written=()
emit_mkdir_p() {
local dir="$1" path="" part
IFS=/ read -ra parts <<< "$dir"
for part in "''${parts[@]}"; do
[[ -z "$part" ]] && continue
path="$path/$part"
if [[ -z "''${made_dirs[$path]:-}" ]]; then
echo "mkdir \"$path\""
made_dirs[$path]=1
fi
done
}
emit_write() {
local src="$1" dest="$2"
echo "write \"$src\" \"$dest\""
echo "sif \"$dest\" uid 0"
echo "sif \"$dest\" gid 0"
written+=("$src" "$dest")
}
for ((i = 0; i < ''${#extra_paths[@]}; i += 2)); do
local_src="''${extra_paths[i]}"
# Normalize to an absolute path without trailing slash; "/" becomes "".
target="/''${extra_paths[i+1]#/}"
target="''${target%/}"
if [[ -d "$local_src" ]]; then
emit_mkdir_p "$target"
while IFS= read -r -d "" entry; do
dest="$target/''${entry#"$local_src"/}"
if [[ -d "$entry" ]]; then
emit_mkdir_p "$dest"
elif [[ -f "$entry" ]]; then
emit_write "$entry" "$dest"
else
echo "Unsupported file type (only regular files and directories): $entry" >&2
exit 1
fi
done < <(
# Never treat our own staging/output directory as input, even if
# the caller placed it inside an extra path.
find "$local_src" -mindepth 1 -path "$workdir" -prune -o -print0
)
elif [[ -f "$local_src" ]]; then
emit_mkdir_p "$(dirname "$target")"
emit_write "$local_src" "$target"
else
echo "Extra path does not exist: $local_src" >&2
exit 1
fi
done > "$cmds"
debugfs -w -f "$cmds" "$rootfs"
# debugfs exits 0 even when commands fail: verify every file landed
# intact and the filesystem is still consistent.
for ((i = 0; i < ''${#written[@]}; i += 2)); do
if ! debugfs -R "cat \"''${written[i+1]}\"" "$rootfs" 2>/dev/null | cmp -s -- - "''${written[i]}"; then
echo "Failed to write ''${written[i+1]} into the root filesystem" >&2
exit 1
fi
done
if ! fsck_output=$(EXT2FS_NO_MTAB_OK=yes e2fsck -fn "$rootfs" 2>&1); then
echo "Root filesystem is inconsistent after injection:" >&2
echo "$fsck_output" >&2
exit 1
fi
if [[ "$compress" == 1 ]]; then
zstd -T0 -q --no-progress --rm -o "$output_image" -- "$img"
else
mv -- "$img" "$output_image"
fi
'';
};
in
{
options.clan.core.image = {
sd-card.addFilesScript = lib.mkOption {
type = lib.types.package;
default = sdCardAddFilesScript;
defaultText = lib.literalExpression "pkgs.writeShellApplication { ... }";
description = ''
Script to inject extra files into an SD card image
(`clan machines build <machine> --format sd-card`).
Files are written directly into the ext4 root partition, so they are
present from the first boot without any initrd hook. Both compressed
(`.img.zst`, the `sdImage.compressImage` default) and raw `.img`
images are supported; the output keeps the source's format.
Source `parse-add-files-args.sh` to get standardized argument parsing.
After sourcing, `$source_image`, `$output_image`, and `$extra_paths`
are available.
Accepts the following arguments:
- --source `<path>`: Path to the original built image (read-only)
- --output `<path>`: Path for the modified output image
- --extra-path `<local-source>` `<target-path>`: Repeatable, maps a local
file or directory to a target filesystem path inside the image
'';
};
};
}
+2
View File
@@ -2,6 +2,8 @@
{
flake.overlays = {
packagesOverlay = import ../pkgs/overlay.nix;
# TEMPORARY: drop with the file once NixOS/nixpkgs#576853 reaches nixpkgs-unstable.
phpPr576853Overlay = import ./nixpkgs-pr-576853-php.nix;
frappixFrappeOverlay = inputs.frappix.frappeOverlay.x86_64-linux;
frappixLibsOverlay = inputs.frappix.libsOverlay.x86_64-linux;
+59
View File
@@ -0,0 +1,59 @@
# TEMPORARY backport of NixOS/nixpkgs#576853
# (php 8.3.36 / 8.4.27 / 8.5.12; fixes CVE-2026-109618, CVE-2026-109619).
# Merged to master 2026-10-21, not yet on nixpkgs-unstable.
# Track: https://track.nix.ee/?pr=576853
#
# Remove once `nix flake update` pulls it in: delete this file and every
# reference to it (grep 576853). The overlay becomes a no-op and warns at eval
# time as soon as the locked nixpkgs ships these versions or newer.
final: prev:
let
inherit (prev) lib;
updates = {
php83 = {
version = "8.3.36";
hash = "sha256-RvS0XjaI7SsYGQFae1MsFbUcwFW907E/0A3f7r7tzV8=";
};
php84 = {
version = "8.4.27";
hash = "sha256-slMKh3xyL0sIyeF0owywi6wrfhkD7m6eMV8KqfX4LCQ=";
};
php85 = {
version = "8.5.12";
hash = "sha256-/7YO3EN87WO1E35JhNs3moERxQBJxxFRDYuVgnfvwag=";
};
};
# php-packages.nix hunk of the PR: the new releases' filter OOM tests only
# pass when the freshly built module is loaded explicitly.
packageOverrides = _: phpPrev: {
extensions = phpPrev.extensions // {
filter = phpPrev.extensions.filter.override {
postConfigure = ''
substituteInPlace \
tests/oom_filter_input.phpt \
tests/oom_filter_input_array.phpt \
tests/oom_filter_default_encoded.phpt \
--replace-fail "'-n'," "'-n', '-d', 'extension=$PWD/modules/filter.so',"
'';
};
};
};
bump =
name:
{ version, hash }:
if lib.versionAtLeast prev.${name}.version version then
lib.warn
"nixpkgs ships ${name} ${prev.${name}.version} (>= ${version}): NixOS/nixpkgs#576853 has landed, remove overlays/nixpkgs-pr-576853-php.nix"
prev.${name}
else
# `.override` on a php buildEnv rebuilds the interpreter with the new
# args, but the extension scope (php-packages) keeps the closure's
# original `packageOverrides`. Only `overrideAttrs` re-enters
# generic.nix with the overridden args and rebuilds that scope, so a
# no-op `overrideAttrs` is what makes `packageOverrides` take effect.
(prev.${name}.override { inherit version hash packageOverrides; }).overrideAttrs (_: { });
in
lib.mapAttrs bump updates
-165
View File
@@ -1,165 +0,0 @@
diff --git a/clan_lib/machines/build.py b/clan_lib/machines/build.py
index 00e2b16..d9ed5a1 100644
--- a/clan_lib/machines/build.py
+++ b/clan_lib/machines/build.py
@@ -98,32 +98,42 @@ def _hostplatform_unset_hint(machine: Machine, build_format: str) -> str:
)
-def _is_image_format(build_format: str) -> bool:
- """Check if the build format is an image format that supports secret injection."""
- return build_format not in ("toplevel")
+def _image_add_files_script(machine: Machine, build_format: str) -> str | None:
+ """Return the format's addFilesScript store path.
+
+ None when clan has no file-injection support for this format
+ (i.e. no `clan.core.image.<format>.addFilesScript` option), in which
+ case secret injection must be skipped rather than fail.
+ """
+ result = machine.flake.select_machine(
+ machine.name,
+ f"config.clan.core.image.?{build_format}.addFilesScript",
+ )
+ return result.get(build_format)
-def _find_image_file(build_dir: Path, build_format: str) -> Path:
- """Find the actual image file within a build output directory.
+def _find_image_file(machine: Machine, build_dir: Path, build_format: str) -> Path:
+ """Resolve the image file within an image build output directory.
- Image builds output a directory (e.g., /nix/store/...-x86_64-linux.iso/)
- containing the image file in a subdirectory named after the format
- (e.g., iso/nixos-26.05-x86_64-linux.iso).
+ Image builds output a directory whose layout is format-specific
+ (iso/nixos-….iso, sd-image/nixos-….img.zst, …). nixpkgs exposes the
+ relative location as `config.image.filePath`, attached to the image
+ derivation as `passthru.filePath`.
"""
- format_dir = build_dir / build_format
- if not format_dir.is_dir():
- msg = f"Expected directory {format_dir} not found in build output"
- raise ClanError(msg)
- files = [f for f in format_dir.iterdir() if f.is_file()]
- if len(files) != 1:
- msg = f"Expected exactly one file in {format_dir}, found {len(files)}"
+ file_path: str = machine.flake.select_machine(
+ machine.name,
+ f"config.system.build.images.{build_format}.passthru.filePath",
+ )
+ image_file = build_dir / file_path
+ if not image_file.is_file():
+ msg = f"Expected image file {image_file} not found in build output"
raise ClanError(msg)
- return files[0]
+ return image_file
def _inject_secrets_into_image(
machine: Machine,
- build_format: str,
+ add_files_script: str,
source_image_path: Path,
output_path: Path,
*,
@@ -137,7 +147,9 @@ def _inject_secrets_into_image(
secrets_target_dir = machine.secret_vars_store.get_upload_directory(machine.name)
with TemporaryDirectory(prefix="clan-build-secrets-") as tmpdir:
- base_dir = Path(tmpdir).resolve()
+ # The tree mapped to "/" inside the image lives in its own subdir so
+ # the format script never sees its own output as input.
+ base_dir = Path(tmpdir).resolve() / "root"
upload_dir = base_dir / secrets_target_dir.lstrip("/")
upload_dir.mkdir(parents=True)
@@ -153,12 +165,6 @@ def _inject_secrets_into_image(
if not any(upload_dir.iterdir()):
return False
- # Get the addFilesScript path from the NixOS config
- script_path = machine.flake.select_machine(
- machine.name,
- f"config.clan.core.image.{build_format}.addFilesScript",
- )
-
# Remove stale output from previous runs (xorriso refuses to
# write to an existing non-empty file when indev != outdev)
output = Path(output_path)
@@ -167,12 +173,12 @@ def _inject_secrets_into_image(
# Write the image into the tmpdir so the sandbox only needs
# write access to the temp directory, not the final destination.
- sandbox_output = base_dir / "output" / output.name
+ sandbox_output = base_dir.parent / "output" / output.name
sandbox_output.parent.mkdir(parents=True)
# Execute the addFilesScript; clean up partial output on failure
inject_cmd: list[str] = [
- f"{script_path}/bin/add-files",
+ f"{add_files_script}/bin/add-files",
"--source",
str(source_image_path),
"--output",
@@ -265,33 +271,40 @@ def build_machine(
if build_path and (store := nix_test_store()):
build_path = store / str(build_path).lstrip("/")
- # Post-build secret injection for image formats
+ # Post-build secret injection for image formats that support it
if (
build_path
- and _is_image_format(options.format)
+ and options.format != "toplevel"
and not options.no_secrets
and not options.dry_run
):
- # build_path is a directory; resolve the actual image file inside
- source_file = _find_image_file(build_path, options.format)
- # Output to a file next to the symlink, named after the source file
- output_file = Path.cwd() / source_file.name
-
- secrets_injected = _inject_secrets_into_image(
- machine=machine,
- build_format=options.format,
- source_image_path=source_file,
- output_path=output_file,
- use_sandbox=options.use_sandbox,
- )
- if secrets_injected:
- log.warning(
- "This image contains secret key material "
- "(age private key). Treat this image as a sensitive "
- "artifact. Do not store it on shared or public media "
- "without encryption. Securely erase after use."
+ add_files_script = _image_add_files_script(machine, options.format)
+ if add_files_script is None:
+ log.info(
+ f"Image format '{options.format}' has no addFilesScript; "
+ "skipping secret injection"
)
- build_path = output_file
+ else:
+ # build_path is a directory; resolve the actual image file inside
+ source_file = _find_image_file(machine, build_path, options.format)
+ # Output to a file next to the symlink, named after the source file
+ output_file = Path.cwd() / source_file.name
+
+ secrets_injected = _inject_secrets_into_image(
+ machine=machine,
+ add_files_script=add_files_script,
+ source_image_path=source_file,
+ output_path=output_file,
+ use_sandbox=options.use_sandbox,
+ )
+ if secrets_injected:
+ log.warning(
+ "This image contains secret key material "
+ "(age private key). Treat this image as a sensitive "
+ "artifact. Do not store it on shared or public media "
+ "without encryption. Securely erase after use."
+ )
+ build_path = output_file
return BuildResult(
machine_name=machine.name,
@@ -1,814 +0,0 @@
From 78a3a5154f42f39bdeb913526439d51cd879f67c Mon Sep 17 00:00:00 2001
From: kurogeek <kurogeek@users.noreply.github.com>
Date: Fri, 25 Sep 2026 10:37:05 +0000
Subject: [PATCH] clan machines build --format sd-card: fix image lookup, add
secret injection
`clan machines build <m> --format sd-card` failed after a successful nix
build:
Expected directory /nix/store/...-nixos-image-sd-card-...img.zst/sd-card not found in build output
Three bugs in the post-build secret injection step (clan_lib/machines/
build.py):
1. _find_image_file assumed the image lives at $out/<format>/. That only
holds for iso. nixpkgs' sd-card image writes $out/sd-image/<name>.img.zst
(nixos/modules/installer/sd-card/sd-image.nix). nixpkgs exposes the
real location as config.image.filePath, attached to every
system.build.images.<format> derivation as passthru.filePath. Use it.
2. Injection ran for every non-toplevel format and then selected
config.clan.core.image.<format>.addFilesScript, which only iso
defines. Probe with the maybe-selector and skip injection (with a log
line) when the format has no addFilesScript, instead of failing after
an expensive image build. --no-secrets is no longer needed for such
formats.
3. The tree passed as `--extra-path <dir> /` and the sandbox output
directory were the same directory, so every addFilesScript saw its
own output as input. The iso script tars the tree before xorriso
writes, so it shipped an empty /output directory in every ISO; a
script that stages a working copy of the image next to the output
would try to inject that copy into itself. Keep them as siblings:
tmpdir/root is mapped to /, tmpdir/output is the only writable path.
Even with those fixed, sd-card images shipped without the age key, so a
freshly flashed machine could not decrypt its secrets on first boot.
Add clan.core.image.sd-card.addFilesScript. Unlike the ISO (read-only
squashfs, needs an initrd hook to unpack /extra-files.tar), the SD
image's root is a plain ext4 partition, so the files are written
directly into it with debugfs: no boot-time component, no mount, no
privileges. The script locates the type-83 root partition with partx,
opens it via e2fsprogs' "<img>?offset=<bytes>" syntax, generates a
debugfs command script (mkdir ancestors, write, sif uid/gid 0), then
verifies every written file with cmp against the source and runs
e2fsck -fn, since debugfs exits 0 even when a command fails. Both
.img.zst (sdImage.compressImage default) and raw .img inputs are
handled; the output keeps the input's format. The raw image is staged
next to the output because the sandbox's /tmp is a RAM tmpfs.
checks/clan-test-sd-card mirrors clan-test-iso: generate vars, run
`clan machines build peer1 --format sd-card` through the real CLI and
injection sandbox, boot the injected image in QEMU and verify over SSH
that a generated secret is decrypted (sops-nix found the injected age
key) and that /var/lib/sops-nix/key.txt is root:root 0600. The x86_64
sd image boots via extlinux.conf, which QEMU cannot load by itself
(nixpkgs' boot-uboot-extlinux test is marked broken), so the test does
what a bootloader would: extract kernel, initrd and APPEND from /boot on
the image's root partition with debugfs and pass them to QEMU with
-kernel/-initrd/-append.
Two unit tests in clan_cli/machines/build_test.py cover the CLI fixes;
both fail on main.
---
checks/clan-test-sd-card/default.nix | 196 ++++++++++++++++++
checks/clan-test-sd-card/flake.nix | 70 +++++++
checks/flake-module.nix | 1 +
nixosModules/clanCore/image/default.nix | 1 +
.../clanCore/image/sd-card/default.nix | 179 ++++++++++++++++
pkgs/clan-cli/clan_cli/machines/build_test.py | 67 +++++-
pkgs/clan-cli/clan_lib/machines/build.py | 107 +++++-----
7 files changed, 573 insertions(+), 48 deletions(-)
create mode 100644 checks/clan-test-sd-card/default.nix
create mode 100644 checks/clan-test-sd-card/flake.nix
create mode 100644 nixosModules/clanCore/image/sd-card/default.nix
diff --git a/checks/clan-test-sd-card/default.nix b/checks/clan-test-sd-card/default.nix
new file mode 100644
index 0000000..e692242
--- /dev/null
+++ b/checks/clan-test-sd-card/default.nix
@@ -0,0 +1,196 @@
+{
+ pkgs,
+ lib,
+ nixosLib,
+ clan-core,
+ ...
+}:
+let
+ system = pkgs.stdenv.hostPlatform.system;
+ qemu-common = import (pkgs.path + "/nixos/lib/qemu-common.nix") {
+ inherit lib;
+ inherit (pkgs) stdenv;
+ };
+in
+nixosLib.runTest (
+ { config, ... }:
+ let
+ closureInfo = pkgs.closureInfo {
+ rootPaths = [
+ config.clan.test.machinesCross.${system}.peer1.config.system.build.images.sd-card.drvPath
+ config.clan.test.machinesCross.${system}.peer1.config.clan.core.image.sd-card.addFilesScript
+ config.clan.test.machinesCross.${system}.peer1.config.sops.package
+ config.clan.test.machinesCross.${system}.peer1.config.sops.gnupg.package
+ config.clan.test.systemsFile
+ config.clan.test.flakeForSandbox
+ ];
+ };
+ in
+ {
+ imports = [
+ clan-core.modules.nixosTest.clanTest
+ ];
+
+ hostPkgs = pkgs;
+
+ name = "clan-test-sd-card";
+
+ clan.test.fromFlake = ./.;
+
+ # No framework-managed nodes; the SD image VM is created dynamically
+ containers = lib.mkForce { };
+
+ testScript = ''
+ import os
+ import shutil
+ import subprocess
+ import tempfile
+ import time
+ from nixos_test_lib.nix_setup import prepare_test_flake
+ from nixos_test_lib.port import find_free_port
+
+
+ # We don't have any machines to start but this is needed, as the container
+ # driver only sets up the environment correctly for nix build if this
+ # function es executed
+ start_all()
+
+ with tempfile.TemporaryDirectory() as temp_dir:
+ flake, _ = prepare_test_flake(temp_dir, "${config.clan.test.flakeForSandbox}", "${closureInfo}")
+
+ # generate sops keys
+ print("Generating vars...")
+ subprocess.run(
+ ["${
+ clan-core.packages.${system}.clan-cli-full
+ }/bin/clan", "vars", "keygen", "--flake", str(flake), "--debug"],
+ check=True,
+ )
+
+ # Test 1: Build the SD image with clan CLI (includes secret injection)
+ print("Building SD image with clan CLI...")
+ result = subprocess.run(
+ ["${
+ clan-core.packages.${system}.clan-cli-full
+ }/bin/clan", "machines", "build", "peer1", "--format", "sd-card", "--flake", str(flake), "--debug"],
+ check=True,
+ stdout=subprocess.PIPE,
+ text=True,
+ )
+ # The last non-empty line of stdout is the injected image path
+ image_path = result.stdout.strip().splitlines()[-1]
+ print(f"Built SD image at: {image_path}")
+ assert os.path.isfile(image_path), f"SD image not found at {image_path}"
+ assert image_path.endswith(".img.zst"), f"expected a compressed image, got {image_path}"
+
+ # Test 2: Boot the image and verify secrets via SSH
+ # QEMU needs a writable raw disk (the root partition is grown on first boot)
+ disk = os.path.join(temp_dir, "disk.img")
+ subprocess.run(["${pkgs.zstd}/bin/zstd", "-d", "-q", "-o", disk, "--", image_path], check=True)
+
+ # The image boots via extlinux.conf, which QEMU cannot load by itself.
+ # Do what the bootloader would: read kernel, initrd and cmdline from
+ # /boot on the root partition and pass them to QEMU directly.
+ root_start = int(subprocess.run(
+ ["${pkgs.util-linux}/bin/partx", "--show", "-g", "-o", "START", "--nr", "2", disk],
+ check=True, capture_output=True, text=True,
+ ).stdout)
+ rootfs = f"{disk}?offset={root_start * 512}"
+ boot_dir = os.path.join(temp_dir, "boot")
+ os.mkdir(boot_dir)
+ subprocess.run(
+ ["${pkgs.e2fsprogs}/bin/debugfs", "-R", f"rdump /boot {boot_dir}", rootfs],
+ check=True, capture_output=True,
+ )
+ extlinux_dir = os.path.join(boot_dir, "boot", "extlinux")
+ entry = {}
+ with open(os.path.join(extlinux_dir, "extlinux.conf")) as f:
+ for line in f:
+ key, _, value = line.strip().partition(" ")
+ if key in ("LINUX", "INITRD", "APPEND") and key not in entry:
+ entry[key] = value
+ kernel = os.path.normpath(os.path.join(extlinux_dir, entry["LINUX"]))
+ initrd = os.path.normpath(os.path.join(extlinux_dir, entry["INITRD"]))
+ print(f"Booting kernel={kernel} initrd={initrd} append={entry['APPEND']!r}")
+
+ host_port = find_free_port()
+
+ # Prepare SSH private key
+ ssh_key = os.path.join(temp_dir, "id_ed25519")
+ shutil.copy("${../assets/ssh/privkey}", ssh_key)
+ os.chmod(ssh_key, 0o600)
+
+ qemu_cmd = [
+ *"${qemu-common.qemuBinary pkgs.qemu_test}".split(),
+ "-m", "2048",
+ "-nographic",
+ "-kernel", kernel,
+ "-initrd", initrd,
+ "-append", entry["APPEND"],
+ "-drive", f"file={disk},format=raw,if=virtio",
+ "-netdev", f"user,id=net0,hostfwd=tcp::{host_port}-:22",
+ "-device", "virtio-net-pci,netdev=net0",
+ ]
+
+ qemu_log = open(os.path.join(temp_dir, "qemu.log"), "wb")
+ qemu_proc = subprocess.Popen(
+ qemu_cmd,
+ stdin=subprocess.DEVNULL,
+ stdout=qemu_log,
+ stderr=subprocess.STDOUT,
+ )
+
+ ssh_cmd = [
+ "${pkgs.openssh}/bin/ssh",
+ "-o", "UserKnownHostsFile=/dev/null",
+ "-o", "StrictHostKeyChecking=no",
+ "-o", "ConnectTimeout=3",
+ "-p", str(host_port),
+ "-i", ssh_key,
+ "root@localhost",
+ ]
+
+ # Wait for SSH to become available by trying to connect
+ print(f"Waiting for SSH on host port {host_port}...")
+ for i in range(300):
+ if qemu_proc.poll() is not None:
+ qemu_log.close()
+ print(open(os.path.join(temp_dir, "qemu.log"), errors="replace").read())
+ raise Exception(f"QEMU exited prematurely with code {qemu_proc.returncode}")
+ ret = subprocess.run(ssh_cmd + ["true"], capture_output=True)
+ if ret.returncode == 0:
+ print(f"SSH available after {i + 1} attempts")
+ break
+ time.sleep(1)
+ else:
+ qemu_log.close()
+ print(open(os.path.join(temp_dir, "qemu.log"), errors="replace").read())
+ raise Exception(f"SSH never became available on port {host_port}")
+
+ # Verify the test-generator secret is available with the expected value
+ result = subprocess.run(
+ ssh_cmd + ["cat /run/secrets/vars/per-machine/peer1/test-generator/test-secret"],
+ check=True,
+ capture_output=True,
+ text=True,
+ )
+ actual_secret = result.stdout.strip()
+ assert actual_secret == "HongKong", f"Secret mismatch: got {actual_secret!r}, expected 'HongKong'"
+
+ # The injected age key must be root-only on the root filesystem
+ result = subprocess.run(
+ ssh_cmd + ["stat -c '%U:%G %a' /var/lib/sops-nix/key.txt"],
+ check=True,
+ capture_output=True,
+ text=True,
+ )
+ assert result.stdout.strip() == "root:root 600", f"unexpected key.txt ownership/mode: {result.stdout.strip()!r}"
+
+ print("All SD image tests passed!")
+
+ qemu_proc.terminate()
+ qemu_proc.wait()
+ qemu_log.close()
+ '';
+ }
+)
diff --git a/checks/clan-test-sd-card/flake.nix b/checks/clan-test-sd-card/flake.nix
new file mode 100644
index 0000000..94a0948
--- /dev/null
+++ b/checks/clan-test-sd-card/flake.nix
@@ -0,0 +1,70 @@
+{
+ inputs.clan-core.url = "https://__replace__";
+ inputs.nixpkgs.url = "https://__replace__";
+ inputs.clan-core.inputs.nixpkgs.follows = "nixpkgs";
+ inputs.systems.url = "https://__systems__";
+ inputs.systems.flake = false;
+
+ outputs =
+ {
+ self,
+ clan-core,
+ systems,
+ ...
+ }:
+ let
+ inherit (clan-core.inputs.nixpkgs) lib;
+
+ hostSystem = lib.head (import systems);
+
+ # Usage see: https://clan.lol/docs
+ clan = clan-core.lib.clan {
+ inherit self;
+
+ machines.peer1 =
+ { modulesPath, ... }:
+ {
+ nixpkgs.hostPlatform = hostSystem;
+
+ imports = [
+ # Required for the NixOS test driver backdoor (hvc0 shell)
+ "${modulesPath}/testing/test-instrumentation.nix"
+ ];
+
+ # The image is booted from a virtio disk in QEMU
+ boot.initrd.availableKernelModules = [
+ "virtio_pci"
+ "virtio_blk"
+ ];
+
+ # Disable vars state version generation
+ clan.core.settings.state-version.enable = true;
+
+ # Enable one secret to be generated
+ clan.core.vars.generators.test-generator = {
+ files.test-secret.secret = true;
+ script = "echo HongKong > $out/test-secret";
+ };
+
+ # Enable SSH for boot verification
+ services.openssh.enable = true;
+ services.openssh.settings.PermitRootLogin = "yes";
+ users.users.root.openssh.authorizedKeys.keys = [
+ (builtins.readFile (clan-core + "/checks/assets/ssh/pubkey"))
+ ];
+ };
+
+ inventory =
+ { ... }:
+ {
+ meta.name = "foo";
+ meta.domain = "foo";
+ machines.peer1 = { };
+ };
+ };
+ in
+ {
+ # all machines managed by Clan
+ inherit (clan.config) nixosConfigurations nixosModules clanInternals;
+ };
+}
diff --git a/checks/flake-module.nix b/checks/flake-module.nix
index b6aa02a..5ce0733 100644
--- a/checks/flake-module.nix
+++ b/checks/flake-module.nix
@@ -94,6 +94,7 @@ in
service-dummy-test = import ./service-dummy-test nixosTestArgs;
service-dummy-test-from-flake = import ./service-dummy-test-from-flake nixosTestArgs;
clan-test-iso = import ./clan-test-iso nixosTestArgs;
+ clan-test-sd-card = import ./clan-test-sd-card nixosTestArgs;
};
packagesToBuild = lib.removeAttrs self'.packages (
diff --git a/nixosModules/clanCore/image/default.nix b/nixosModules/clanCore/image/default.nix
index 7dc3b5a..6191385 100644
--- a/nixosModules/clanCore/image/default.nix
+++ b/nixosModules/clanCore/image/default.nix
@@ -2,5 +2,6 @@
{
imports = [
./iso
+ ./sd-card
];
}
diff --git a/nixosModules/clanCore/image/sd-card/default.nix b/nixosModules/clanCore/image/sd-card/default.nix
new file mode 100644
index 0000000..2ef9fee
--- /dev/null
+++ b/nixosModules/clanCore/image/sd-card/default.nix
@@ -0,0 +1,179 @@
+{
+ lib,
+ pkgs,
+ ...
+}:
+let
+ parseAddFilesArgs = builtins.path {
+ path = ../parse-add-files-args.sh;
+ name = "parse-add-files-args.sh";
+ };
+
+ # Writes extra files straight into the ext4 root partition of an SD image
+ # with debugfs, so no boot-time hook is needed: the files exist on the root
+ # filesystem from the first boot. Runs unprivileged and without mounting.
+ sdCardAddFilesScript = pkgs.writeShellApplication {
+ name = "add-files";
+ runtimeInputs = with pkgs; [
+ coreutils
+ diffutils
+ e2fsprogs
+ findutils
+ util-linux
+ zstd
+ ];
+ extraShellCheckFlags = [ "-x" ];
+ text = ''
+ # shellcheck source=${parseAddFilesArgs}
+ source ${parseAddFilesArgs}
+
+ # If no extra paths, just copy source to output
+ if [[ ''${#extra_paths[@]} -eq 0 ]]; then
+ cp -- "$source_image" "$output_image"
+ exit 0
+ fi
+
+ # The injection sandbox mounts /tmp as a RAM-backed tmpfs and a raw SD
+ # image is several GiB, so stage it next to the output, the only
+ # writable on-disk path.
+ workdir=$(dirname "$output_image")
+ img="$workdir/.$(basename "$output_image").raw"
+ cmds=$(mktemp)
+ trap 'rm -f -- "$img" "$cmds"' EXIT
+
+ case "$source_image" in
+ *.zst)
+ zstd -d -q --no-progress -o "$img" -- "$source_image"
+ compress=1
+ ;;
+ *)
+ cp --no-preserve=mode -- "$source_image" "$img"
+ compress=0
+ ;;
+ esac
+ chmod u+w -- "$img"
+
+ # sd-image.nix writes an MBR whose ext4 root is the (only) type-83 partition.
+ root_start=""
+ while read -r _ start type; do
+ if [[ "$type" == "0x83" ]]; then
+ root_start=$start
+ fi
+ done < <(partx --show -g -o NR,START,TYPE -- "$img")
+ if [[ -z "$root_start" ]]; then
+ echo "No Linux (type 83) root partition found in $source_image" >&2
+ exit 1
+ fi
+ # e2fsprogs opens a partition inside a disk image via "<file>?offset=<bytes>".
+ rootfs="$img?offset=$((root_start * 512))"
+
+ # Build a debugfs command script. debugfs has neither mkdir -p nor a
+ # recursive write, so directories are created ancestor-first ("already
+ # exists" errors are harmless). write copies the source mode; ownership
+ # is forced to root since the script runs as an unprivileged user.
+ declare -A made_dirs=()
+ written=()
+ emit_mkdir_p() {
+ local dir="$1" path="" part
+ IFS=/ read -ra parts <<< "$dir"
+ for part in "''${parts[@]}"; do
+ [[ -z "$part" ]] && continue
+ path="$path/$part"
+ if [[ -z "''${made_dirs[$path]:-}" ]]; then
+ echo "mkdir \"$path\""
+ made_dirs[$path]=1
+ fi
+ done
+ }
+ emit_write() {
+ local src="$1" dest="$2"
+ echo "write \"$src\" \"$dest\""
+ echo "sif \"$dest\" uid 0"
+ echo "sif \"$dest\" gid 0"
+ written+=("$src" "$dest")
+ }
+
+ for ((i = 0; i < ''${#extra_paths[@]}; i += 2)); do
+ local_src="''${extra_paths[i]}"
+ # Normalize to an absolute path without trailing slash; "/" becomes "".
+ target="/''${extra_paths[i+1]#/}"
+ target="''${target%/}"
+
+ if [[ -d "$local_src" ]]; then
+ emit_mkdir_p "$target"
+ while IFS= read -r -d "" entry; do
+ dest="$target/''${entry#"$local_src"/}"
+ if [[ -d "$entry" ]]; then
+ emit_mkdir_p "$dest"
+ elif [[ -f "$entry" ]]; then
+ emit_write "$entry" "$dest"
+ else
+ echo "Unsupported file type (only regular files and directories): $entry" >&2
+ exit 1
+ fi
+ done < <(
+ # Never treat our own staging/output directory as input, even if
+ # the caller placed it inside an extra path.
+ find "$local_src" -mindepth 1 -path "$workdir" -prune -o -print0
+ )
+ elif [[ -f "$local_src" ]]; then
+ emit_mkdir_p "$(dirname "$target")"
+ emit_write "$local_src" "$target"
+ else
+ echo "Extra path does not exist: $local_src" >&2
+ exit 1
+ fi
+ done > "$cmds"
+
+ debugfs -w -f "$cmds" "$rootfs"
+
+ # debugfs exits 0 even when commands fail: verify every file landed
+ # intact and the filesystem is still consistent.
+ for ((i = 0; i < ''${#written[@]}; i += 2)); do
+ if ! debugfs -R "cat \"''${written[i+1]}\"" "$rootfs" 2>/dev/null | cmp -s -- - "''${written[i]}"; then
+ echo "Failed to write ''${written[i+1]} into the root filesystem" >&2
+ exit 1
+ fi
+ done
+ if ! fsck_output=$(EXT2FS_NO_MTAB_OK=yes e2fsck -fn "$rootfs" 2>&1); then
+ echo "Root filesystem is inconsistent after injection:" >&2
+ echo "$fsck_output" >&2
+ exit 1
+ fi
+
+ if [[ "$compress" == 1 ]]; then
+ zstd -T0 -q --no-progress --rm -o "$output_image" -- "$img"
+ else
+ mv -- "$img" "$output_image"
+ fi
+ '';
+ };
+in
+{
+ options.clan.core.image = {
+ sd-card.addFilesScript = lib.mkOption {
+ type = lib.types.package;
+ default = sdCardAddFilesScript;
+ defaultText = lib.literalExpression "pkgs.writeShellApplication { ... }";
+ description = ''
+ Script to inject extra files into an SD card image
+ (`clan machines build <machine> --format sd-card`).
+
+ Files are written directly into the ext4 root partition, so they are
+ present from the first boot without any initrd hook. Both compressed
+ (`.img.zst`, the `sdImage.compressImage` default) and raw `.img`
+ images are supported; the output keeps the source's format.
+
+ Source `parse-add-files-args.sh` to get standardized argument parsing.
+ After sourcing, `$source_image`, `$output_image`, and `$extra_paths`
+ are available.
+
+ Accepts the following arguments:
+ - --source `<path>`: Path to the original built image (read-only)
+ - --output `<path>`: Path for the modified output image
+ - --extra-path `<local-source>` `<target-path>`: Repeatable, maps a local
+ file or directory to a target filesystem path inside the image
+ '';
+ };
+ };
+}
diff --git a/pkgs/clan-cli/clan_cli/machines/build_test.py b/pkgs/clan-cli/clan_cli/machines/build_test.py
index ad0caf0..b5642a0 100644
--- a/pkgs/clan-cli/clan_cli/machines/build_test.py
+++ b/pkgs/clan-cli/clan_cli/machines/build_test.py
@@ -1,8 +1,9 @@
from pathlib import Path
import pytest
-from clan_lib.errors import ClanError
+from clan_lib.errors import ClanError, CmdOut
from clan_lib.flake import Flake
+from clan_lib.machines import build as build_lib
from clan_lib.machines.build import BuildOptions, build_machine
from clan_lib.machines.machines import Machine
@@ -131,3 +132,67 @@ def test_build_machine_without_hostplatform_raises_friendly_error(
assert "nixpkgs.hostPlatform" in msg
assert "--system" in msg
assert "jon" in msg
+
+
+def test_build_machine_skips_injection_for_format_without_add_files_script(
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """Formats without clan.core.image.<format>.addFilesScript (e.g. sd-card)
+ must build successfully and return the raw nix output, not fail trying
+ to locate an image file for injection.
+ """
+ build_dir = tmp_path / "out"
+ (build_dir / "sd-image").mkdir(parents=True)
+ (build_dir / "sd-image" / "nixos.img.zst").touch()
+
+ def fake_run(cmd: list[str], *_args: object, **_kwargs: object) -> CmdOut:
+ return CmdOut(
+ stdout=f"{build_dir}\n",
+ stderr="",
+ env=None,
+ cwd=tmp_path,
+ command_list=cmd,
+ returncode=0,
+ msg=None,
+ )
+
+ def fake_select_machine(_self: Flake, _machine: str, selector: str) -> object:
+ assert selector == "config.clan.core.image.?sd-card.addFilesScript"
+ return {}
+
+ def no_secrets(*_args: object, **_kwargs: object) -> object:
+ msg = "secrets must not be generated for a format without addFilesScript"
+ raise AssertionError(msg)
+
+ monkeypatch.setattr(build_lib, "get_build_target", lambda *_a, **_k: "flake#x")
+ monkeypatch.setattr(build_lib, "run", fake_run)
+ monkeypatch.setattr(build_lib, "get_machine_generators", no_secrets)
+ monkeypatch.setattr(Flake, "select_machine", fake_select_machine)
+
+ machine = Machine(name="jon", flake=Flake(str(tmp_path)))
+ result = build_machine(machine, BuildOptions(format="sd-card", no_link=True))
+
+ assert result.build_path == build_dir
+
+
+def test_find_image_file_uses_image_filepath(
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ """The image lives at config.image.filePath inside the build output,
+ which is not necessarily a directory named after the format
+ (sd-card -> sd-image/...).
+ """
+ image = tmp_path / "sd-image" / "nixos-image-sd-card-x86_64-linux.img.zst"
+ image.parent.mkdir()
+ image.touch()
+
+ def fake_select_machine(_self: Flake, _machine: str, selector: str) -> str:
+ assert selector == "config.system.build.images.sd-card.passthru.filePath"
+ return "sd-image/nixos-image-sd-card-x86_64-linux.img.zst"
+
+ monkeypatch.setattr(Flake, "select_machine", fake_select_machine)
+ machine = Machine(name="jon", flake=Flake(str(tmp_path)))
+
+ assert build_lib._find_image_file(machine, tmp_path, "sd-card") == image
diff --git a/pkgs/clan-cli/clan_lib/machines/build.py b/pkgs/clan-cli/clan_lib/machines/build.py
index 00e2b16..d9ed5a1 100644
--- a/pkgs/clan-cli/clan_lib/machines/build.py
+++ b/pkgs/clan-cli/clan_lib/machines/build.py
@@ -98,32 +98,42 @@ def _hostplatform_unset_hint(machine: Machine, build_format: str) -> str:
)
-def _is_image_format(build_format: str) -> bool:
- """Check if the build format is an image format that supports secret injection."""
- return build_format not in ("toplevel")
+def _image_add_files_script(machine: Machine, build_format: str) -> str | None:
+ """Return the format's addFilesScript store path.
+
+ None when clan has no file-injection support for this format
+ (i.e. no `clan.core.image.<format>.addFilesScript` option), in which
+ case secret injection must be skipped rather than fail.
+ """
+ result = machine.flake.select_machine(
+ machine.name,
+ f"config.clan.core.image.?{build_format}.addFilesScript",
+ )
+ return result.get(build_format)
-def _find_image_file(build_dir: Path, build_format: str) -> Path:
- """Find the actual image file within a build output directory.
+def _find_image_file(machine: Machine, build_dir: Path, build_format: str) -> Path:
+ """Resolve the image file within an image build output directory.
- Image builds output a directory (e.g., /nix/store/...-x86_64-linux.iso/)
- containing the image file in a subdirectory named after the format
- (e.g., iso/nixos-26.05-x86_64-linux.iso).
+ Image builds output a directory whose layout is format-specific
+ (iso/nixos-….iso, sd-image/nixos-….img.zst, …). nixpkgs exposes the
+ relative location as `config.image.filePath`, attached to the image
+ derivation as `passthru.filePath`.
"""
- format_dir = build_dir / build_format
- if not format_dir.is_dir():
- msg = f"Expected directory {format_dir} not found in build output"
- raise ClanError(msg)
- files = [f for f in format_dir.iterdir() if f.is_file()]
- if len(files) != 1:
- msg = f"Expected exactly one file in {format_dir}, found {len(files)}"
+ file_path: str = machine.flake.select_machine(
+ machine.name,
+ f"config.system.build.images.{build_format}.passthru.filePath",
+ )
+ image_file = build_dir / file_path
+ if not image_file.is_file():
+ msg = f"Expected image file {image_file} not found in build output"
raise ClanError(msg)
- return files[0]
+ return image_file
def _inject_secrets_into_image(
machine: Machine,
- build_format: str,
+ add_files_script: str,
source_image_path: Path,
output_path: Path,
*,
@@ -137,7 +147,9 @@ def _inject_secrets_into_image(
secrets_target_dir = machine.secret_vars_store.get_upload_directory(machine.name)
with TemporaryDirectory(prefix="clan-build-secrets-") as tmpdir:
- base_dir = Path(tmpdir).resolve()
+ # The tree mapped to "/" inside the image lives in its own subdir so
+ # the format script never sees its own output as input.
+ base_dir = Path(tmpdir).resolve() / "root"
upload_dir = base_dir / secrets_target_dir.lstrip("/")
upload_dir.mkdir(parents=True)
@@ -153,12 +165,6 @@ def _inject_secrets_into_image(
if not any(upload_dir.iterdir()):
return False
- # Get the addFilesScript path from the NixOS config
- script_path = machine.flake.select_machine(
- machine.name,
- f"config.clan.core.image.{build_format}.addFilesScript",
- )
-
# Remove stale output from previous runs (xorriso refuses to
# write to an existing non-empty file when indev != outdev)
output = Path(output_path)
@@ -167,12 +173,12 @@ def _inject_secrets_into_image(
# Write the image into the tmpdir so the sandbox only needs
# write access to the temp directory, not the final destination.
- sandbox_output = base_dir / "output" / output.name
+ sandbox_output = base_dir.parent / "output" / output.name
sandbox_output.parent.mkdir(parents=True)
# Execute the addFilesScript; clean up partial output on failure
inject_cmd: list[str] = [
- f"{script_path}/bin/add-files",
+ f"{add_files_script}/bin/add-files",
"--source",
str(source_image_path),
"--output",
@@ -265,33 +271,40 @@ def build_machine(
if build_path and (store := nix_test_store()):
build_path = store / str(build_path).lstrip("/")
- # Post-build secret injection for image formats
+ # Post-build secret injection for image formats that support it
if (
build_path
- and _is_image_format(options.format)
+ and options.format != "toplevel"
and not options.no_secrets
and not options.dry_run
):
- # build_path is a directory; resolve the actual image file inside
- source_file = _find_image_file(build_path, options.format)
- # Output to a file next to the symlink, named after the source file
- output_file = Path.cwd() / source_file.name
-
- secrets_injected = _inject_secrets_into_image(
- machine=machine,
- build_format=options.format,
- source_image_path=source_file,
- output_path=output_file,
- use_sandbox=options.use_sandbox,
- )
- if secrets_injected:
- log.warning(
- "This image contains secret key material "
- "(age private key). Treat this image as a sensitive "
- "artifact. Do not store it on shared or public media "
- "without encryption. Securely erase after use."
+ add_files_script = _image_add_files_script(machine, options.format)
+ if add_files_script is None:
+ log.info(
+ f"Image format '{options.format}' has no addFilesScript; "
+ "skipping secret injection"
)
- build_path = output_file
+ else:
+ # build_path is a directory; resolve the actual image file inside
+ source_file = _find_image_file(machine, build_path, options.format)
+ # Output to a file next to the symlink, named after the source file
+ output_file = Path.cwd() / source_file.name
+
+ secrets_injected = _inject_secrets_into_image(
+ machine=machine,
+ add_files_script=add_files_script,
+ source_image_path=source_file,
+ output_path=output_file,
+ use_sandbox=options.use_sandbox,
+ )
+ if secrets_injected:
+ log.warning(
+ "This image contains secret key material "
+ "(age private key). Treat this image as a sensitive "
+ "artifact. Do not store it on shared or public media "
+ "without encryption. Securely erase after use."
+ )
+ build_path = output_file
return BuildResult(
machine_name=machine.name,
--
2.54.0
+2 -2
View File
@@ -11,8 +11,8 @@ let
domain = "git.b4l.co.th";
owner = "newedge";
repo = "default_thai_company";
rev = "68f08171252099920d52f5c47800d65ddf4689b3";
sha256 = "sha256-9UomP8G5K+nkAMCfn+gXbBNivLlDo0c/36FYZgHm2Cw=";
rev = "c8fb98374989a22d0c907aa44877f247391a49ac";
sha256 = "sha256-c+pqds4l3Laldgf3+Rj2Ug693kFJYDB1j4a15j4U5SY=";
};
in
buildPythonPackage (finalAttrs: {
+3 -4
View File
@@ -7,18 +7,17 @@
let
repoSrc = fetchgit {
url = "https://git.b4l.co.th/newedge/think-greaterchiangmai";
rev = "6f8c8d7dfaf5a0c1eb2077de1d6fb35ceaf3d4ec";
hash = "sha256-2mCdn8xGjWZrANclctGTmxQhkNc43VzlzMTVwVIFJcM=";
rev = "895092dea1da99ca1fa3c5c6d805384e579d417e";
hash = "sha256-ffbwQVQ1lL8XpubsGX6FTbJOjz1mbqCn+AKvR7xJhRw=";
};
src = "${repoSrc}/upload-large-file";
in
buildNpmPackage {
pname = "gtcm-file-uploader";
version = "1.0.0";
version = "1.1.2";
nativeBuildInputs = with pkgs; [
nodejs
breakpointHook
];
inherit src;
+1 -1
View File
@@ -1,5 +1,5 @@
final: prev: {
think-gtcm = final.callPackage ./think-gtcm.nix { };
think-gtcm = final.callPackage ./think-gtcm.nix { php = final.php83; };
think-backend-gtcm = final.callPackage ./think-backend-gtcm.nix { php = final.php83; };
gtcm-file-uploader = final.callPackage ./gtcm-file-uploader.nix { };
}
+4 -4
View File
@@ -6,14 +6,14 @@
let
repoSrc = fetchgit {
url = "https://git.b4l.co.th/newedge/think-greaterchiangmai";
rev = "7c17aa78436538241c09fc7d633904d3c063011e";
hash = "sha256-GDx0+PmuCXC+UPtsvsocCZQiTPcnOZEzJI17sxrVv7Q=";
rev = "895092dea1da99ca1fa3c5c6d805384e579d417e";
hash = "sha256-ffbwQVQ1lL8XpubsGX6FTbJOjz1mbqCn+AKvR7xJhRw=";
};
src = "${repoSrc}/think-backend.greaterchiangmai.com";
in
php.buildComposerProject2 (finalAttrs: {
pname = "think-backend-gtcm";
version = "1.0.0";
version = "1.1.2";
inherit src;
@@ -32,5 +32,5 @@ php.buildComposerProject2 (finalAttrs: {
'';
composerStrictValidation = false;
vendorHash = "sha256-wGfbprSDULBje1s5y3+ZiU/nCwYGDEULobZzyzGZ9bQ=";
vendorHash = "sha256-1cY33xOF0f1qWkZKtJSjOYReb9vLQRnLZuF+GdVA7xU=";
})
+4 -4
View File
@@ -6,14 +6,14 @@
let
repoSrc = fetchgit {
url = "https://git.b4l.co.th/newedge/think-greaterchiangmai";
rev = "7c17aa78436538241c09fc7d633904d3c063011e";
hash = "sha256-GDx0+PmuCXC+UPtsvsocCZQiTPcnOZEzJI17sxrVv7Q=";
rev = "895092dea1da99ca1fa3c5c6d805384e579d417e";
hash = "sha256-ffbwQVQ1lL8XpubsGX6FTbJOjz1mbqCn+AKvR7xJhRw=";
};
src = "${repoSrc}/think.greaterchiangmai.com";
in
php.buildComposerProject2 (finalAttrs: {
pname = "think-gtcm";
version = "1.0.0";
version = "1.1.2";
inherit src;
@@ -32,5 +32,5 @@ php.buildComposerProject2 (finalAttrs: {
'';
composerStrictValidation = false;
vendorHash = "sha256-b8+AKUmjQiOdV8UC9GYfJzAHFs9+FRSH91YsxKt0rDA=";
vendorHash = "sha256-rEE1C4uYNoVq5wiCooNnjj6uIQmSel7u9khAlM3hopk=";
})
+1 -9
View File
@@ -9,15 +9,7 @@
devshells.default = {
devshell = {
packages = [
# Local fix until upstream lands (patches/upstream-sd-card-secret-injection.patch):
# `clan machines build --format sd-card` resolves the image via
# image.filePath, skips secret injection for formats without
# clan.core.image.<format>.addFilesScript, and stages the
# injected tree apart from the sandbox output. The sd-card
# addFilesScript itself is modules/nixos/sd-card-image.nix.
(inputs.clan-core.packages.${system}.clan-cli.overrideAttrs (old: {
patches = (old.patches or [ ]) ++ [ ./patches/clan-cli-image-filepath.patch ];
}))
inputs.clan-core.packages.${system}.clan-cli
];
};
};
@@ -0,0 +1 @@
../../../../../sops/groups/admins
@@ -0,0 +1 @@
../../../../../sops/machines/adhil
@@ -0,0 +1,46 @@
{
"data": "ENC[AES256_GCM,data:HT4hSTyFNaZlSVG5wjxFE6xULu+Cf+hmMi+GJ+YWXJrvNw5bYHaEguJ3Sbq7u+BAYLG/uPVpu/H83VXG9IY6jRKWsClXNmzqimZMSIo6KiIcNW6+czCkk1UTUsTGVi8bQRBiIOGMtMp+RYn3sJ0AxlZx3GrtRq4a/XAlvCrHsw7yxYT0Cs6qJxtXbM2iMUbcfr/lfolDrqjNmhkS5TulY4LzKIeDD6VHkCYOlkIW/m+x6wSquEX6RnPGEuvcvBXte6EvUi692qxjNPRrSMu4tRmVUSr55CY63QsNsEzo1cRsU8+jIGIarN9uxFMQaVBGXQAQNnZgFN3IUrs/sgWYIgCdFIk7Xa8WPra14OxO,iv:PxJrjt1dpL6iHALcvYaqkBybcSvCK9qMEZE3MWjixBQ=,tag:mL//WdhB8SVXp/M3Jxb0HQ==,type:str]",
"sops": {
"age": [
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqTWViMC9uVHl4QitxZXV4\nWFFtV253NTBBSnJjazJ0UXd1Z3kzaTYvN3hZCnNyRjZaOU02TU5hbUxSVGdlQmlD\ndVZnUDVFSFMzTGg4Q2FIMG5XZHAvNFEKLS0tIHRUMURnYlhFS3RBaXVXaEVyQ21S\nMElwbTBUOVR1dTV2a2ZHVWV4czQwUWMKgQmIfaVSTRr9yytEgEiTQVp4jCREXLQ7\nlOfBUK02hqN26rkVEVu4YcCttx9/nH7Dwc+wc/nxXXDWYLIcwpdMLA==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age17d4qt0n9edq57tgcqyk8eu5mrendl59yt6z2y3a4vkq7el8krqtq6lq28g"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWYmt1ZElyUjJsclJlZXBY\nWXlXNWVpMUZPZmczaWs2SWFLdzkyT09neEc0CnplRlZ5VS9ZZk5ZODNoNy8yVUtm\ncnQ3RHVtRGVNYk1xSFg1dFRta2pBZTQKLS0tIFBqZ3hwWTJnYkhpZVZUTElIcmto\nc1A1NHdnSFRzRkdGQ3JLYVg2UzF5SzQK4bCCxZRiMSXTkUNOJeuCUcGwz0xoYr7s\nkNVMHSm74UOZG9BowimHiTb9n280ViCaGhZfvKIWkQLRdsYGiUxH5g==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1dytdeqtct0jy3vvmqvapgqjf3v9p486jjcpx60gfxwgrptgyyqrs0lj9vc"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIHZBRlhQWGwv\nQkVLendPcXExRzBXaG9ieDJycWRXMXhOWDN0K2RWSFoyVFkgQVEgd0l6SlBCVW1v\neDl5N0lHYmVrajZzVzVTYjd4WVdNVjZoZVlkUEtmVXlMdyBIdmtoczF0dmhxL1py\nS1hsc0ErckdoVVB0bE02RGdCNW5raERXT1ZJMTkzNjRGNHd2T3ZSTVYrKzZlbEll\nVTZKc2JVN1B2SzRuNkpaekhwSWJQaFZVQQpXUVE4eGJxUTFNYTZSejNoS2tJK1Nw\nUUkzUm44T2l2RjFXTEpaM2w0MkdFCi0tLSA3UElXSlVmZ21nZHUwYUlPU09yZkpi\nc2VSaXo4eVBKOFZ2L3lpVDNxMytZCi5Uz/e8luflh7aUmsDRIo6SIUnvRslFwLzw\nbb1zehkiEHPOdlsccqLNy3jWHT5j26O1/wJgnyUNF+EHlH0SdrU=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqpf43tgcfjm048lsqskvq34w2t4uvrm5qy6m2eg6zjj82ctca8wctgpczxvj0q4y6337uhvsxdh5j86k9h9ymautpvv2759ucwnef75ez7pa7fpkddklp40mxk2tedsp74359g0kefn5rsq0x0yss6cu4yd0h06up0rp08t6yc4l0hfa9y8jn5fkx6nk0hjhz06ykwv0fyxe7z42q683jy0"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIHNMTm44d1Rh\naGZPM29DOSs2THkxeEoxUUZ1WkxyMUFiMmFWUEZXZm1KU3MgQVEgZlFBRFNyWkNS\nRDJQaUJ3WWowTEcwaTk0Z0tiRHpQNmhkMTJZUlZHbHlEMCBtMzE5bzVQb1JmZkpy\nY0orbEs2RzBSVXA3UmZYZkRRanNnc2pHSnJjK1pLVmZOWGR4d0IwZjh1YXlkakRx\nTm5EMXR3ZjNHWGl0Ty8rcHpQbkcxRTZmZwpObkliM0s3aUI4UnI1Y1J0TEtRQjh4\nWDlBd3pjNlZGSklVTHNUYnRFV1RZCi0tLSAwT2N4ejBLUjlOSU03QmlMRmF2aG5v\nYy81VzB6b0UzTCtBbmNSUFM4UWZZCggMWJpWYEWTe4Am4el4VtIwagRb3uu0qWky\nsdh9OJ9uVoRtUKWdLFKfT4o5eWTj9zP0C/mMuSjpelwo2iRcQ1s=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqprw0vfpc8wzsu78quc777kmee54ln6nnsjrnrhl7nr33eh4kvkksqp05qqxj4kgfzrmrugrsvg7skx6ghh3q9xc0x0agthtkvy25d9eq7eklta5wf7s30hexkuyl5546rdz9ffa5tawlp5yweqkgccntw0ny540n2am3cqw3luhxkfmrp63kwr6mwplhr9u26wll48x0n3k5f60c7hg9a3"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIHVJMmhGZC85\nT1NQdXBHNXQydFRoclNQUURRVnJjYmlSR3pXTXhWNTVFUmsgQVEgZUh4S2o1R2RE\nV0svbnErTmxodXE2bUxJK3lla2lvajB3OXVwZHJtcHU5dyBKU3I4WFJnK3BOQXl0\nZDh5NTB5ZFlteFJIM2FVTVp5cmNyRXZqL1V0c0l6L0tSb0c5ZU9jbmRXMTIwb2c1\nQjZsREgzWUxaOXBUd2pQK2NncDZuUmZkZwpNS2FlMEdxNjAyZE1hQVZVVUtPL1Jl\nTE5CTXh6TTNKNlI5Q3BtZ01OdnJrCi0tLSBwVERvMnhEYWdwcjE1WFQ2dldqTW5r\ndTVOdzI4TWsrYUdOQ2laOUk4cUVFCi3++2i2ULNWHy6EbUAIVfQo3be4srgMuzMR\nlUWlzlsHmZksrvdpfdWM72i5Hj7hvgIZAyVroPijEc8PXyORm2s=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqpyewum3q8dfcumfgec8nn958aec9f4q9aqy0k06kw5kq27d6fdqdgp0p7y4ru3n5xk90u747xevxa2af3v37e85j9g3axrmw5hdwdfh0wz22hut5vrafxsx26a7vh8fjwkymz3ramfgvvu4detztu075kmpr8l9ydqda0rnjwatdwmfgswg849p37astvld98s3nleeq575azlwc2hhpuh"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCUHpuc2JjcjFucVpjc0lq\nN1BHclRzV1IzanZrcmdoMFp5UEJ0WGo4MUdZClBOczBCNTZzRitCdmJwZ0RnOE5w\nS2t6aGNhaGJUbXdjYUJNUS83c1FsRGsKLS0tIEEybm9Sc21HOFZLWHNhRml1ZFZF\ndjdiZVo5OElHdVg1L2FnK3NrQzROMXMKHfKVsiisagg77l81ycyfHarJhRaaypOO\nsKbgOWXr33j7D+O9pPXUNlPgofsZTJC10MHUlnTebv74NZUnOIK89g==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1hlzrpqqgndcthq5m5yj9egfgyet2fzrxwa6ynjzwx2r22uy6m3hqr3rd06"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5VVp2eXlSd0l3Y2ZJejBq\nMDhEZGRMOUZxTTBLbG1CUDVZTkR0SzEvdVVzCk5BYk5kVENVNE5yMXNmSXQvaFlu\nbFlHZ0s3cExPOVhUUTFWdm56U21JcmcKLS0tIHlySmJjMzJwN3lFamZkSS9SbnZ6\nWkhRM1ZBNmsxcjI3UDEyYS8wZ3p4TzQKTqU63FMjj3HywYeENZpVdzthhlOAhf7M\naQ33Cb+EQasD9Osb5gF/ZKjDvhLTe0LemV8CVG2eb6DwyLpDgOYcIA==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1sg0rvgyetdcqw7j2x983fh69kdkvqsngpe5x36e5920qa7fze3cqhj4wgx"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCckJ4bjFkUzY4RFRxMHln\nU0NLUWg5YlJ1cXJEbnN1TW93bmNJMjB4emhrCnZCR242ak43V0wwZE5zeHE5dmpv\nSW8zc2lQZWV0QUpxZnJvVFhXK0xFNE0KLS0tIFVxS0pFNFJDRFVIT01lS3ZCQm02\ndllQV3FtYjJHcmJuWVZSR3MzQmVTQzQKfA/QgdtihvvYWU0WBefz+w5PhPjR31BF\nzTcv4Ynd1tQRy4mDWcPVgcyJP4nN8GabfkBAA0+pjI2jcXIl0sWaRA==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1swlyyk2rzvevqawyeekv75nx2dz34zpe3xqhkqme26gcgeavy4dqrfpcd8"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1ak1objdQYVdUeWl6RWNW\nSTVsZlFnblIvRnJNVmRMdTJDeVd2NGlKZWpRCjZ6ZjJQQi81OHY4bTNqUCtiaWV2\nSUwwOEpRNEQ4MWY2cDdwS0Q1OFBUaEEKLS0tIFF4WVlUbDdFNncyNXR6V28xbmpj\nVEkveXIweEcwWi9BdW9rSEZuakN3djgKmiHdiJypk0r+EWWHgSmw7+Yd1f59m3Ul\n6rIcw9j7Cjy/9HUGkfqzZJIVf+TSBpCh4BOY6Wwnyi2h2CvKvaNQRw==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1vphy2sr6uw4ptsua3gh9khrm2cqyt65t46tusmt44z98qa7q6ymq6prrdl"
}
],
"lastmodified": "2026-10-09T07:44:42Z",
"mac": "ENC[AES256_GCM,data:h/fejv7NsOKoVkb1Jz0LejcSof5j6NlIfpTjfirhnmaw3rO2aa1G5/ZRUsNNcEfxqcahzm7Y/ucs+nSoA8uyJa73pE98sctrUPLis2VfBHy8EY4UQq/3MS62VLDY9tCH8VSvA0+/IKtjCsFyeEMS5nzYwME6A2clvh6XQq2cpE4=,iv:oKI4v1SuH/9pUghbGiVP0+bG8adNmFB/groeuXfGe2E=,tag:YhTfSeZ/vSFFRI7h4+5S/g==,type:str]",
"version": "3.13.3"
}
}
@@ -0,0 +1 @@
../../../../../sops/users/kurogeek
@@ -0,0 +1 @@
../../../../../sops/groups/admins
@@ -0,0 +1 @@
../../../../../sops/machines/almach
@@ -0,0 +1,46 @@
{
"data": "ENC[AES256_GCM,data:C0rXbTr/YQ3db7PHAUNQC3lvn8MFlsMnkEMd342f4v9R6e5h0BomePsqhgd2UNjO7P1DWxTwDVhjWsCiVct0JgjwbAEbVTDcJHKEIPLs4+oDj5ZACZSUr4vp17ZkaaZOdIN3Ywy8bnV/pCr9F8xZ6caLCBvNSR+g40xjlpNyJdcUaGdQYo4tmUlnMT84cmpubF2lNUTHSLhYzunKvO49NCWlS1CNalcZjW39whhfTqPiTMp7J7r37yXgjLX2cHdL6mFatrs/Xxwj82hahb5a3QJvgXJ6+UZrurirrGX7Q8rfiGKNUd6IWyyVL+0To/kTPGzkQ4Kz+gW4WMugKO4vwqA+mwL3BbHdLhIakgFI,iv:pS3syLhM0N33YJPJ3MoQHspjV+717qSRgZYllq1Z0is=,tag:6CAaJ4aF5Dv5vQOSr2qpQQ==,type:str]",
"sops": {
"age": [
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1VDdJMXR4RTk4MVVSOTdt\nL1ozb09JRzdvckM3dTdCRFRMamZHNHpBT1FvCm5ucG10bi9rODdOaXBIMlhvV1NK\nVWpUdWgyTzNpRWx1UUZ2bHdjVTRGNDAKLS0tIHFrTVdOQVRDUTM2bk9RSndISlJy\nVWNoaFh0UmErOHFlemtjZ25ETXdyVm8KN6AdmVGo/HPUYmSdJkTrtuMPNluU9X2h\nEfXvT8rjxZTd4SwD3r4AktVIRhXq/0bwsPNTbW2ZpbdJ/CJ/pT5S/g==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age17d4qt0n9edq57tgcqyk8eu5mrendl59yt6z2y3a4vkq7el8krqtq6lq28g"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIEM2SHFKSUNx\nKzBMK0dmZ21iWlFwMWNxcFNzVitJRW50Z3E3OTUrUHJYUzAgQVEgd0l6SlBCVW1v\neDl5N0lHYmVrajZzVzVTYjd4WVdNVjZoZVlkUEtmVXlMdyBIdmtoczF0dmhxL1py\nS1hsc0ErckdoVVB0bE02RGdCNW5raERXT1ZJMTkzNjRGNHd2T3ZSTVYrKzZlbEll\nVTZKc2JVN1B2SzRuNkpaekhwSWJQaFZVQQp0OXRybUdwVHBxWHFWUWtodnZpQkJ5\nK25sNDAybTM2Sk5JOVJTY0NGb1hZCi0tLSBhY1lkTkR1aWVkUGM4bnhZRFIzRy9Q\naSs2SlBvYmVUK3ZZbURKdUtjTk1vCqIroKG03zORXYfC8ySosSUKJFLtOW3YvZxq\njvGOi7gdJ18bBcbq5DxWZMremfYoWeplWegYIt4agXmaGIq8nXE=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqpf43tgcfjm048lsqskvq34w2t4uvrm5qy6m2eg6zjj82ctca8wctgpczxvj0q4y6337uhvsxdh5j86k9h9ymautpvv2759ucwnef75ez7pa7fpkddklp40mxk2tedsp74359g0kefn5rsq0x0yss6cu4yd0h06up0rp08t6yc4l0hfa9y8jn5fkx6nk0hjhz06ykwv0fyxe7z42q683jy0"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIFNjaDhaZFBo\nTUQwb2NIbFo1eVZIcTFqWXN5L0VwZEcrVU02UXVjSHlYVncgQVEgZlFBRFNyWkNS\nRDJQaUJ3WWowTEcwaTk0Z0tiRHpQNmhkMTJZUlZHbHlEMCBtMzE5bzVQb1JmZkpy\nY0orbEs2RzBSVXA3UmZYZkRRanNnc2pHSnJjK1pLVmZOWGR4d0IwZjh1YXlkakRx\nTm5EMXR3ZjNHWGl0Ty8rcHpQbkcxRTZmZwpsTGh6NmljQzNtOXhjZm9Nb2VMTnhW\ncU5KOWNKNE10TmcwMVk4bjlwQzlvCi0tLSBsdVhIREtyZk5qdkg5a2Z3N0RURVR3\nbU9ualB3ems0RUNRMGM0c3dGcmN3Cnqneq/mJFWsJMLrZD2yUGd0gBqliC8OK8iV\nC1JKHR9iZ4vIrdPDhC3S0AoIQ/iIYNUs9F36NoXnWzFYHJnDtQE=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqprw0vfpc8wzsu78quc777kmee54ln6nnsjrnrhl7nr33eh4kvkksqp05qqxj4kgfzrmrugrsvg7skx6ghh3q9xc0x0agthtkvy25d9eq7eklta5wf7s30hexkuyl5546rdz9ffa5tawlp5yweqkgccntw0ny540n2am3cqw3luhxkfmrp63kwr6mwplhr9u26wll48x0n3k5f60c7hg9a3"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIGk3WlY0cFA2\nVy9nZzBpemVOSjAvV0FRVGhEaVJWK0NIdWt1UjYyY0dCaVUgQVEgZUh4S2o1R2RE\nV0svbnErTmxodXE2bUxJK3lla2lvajB3OXVwZHJtcHU5dyBKU3I4WFJnK3BOQXl0\nZDh5NTB5ZFlteFJIM2FVTVp5cmNyRXZqL1V0c0l6L0tSb0c5ZU9jbmRXMTIwb2c1\nQjZsREgzWUxaOXBUd2pQK2NncDZuUmZkZwp6alF6dVZzVHJRQ2VsNkoyYUY1RTUr\nMm9yZ1R4VWZCUUVMTEZpZXZTNXVvCi0tLSA4NW5mN1RFSXkxOXpMb0xzWUVxUlRF\nTkFJVElNM0lySWpOc2dOT2NNelpRCuQeukSZN4c9n90QpSyzZv1pA4yr9svAcdA3\nGAyBu4LV9Fd2cO9j3rN9iLF2qUR2toAhIGlJBnIyGHtVTiH+++o=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqpyewum3q8dfcumfgec8nn958aec9f4q9aqy0k06kw5kq27d6fdqdgp0p7y4ru3n5xk90u747xevxa2af3v37e85j9g3axrmw5hdwdfh0wz22hut5vrafxsx26a7vh8fjwkymz3ramfgvvu4detztu075kmpr8l9ydqda0rnjwatdwmfgswg849p37astvld98s3nleeq575azlwc2hhpuh"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBemdVa0hwTUxHSVFpN21r\ncFBJSzdtcHFOa3YwdC9PQzFkT1FvdnY2UHpvCkZybUZCWHgvMU5SUVlBOTA2ZnBw\nR1VVSU1aL05PR093TlY2TitWelF3em8KLS0tIDVTOFBoYitSallBVGlucTh1TXBC\nV3JnWUVXQkNzWEV2WCsreWhjeFRvOW8KKL+d+uJyGTmUaxvL6QlSbCPPLfcMgqEr\nufojmvBYhLsm7m6a1QUTxtdMuZKN5Wd4VdhIFE3JI5YaUzdTZY2EHA==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1hlzrpqqgndcthq5m5yj9egfgyet2fzrxwa6ynjzwx2r22uy6m3hqr3rd06"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVcUE5VFp1WTR5TTV3SEFr\nUnE1YVpVRjFMQzJsR0phNzF6MWFGeTh3aUZFCmZwOVNYSTN1aVRLQm9PWlhEWnFG\nSSs1TVVicnNWRURHZUU3Zjl6WG11MjAKLS0tIEpVWXRHaXVDYzlIOW1MSk9qajdr\nZlR5Y2pvaXNJTGdQeG5sZDFrU01odDAKEx1zKdU6x6uNbpyxLAGSNYxaaj+Pgj+7\nBYU4PSeN1PPqp7el13lfHscUHUG4i5OWRY4VBWF39DGgp4kSXawyJg==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1m2e4c3gv7mxyg839gf0lfsh8wvygx3rn8u5cwnn99vw2w43ssers40tlq8"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0RTZyOENUUmI1UUJEdVlQ\nS0xEMnd4a1dHZ00zcUp1U3VzK0xvdGJsVEJ3CnFTMXF3Wkd2cWdPVDYxNGdZUVph\nVVRJaitjUzY3VUo0ekw5dG5kL2hRaDAKLS0tIHpWOVRhZy92ZXpTWFlnTGNiSUti\nL3d6dE9TeW9reVBJMnNwMnNrelZxSXcKOPfEEv1c2k6bHZJ+i5v7XKZJxhfkJgg3\nsfgiG0jKKuzERCZTBXNUFzEWH3zw96LAcrLT6iAdZSfrDOYQw5O8yw==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1sg0rvgyetdcqw7j2x983fh69kdkvqsngpe5x36e5920qa7fze3cqhj4wgx"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAySlRBdTRobjBKVTBiTmhD\nZXM5ais0ZUE4QjlQTlA1RU5ObUtna1owS1hjCksrNW9LMzJKUkptUDAxZ1h6ZGFi\naysrelFHZ3pCbUd2d0xhZlZOV3V3V0EKLS0tIE1LVXdMTHlpcWxaTm9WeW9EcEFu\nbi93TVVzdll1aThzbDFPd3dqNnlOZEkKmnmiOvMvdxrxrxORf5Xqu9zZ0zLQIiSI\nLPI2oLaka0XHHe9ntHoINAT7MgZMSO6whELRYlIOjSY4BOTPYUN4eg==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1swlyyk2rzvevqawyeekv75nx2dz34zpe3xqhkqme26gcgeavy4dqrfpcd8"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBINkxtem5NTTV2QUoyYUxZ\nTTNYK0JQS3pQbzcySkFGRmtyMXlnVFRwaDJvCk5PK1VRQ05WTDBUakxYNUtia3Bv\nUUgvSW1PZVhyZWhXVEcvWnZqMkZUOE0KLS0tIFRpNkpzQXJOTit4enB6d2FIc051\nL216WWNESU45U1Ziam1TZVUrUGJ2N2MKpuZHpZHGQZzQlsdFiuw5siIReOGe2zdw\n2uMl25AvnWsWtoydEYzDPSbz9AIifUZy3TpzNZ6MzlL670pfANlEiQ==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1vphy2sr6uw4ptsua3gh9khrm2cqyt65t46tusmt44z98qa7q6ymq6prrdl"
}
],
"lastmodified": "2026-10-09T07:44:42Z",
"mac": "ENC[AES256_GCM,data:EdR/UIwCVTxWBUAAzTBWVPqjA4wZG2YYV5WtsGzvfC+X3noypuKKkqGRhOwYeTvurc9riNByQbXwob5TqLcx8FmnGaaEY3/1SuOom5eJo4kIDdd41ETyqsGyHNwCpY8EpafmnTQjed35t8B9xyQUtasF8m6O7vj7shK/HBKAfwU=,iv:N5/YgfoMLDJyYzxFse842I5mITThzu884yOIZdDFPOc=,tag:IVjldPzHM1nLoMk1wxYiIg==,type:str]",
"version": "3.13.3"
}
}
@@ -0,0 +1 @@
../../../../../sops/users/kurogeek
@@ -0,0 +1 @@
../../../../../sops/groups/admins
@@ -0,0 +1 @@
../../../../../sops/machines/alpheratz
@@ -0,0 +1,46 @@
{
"data": "ENC[AES256_GCM,data:1/BI/yaoHAnKCvXeUMtuFi074H8iKqbhRF0nW4Bxw7mqruHGghvBOWdh8oS4EeC5voruKmJHXUpjunuQIPC4yZn8FVZ6y0SA+No1R2zAGSfIcmEeB9MbN0Bq1xIbK3Meii8ocYfEKtZVTcp5MiRTbGxXWZJkQHQkCDEoZ2FQrJkqRmLpHogZpuB7De/8auj/HniRPSwvNO+qPxUqjdmuVyTc8h41Wsjqbfj3nQKZaMKLHVfOYVStYyTFhxcvAAY4+k2I9rCJM1gpvwJ3ASwYguOWNYGoMO0QAY7HlvAqt/34mw9HoAuj/9dKX+wRvjEcHFPGMhWilUreXc/nFkNhamuQj3jBzFwlOZ5gUrc+,iv:hSPk3gZMEzhO/TTwnh+JJsDLrCuo0ezF1Ou92nByuX0=,tag:6GmCP2Rj6g8Q+1/jXatlGw==,type:str]",
"sops": {
"age": [
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBldkRFcStkREx0OExjWFYy\ndEIzdnRQNnZjdWRHQlVmOTlsVXdnMjl2VVVFClVTSmtocWk0ZjJZWmZkTzBKb1lI\nVnRYeXV2VTI0OFdFZEgxT1BITkVVbVUKLS0tIENZVzVoejVFckkySzFBdVRHQm1u\nWnJnV0VOTWc4dWNKakNwT1NPdnp2b0kKQELeL2FOqprZ0ioGIEXet/v7/xHL5+LJ\nFZu3TNAfTgq4GfnZ81IJpm+0W6emiYQIojtmZjI1J1sgNQk9rlR10w==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age17c9908f08w2w0ajprep4p60a7hd27cznrnxmq4mxwznvgzjuhymsxttd3d"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNT21ibHg1alUyZnVseGJK\nMTRaS2o3b2FwUjhQRHgvL0thTUluV0xzM1NFCjhvTTBOcVZDZ2tMbExTZ0ZYSnhj\nMUZRQ2lWbjZJNHhkcjYrZ1dsUjNTbEEKLS0tIHFBMFFqM2h6Mm9zQitTZk50bDh0\nZzFPUS9jMzArWjV0T3Rja1hYeHJ5SUEK9fWE8AfIiLOktOz/T5iXsgOMbIdzDavP\n3ecNfrWt5Pkwq5U1WZJgB4m8OCxOxmlalcpSoyuQnhnaeKwDtrpcNQ==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age17d4qt0n9edq57tgcqyk8eu5mrendl59yt6z2y3a4vkq7el8krqtq6lq28g"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIEdlc0lRQ1hV\ncjRyQXRZdnhnWlU1SFNQYmdoUWlwaWIxZ0FSdTI3YjZMaG8gQVEgd0l6SlBCVW1v\neDl5N0lHYmVrajZzVzVTYjd4WVdNVjZoZVlkUEtmVXlMdyBIdmtoczF0dmhxL1py\nS1hsc0ErckdoVVB0bE02RGdCNW5raERXT1ZJMTkzNjRGNHd2T3ZSTVYrKzZlbEll\nVTZKc2JVN1B2SzRuNkpaekhwSWJQaFZVQQpVb0gyS0ptS2JmVDlpZ3VPUlNLYkNX\nWkZrR2VydnNYUjVPcnBCTmtVRFZJCi0tLSBQcmsrMHBMZXdvWUg2R1NvbnVCbHFq\ndG5MMTlnTG1wMW1PdE8rWXdKeTVjCnRsPfPMojLJ69iYUls41EMcpyj8/+GF0Jhl\nsooxmxvuwGhOPXuWTewFsEji1yfye6rsgNLA9Cd0RCpvyIJw0P0=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqpf43tgcfjm048lsqskvq34w2t4uvrm5qy6m2eg6zjj82ctca8wctgpczxvj0q4y6337uhvsxdh5j86k9h9ymautpvv2759ucwnef75ez7pa7fpkddklp40mxk2tedsp74359g0kefn5rsq0x0yss6cu4yd0h06up0rp08t6yc4l0hfa9y8jn5fkx6nk0hjhz06ykwv0fyxe7z42q683jy0"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIEVUU1lSaytR\nR3EzbUdrSG5LMndoTjFYNnlIVVgvNU5na3JSekxOQllRSHcgQVEgZlFBRFNyWkNS\nRDJQaUJ3WWowTEcwaTk0Z0tiRHpQNmhkMTJZUlZHbHlEMCBtMzE5bzVQb1JmZkpy\nY0orbEs2RzBSVXA3UmZYZkRRanNnc2pHSnJjK1pLVmZOWGR4d0IwZjh1YXlkakRx\nTm5EMXR3ZjNHWGl0Ty8rcHpQbkcxRTZmZwpxT1lnNGZyZFg2QUxWM000VEoxUEVy\nNjBMcFFoeHVaQThKc3IzRVhuV3pnCi0tLSByckIxTUZVQWtnalByTHBuaXN1NDN6\nZG1PcUhNUTYzL1JBbW9KOVVRZEo0CgQg8XcyqPKDCVKwl0Dl2etfAv92GdKDRYM+\nAWO6BLNlf77kNpdOlUPW4UHkbm/RLm44db6nbJnxhORn5ywql1Y=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqprw0vfpc8wzsu78quc777kmee54ln6nnsjrnrhl7nr33eh4kvkksqp05qqxj4kgfzrmrugrsvg7skx6ghh3q9xc0x0agthtkvy25d9eq7eklta5wf7s30hexkuyl5546rdz9ffa5tawlp5yweqkgccntw0ny540n2am3cqw3luhxkfmrp63kwr6mwplhr9u26wll48x0n3k5f60c7hg9a3"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIGZLTnFMUnJC\nZEFsdVp0aTNxSVJ2OEE0U202ajNzTGFSUTN3bkZTMlZHeGcgQVEgZUh4S2o1R2RE\nV0svbnErTmxodXE2bUxJK3lla2lvajB3OXVwZHJtcHU5dyBKU3I4WFJnK3BOQXl0\nZDh5NTB5ZFlteFJIM2FVTVp5cmNyRXZqL1V0c0l6L0tSb0c5ZU9jbmRXMTIwb2c1\nQjZsREgzWUxaOXBUd2pQK2NncDZuUmZkZwpLZDhXb3pJdVRUd3oxSnJ6dWF0WnJH\nRS9aN1pvZllLM1oyM3pRanczcWF3Ci0tLSA2aEhIQTgyS0N6T1pmNFo0M081bGJu\nUEkraTh4YVBnOE1SN3BacUR6RTVBCunmdr+N4+GF4pggFEnR+8Pjo8sid/O0j+EH\nwA/jT3rreM3G/IIZhiXPtlg9IRQAsops1AWpmRtXpg+m3ra5O7I=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqpyewum3q8dfcumfgec8nn958aec9f4q9aqy0k06kw5kq27d6fdqdgp0p7y4ru3n5xk90u747xevxa2af3v37e85j9g3axrmw5hdwdfh0wz22hut5vrafxsx26a7vh8fjwkymz3ramfgvvu4detztu075kmpr8l9ydqda0rnjwatdwmfgswg849p37astvld98s3nleeq575azlwc2hhpuh"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYN3hvS2FTS1FKaUVLdjk2\nN2E0RDBoZGx2djVTYkxOU2JQNmoyaE9XeHdBCkJmc1pzSU9iOXFTZW53ZFNSSktS\nUDFaQlJjZVhIOEh3TkxEYk9GVU5peHMKLS0tIDd3N2lia1B5WnZJQUhpTytDcENK\nQlQ5eW1rLzFKZndWVmZaYmJqdnRhQzgKqNW6DT439xoXARintD8N+baALXqH+Fg/\nmBw2wxY8vXsdhVHnl1JQ7c5xKhsXZrPnuOokoSq1drjgoZaTIrvo8A==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1hlzrpqqgndcthq5m5yj9egfgyet2fzrxwa6ynjzwx2r22uy6m3hqr3rd06"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5UFhpWmRSTXkxM0RnL2t4\nMWRkVWhQSFFIRU1IK3BRMmpLYVhic1M0Nm5vClZJb2dicGFnVFFZdGlnQlZkNUtp\nMlRLcEk3eHJNZVZOU0ZPbldpbGNxRG8KLS0tIEVCSXA3dldodEl3Z2hHUTg0OEEr\nSEtsMTVxT0t2VG16S0JZcWNSZGZPYlkKGHVe915N+bgdXGjrLiOeTCGFv/tHPdUZ\n2hRQW0aPFwAq9S2ydJ12h47LfhhBPYpntDG2JRaoNHlcx87na39Fnw==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1sg0rvgyetdcqw7j2x983fh69kdkvqsngpe5x36e5920qa7fze3cqhj4wgx"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOL2VWNWV6Tm56eTNIZk1M\nTlVMdWtjb2R4cGM0eHo3S1FLeDFqdFNEYlc4CnY1TmUrSENGdGtCUzJ0OW5oOWxq\nQWU5alZKNC9OOVNZeUJ6N2xnK3RXc28KLS0tIEpaRVQ2Wm1hV0dxUFRFVS82akNy\nL2ZIUm9pTWpaaEU1QlMvMEZMY1pVVlkKhT2ETLPMpHqe3tflE4H9jxH4fesR1TgF\ndHk4i0bCkWRJZCTDYKPmGMkHRvanUIf7u0lP6rm/4qdA36g50fTleQ==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1swlyyk2rzvevqawyeekv75nx2dz34zpe3xqhkqme26gcgeavy4dqrfpcd8"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjWkZOdmQ4T29zQVZnZUlZ\nbklXN0dvZ1BRcTA2dXFINEhIbmZJdU5TWjFjCndXWHFyL2J0Tmt1YnJIdTVCWitF\nZFZ2QUgvWUUxV1UwWXJsQURnbkQ3TkEKLS0tIE5YQ2tzOVVjVDZzRnl0dCt2bGtF\nUXdaajNuUnBxS2pQUzJEbWgxOGlNZ3cKCau1pvPuaIDDDnWvwGTFWU8ozLlCHfFN\ngp2lGxlGU8jI4yjfTT83+W15QXwbQsSWssr3ex7zkRKg0hKabQHzvA==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1vphy2sr6uw4ptsua3gh9khrm2cqyt65t46tusmt44z98qa7q6ymq6prrdl"
}
],
"lastmodified": "2026-10-09T07:44:43Z",
"mac": "ENC[AES256_GCM,data:5gW6xx0tNOh9i6v6+etYYCfWmQ7ISW1BppRUX58Mq+Ulhz0wfYyRTiiHZqiDzUYUl0lCk20VLaGPynAIwIda672bmK5OvGJC/9HuBuJoKRpSpRgJ4uF3pKS4C5DBMKkSXEl3efzCZQ2xrwISNBFQnXPzWBX4Fo5nYZOtMTTwRbw=,iv:/H54jQ7cto6RfAxNYs4CXjcdZ2Oguzn64NhDY+8dRjE=,tag:+p4fQf3cqdoyqDJnpw9vTw==,type:str]",
"version": "3.13.3"
}
}
@@ -0,0 +1 @@
../../../../../sops/users/kurogeek
@@ -0,0 +1 @@
../../../../../sops/groups/admins
@@ -0,0 +1 @@
../../../../../sops/machines/buna
@@ -0,0 +1,46 @@
{
"data": "ENC[AES256_GCM,data:QaeDS07zlZznnp51cjGu8RhinPA0QEQcKv6qcfLh5xtivdttkScAp9tX4D8eepXQ97YsB1JJdPpp6gq+iW8HXyem+tYrkuoVvWKUzydAhvepyRi7cgyBV9YqI5OY3LnMSIpaDrcibKvV8qyQCD92HXeDS35ufE+0AOhy0TeO7AEZ2uXEc3BoJRvQW9lrApWMrfPe+6QltizCUX4xZk8DHdmNu+KsOogiUO8KGx5DCjyGYOugAgMGHbd/ZtkwOWgyGMzSMMRlrOdumpGJacIVH+Clb1EOtpK1URzJy1RgeqqP8cKEvRailR5VXyZviEpUGecUfu1T7LicBkVn9b8+EGHM3lH4A+lahRJm8JYt,iv:Lw1B9JrVC0bs/ZqILYjJlSk6zIyI3VyPvTWSDV9SDmo=,tag:dJnaYhLRdQE4UJ9tbItjGw==,type:str]",
"sops": {
"age": [
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6M3JhMDMxT0FLcVMyN2l3\neU9PTFM2ZnJISHJ6T1NLM0d4a1NRT3NWNHowCkRhZlBmS2xRWnNxSFlEb1BmR3NV\nN0FmMWtsVWp3NzhvQmZSZWYyb1FXTGMKLS0tIENzaGt1VjJKOTN3TFExYlhrK2hn\nUVhGZW96SDFzK3FkaFdVNTArQXVLK1kK/eijw2CfYeUDP0mN6gK3cVlasrKMDf9Q\npJAE7j7qJBY12+KCaH0U7GeU4qe0rQW5gQvZAp/Rjau3OAyBmJtuPw==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age17d4qt0n9edq57tgcqyk8eu5mrendl59yt6z2y3a4vkq7el8krqtq6lq28g"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIEF5UndoV1hm\nUVNNeHFuWjZHRVNzaGlGeXpyalN5KzZURDNvcnRyZHgxU0kgQVEgd0l6SlBCVW1v\neDl5N0lHYmVrajZzVzVTYjd4WVdNVjZoZVlkUEtmVXlMdyBIdmtoczF0dmhxL1py\nS1hsc0ErckdoVVB0bE02RGdCNW5raERXT1ZJMTkzNjRGNHd2T3ZSTVYrKzZlbEll\nVTZKc2JVN1B2SzRuNkpaekhwSWJQaFZVQQpZWmwzME1lNUNzelFGU1FUNFZzSldP\nRmtuU1l0a0x1MUFXbzU3MmVkU2pFCi0tLSBRY1BUQ25qa2ZqcXhYREQ0L1A5VUV3\naWhDaGpNMFU2eUdWSmpJT3dNbWQwCsdznbkReqYz+oeevciXSl8aAJd+dMXIGJMj\nyJj8yIwIo7S/EP1eamg46UYwJdEdZzvKXHCNp+NwHGMUSNMiwew=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqpf43tgcfjm048lsqskvq34w2t4uvrm5qy6m2eg6zjj82ctca8wctgpczxvj0q4y6337uhvsxdh5j86k9h9ymautpvv2759ucwnef75ez7pa7fpkddklp40mxk2tedsp74359g0kefn5rsq0x0yss6cu4yd0h06up0rp08t6yc4l0hfa9y8jn5fkx6nk0hjhz06ykwv0fyxe7z42q683jy0"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIEwvcEp2Z0pP\nL0VHekxtY2NIcVA3ZnFRc3hxTHhvVGQ3WHBzVDRRd0RLa2sgQVEgZlFBRFNyWkNS\nRDJQaUJ3WWowTEcwaTk0Z0tiRHpQNmhkMTJZUlZHbHlEMCBtMzE5bzVQb1JmZkpy\nY0orbEs2RzBSVXA3UmZYZkRRanNnc2pHSnJjK1pLVmZOWGR4d0IwZjh1YXlkakRx\nTm5EMXR3ZjNHWGl0Ty8rcHpQbkcxRTZmZwpyZHdkSTcvNVd6MVQ4eGFsdEw0cGN6\nenNRNHptZU9xN2k2R0I0MmZLdXpJCi0tLSB2clVhcTAzQU1RQ2lBUHhzWUVLRDJn\nY0N5Q2ROd0dVWHVmcjE0YjZUWG5rCgLBnzsYiVYjAOVy3i+s+rJlBFAWQWAgn0/6\nNm+u4QgqGvDSc3USB81KyMIUDbiQB7ppcEooNc39TvsV5fECrX8=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqprw0vfpc8wzsu78quc777kmee54ln6nnsjrnrhl7nr33eh4kvkksqp05qqxj4kgfzrmrugrsvg7skx6ghh3q9xc0x0agthtkvy25d9eq7eklta5wf7s30hexkuyl5546rdz9ffa5tawlp5yweqkgccntw0ny540n2am3cqw3luhxkfmrp63kwr6mwplhr9u26wll48x0n3k5f60c7hg9a3"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIFRmMG8wSkFa\neFd4VG5lYVFxcW9vditscU5SN1Z1QlU4SVlrU0k1VlB2MkEgQVEgZUh4S2o1R2RE\nV0svbnErTmxodXE2bUxJK3lla2lvajB3OXVwZHJtcHU5dyBKU3I4WFJnK3BOQXl0\nZDh5NTB5ZFlteFJIM2FVTVp5cmNyRXZqL1V0c0l6L0tSb0c5ZU9jbmRXMTIwb2c1\nQjZsREgzWUxaOXBUd2pQK2NncDZuUmZkZwp3TVJ0UXFwbnFNQXdFNDd4LzZDcUN0\nRzBmZUE1QllZVmJ2Q0Z3bWNqdGdvCi0tLSB5UG4zUVVuNXkxUkkxWWVmVCtWYTBQ\nazhUQnAwK0lBMWlhRXB5N1dPNEpzCgH+Dmfy9/Yd3DnaRHIA0pFo8wcZgGdQd8N0\n+pz2o1GnVSoC5kq0NxY+sFKEvVbL6wl7V6kIJ3fGclsSOQaXFvw=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqpyewum3q8dfcumfgec8nn958aec9f4q9aqy0k06kw5kq27d6fdqdgp0p7y4ru3n5xk90u747xevxa2af3v37e85j9g3axrmw5hdwdfh0wz22hut5vrafxsx26a7vh8fjwkymz3ramfgvvu4detztu075kmpr8l9ydqda0rnjwatdwmfgswg849p37astvld98s3nleeq575azlwc2hhpuh"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxcG94d2NnVjV1eVY4UFFw\nTmhFZk05cDdvdDhOUnNtK01lZFR0c05Kb21vCjhMT09WR1EwZjRDOXdTaWN6VjJo\nelQzbzBuRzJyYVVKTDE4WVcwcGE0UzQKLS0tIHR3SW85aXFhd29RV0dvSUhtMkZM\nMTF5VjFIRVRKQUY5RmhyTklvOW1rZEUK62GxTlYLJOOjlYJl02uygjSt2yewjoMX\ntY4mw9av6gDRbnVDNkP3lLhDQA5tGE0KpTvcGcTOjkdzPIBb5pygqg==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1hlzrpqqgndcthq5m5yj9egfgyet2fzrxwa6ynjzwx2r22uy6m3hqr3rd06"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnTFU5MFhQMkd0UGhtWEIz\nMEZscktHSTN6aFJVR3grWjRMVk5qMGlaV1JnCm8zRDNLVGdrZExDQXVxODFJQ2pM\neFMwMUZJeWpkTllKL1JXTk9WQjJhSXcKLS0tIDhWUjZEMnhWblM5eTBsTjZVSFMw\nUFh4YmdOZWZmcTAwdEFUVnpqeUlvWDgKpyC3/jxXEiV5A5U1SLkgXuWkRZFFt2fe\nvKhmhBXqyD2PbqAvdmIRBbh3L89oqsgEFr2md9Kvo9piKSCtWVcwMA==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1pgxgvwck2vr3va6c4c3utj7w2rfg2st9qvm76nc92vhfmplqmu4qs928lm"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUaWFEdGd1UmJ2R000dzJI\neUNwSVVYM2JYc0ZleUozS05uM2EvWnpqTGlRClBSQ2VQR3QrektXTWo4YjBuZDJj\nUW44UjNEeSthbWlxblUrY3VjYnVRdW8KLS0tIGZNdnVwRFdnbk1WcTcwSVV2VkZV\nMnRIVmd6ZzhQL2JYemgxTjd6RGFSY0EKGOT6w/7Kbn42afcyU9vWkIWW9vCHDzOi\nft+z61VK2z0P/VCq21dcBVW6q78CEg6v3eJEbhAdMQZvK11hLf1zYw==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1sg0rvgyetdcqw7j2x983fh69kdkvqsngpe5x36e5920qa7fze3cqhj4wgx"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwVzBkbEN1Uno4UE9uSXBF\nNHhMc2ZxbHBnSWNsMkRBQ1F6c3hYOVJOblNJCmFrNnkvRnlFdm0ra1NBejVtS21G\naVl0RzFKL1REanZJUWh0VmtTQjFGNWsKLS0tIFNDekloQ0ZqdHBMUEllZkNCTG1r\nY3MybWQzdjNVQXhuUFp3Q2dqWDIrZEkKopZMeSmWY1JAbQMw92F9UsLr+WP4k/Sf\naFFPr6p5MCfvISFXfi75XZK073ylg9jISSBIoMOr+49Q+QZH9qeP6w==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1swlyyk2rzvevqawyeekv75nx2dz34zpe3xqhkqme26gcgeavy4dqrfpcd8"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwajBZTWI2Q2o0a2lkYTVt\nWHpSZzM0NExRUVlRUVpaR3pMdFhnaUdjM0g4CnFXck1zWmxYSzdOYVFPWUZLc1J0\nMmttU1kreklaQXBZMllWSk5TR2ZVdzgKLS0tIGVMTytxWmJ0WUFOc0xDU3puRTY0\nTlNXakxKenZJa0NUZ0hueXlKOGJIY1EKm0OCBDKnM4gSB6sh5DU98EXv2J+LxXyR\n4V213yMXE0QuTkXCJYeaTKyNT0BhlJ51TF8TUXfpiP7+W/py1ADMdA==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1vphy2sr6uw4ptsua3gh9khrm2cqyt65t46tusmt44z98qa7q6ymq6prrdl"
}
],
"lastmodified": "2026-10-09T07:44:44Z",
"mac": "ENC[AES256_GCM,data:gykkTbBTzcHSl/p67q38V29VJjmlpWLzpcn2/wAtFf3n8S7m+Ma1xCGUbsVnd4mV2OMClXKjdaz5wN8rNhkfvQy8jnWDgdmTJN4Z3LawvmXhuzEOmViv4uIuTLqTnmfrc4BNGTgajvtwGIz9vDVmBvMFv15b7xFUY3wU5xuAdsQ=,iv:XACWsdOFU6rRPfu2eCsKI9Lbt1cqdgPEQLn1vKztOHo=,tag:eBM4wtrmZRiXx7/qgATA4g==,type:str]",
"version": "3.13.3"
}
}
@@ -0,0 +1 @@
../../../../../sops/users/kurogeek
@@ -0,0 +1 @@
../../../../../sops/groups/admins
@@ -0,0 +1 @@
../../../../../sops/machines/mirach
@@ -0,0 +1,46 @@
{
"data": "ENC[AES256_GCM,data:ICq7CQHrcO4kuOQ24kjl036JWx6dnS5YVNqU+eJeHi7QZ0WtI4XTSzVsdZ0slCGTwIrDY4knrPFvC6zoqtXVimQCE7qkvrr9GgNS4JdJgMmq+9tuA3f9nvf2Z7JDfGWU3Ht/gQvHlmu/tN9Wtg/buqpdk93lRXkBi95Fe6799ocFmus3vKfkFIddcq7V1wkfX774OaHLE3/ho+dMuQxUcgSuy29rQwHr7l+DXdJQ5gUXk/z/XAU30SVFOnSH92DjSDMLnrg9hkgPHgaFVNAgP3hG+C6S9WFBNuz2Z14jm/0ylTKjANyqLrX9wRi4SHFc8IDyFW4q8xqZy+Fm1LkYTehG4Re10F9wgh3q8vt4,iv:Bs9WJNERCDOQktKhdxZzAW1k/bzdvUTtyM1NU6/tThc=,tag:CD+oBGHCj+lNdEh9elL9sg==,type:str]",
"sops": {
"age": [
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMdDBSNTZSQTVPVVlGNXI5\ncFpCVWJhSHIwczJKTUdZQTJQTE5RVE5XUERVCjFpdDdJeEVRM3k2YlNPaEk5dzBK\nNldvUUF3eE9kaEg2c0ZvNjl4Lys1dFkKLS0tIFpBam0xRjFkRnZjWEtWUHJZUmth\naEgxME0rUE5Ya25JZC9TdmEzRG9Tb2MKpLndT2smBLsxeKbCy310XMEM94NDUJI/\nLK9pn0IvPKndGETgUuOoPZNOvKNptEmk6DtmiwJx6M2gpNsQRogCQA==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age15k6cz0lj207608gdlgxq6l2n5zsflvg0lw8qqlvkyk0xddc4kqvq9nh6ex"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBadjhaazA2TWJvVFVyUC9D\nSWVRRWFKbU5LRUpqeTBIWk80cFRQU1MvaUMwCmNPR0thUFRiR0R4OCtWRC9vbFdU\ncDZ3SU5iWC9BL2U5bEUvZFRnSWFzUVUKLS0tIEtha3Y5Z1VnRVVpS2EwbGxnNU11\nclhDTkdseWJXenFCaWhFejkvZ1QyaVUK4l+jug4AU3WKCasru29KrqOryUOoxXpL\naoigeE0yTDacBMdh8bMZK08HBzDgBW3q1WLi34xf0rR8FOk08JN3aw==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age17d4qt0n9edq57tgcqyk8eu5mrendl59yt6z2y3a4vkq7el8krqtq6lq28g"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIGxsYTZOWVBz\nRnNMUDF1cDNwL0M0MTRuNlFNR01KaXJEN1FVTDNCeEM1MEUgQVEgd0l6SlBCVW1v\neDl5N0lHYmVrajZzVzVTYjd4WVdNVjZoZVlkUEtmVXlMdyBIdmtoczF0dmhxL1py\nS1hsc0ErckdoVVB0bE02RGdCNW5raERXT1ZJMTkzNjRGNHd2T3ZSTVYrKzZlbEll\nVTZKc2JVN1B2SzRuNkpaekhwSWJQaFZVQQpQSzZicUQ1ZDJ6a0p4YzY4blBtbHRv\naHVBU3R4S0I2dVRhZnFyR0Q1azZnCi0tLSBnQno3V3lQcEJJVjVpRlBGbjNTRzM4\nQ1B5bVc1d0FVYVNyK2tjbDMrWEI0Cg+VtL8ZC39HU76T8ZyQH5k9AN2ouj31P0Id\nhmEfWMGp6ilC0Fm+fMkUoleSO4Us1bc7C+CjwJN88xucNEkcKqE=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqpf43tgcfjm048lsqskvq34w2t4uvrm5qy6m2eg6zjj82ctca8wctgpczxvj0q4y6337uhvsxdh5j86k9h9ymautpvv2759ucwnef75ez7pa7fpkddklp40mxk2tedsp74359g0kefn5rsq0x0yss6cu4yd0h06up0rp08t6yc4l0hfa9y8jn5fkx6nk0hjhz06ykwv0fyxe7z42q683jy0"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIFpZTjJmWnN2\nVnI3UHArVXhkeWx0UkdLM1h4ZkdSdHdyRU8wZmVYNlE3M2MgQVEgZlFBRFNyWkNS\nRDJQaUJ3WWowTEcwaTk0Z0tiRHpQNmhkMTJZUlZHbHlEMCBtMzE5bzVQb1JmZkpy\nY0orbEs2RzBSVXA3UmZYZkRRanNnc2pHSnJjK1pLVmZOWGR4d0IwZjh1YXlkakRx\nTm5EMXR3ZjNHWGl0Ty8rcHpQbkcxRTZmZworcmgrbnM2bXVzdmRETzV2Q2ZxT1l3\ndmF4TkJIdjNMS0FDY3lJZEJ3a1NRCi0tLSBpWXJOQjhGUW5jRzhrdzQ0eEVNcTJX\nVGJzVXNxYUg2ZWdjbjRRWm5ibUNJCvzc5NMCGROVcGH6uLBL1ArMuSku0J/8cy4e\nXrriQktXy2Exw14jBC7w6kirE8zqRm8rQK5HNHDCudSNwSG1lM4=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqprw0vfpc8wzsu78quc777kmee54ln6nnsjrnrhl7nr33eh4kvkksqp05qqxj4kgfzrmrugrsvg7skx6ghh3q9xc0x0agthtkvy25d9eq7eklta5wf7s30hexkuyl5546rdz9ffa5tawlp5yweqkgccntw0ny540n2am3cqw3luhxkfmrp63kwr6mwplhr9u26wll48x0n3k5f60c7hg9a3"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IGZpZG8yLWhtYWMgQUFJIEZuRWpNaW1X\nRXYxUlVVY255bUtLUmdwclR0SENGZnFGbDAzWEp5anJCRTAgQVEgZUh4S2o1R2RE\nV0svbnErTmxodXE2bUxJK3lla2lvajB3OXVwZHJtcHU5dyBKU3I4WFJnK3BOQXl0\nZDh5NTB5ZFlteFJIM2FVTVp5cmNyRXZqL1V0c0l6L0tSb0c5ZU9jbmRXMTIwb2c1\nQjZsREgzWUxaOXBUd2pQK2NncDZuUmZkZwpqMXFNT2tRa2cxT1ZOUU1EVXFzb2ta\nM0NOS3lRTVdGY2kvL0VzTE1Oa2lzCi0tLSBXQ1d5L2dZV0RtRDd0b1pGdXYrZklY\ncmFPa3owZ05zZEo2UjZ0YlNZc0ZRCiQKbOn4DMdVy25AjJ7J+bqLmt3cPTeErtYu\nhSUSZ9TW50bFEopZHLIMHmqEnfp4ckInGva9BD3rUmdwdX5OpEg=\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1fido2-hmac1qqpyewum3q8dfcumfgec8nn958aec9f4q9aqy0k06kw5kq27d6fdqdgp0p7y4ru3n5xk90u747xevxa2af3v37e85j9g3axrmw5hdwdfh0wz22hut5vrafxsx26a7vh8fjwkymz3ramfgvvu4detztu075kmpr8l9ydqda0rnjwatdwmfgswg849p37astvld98s3nleeq575azlwc2hhpuh"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBud282MTN6UVpyM3V6OUE0\nV3cxWUU1UThibU83MFpiWGFiVWsxMmYvbkM4CmtoNFBWbWNGaVNkVnJEbUp1elZR\nTk9GL2NWVEw1OUIyaUR4WUh5aUh0YVEKLS0tIFF5b0I3UUkxNDN1c3NETzVPNjhz\nZlFnYm1URmE2U0NKUnJIcmwvWlk1cDgKyFm8sfGceuW3c1LQHJazv15Ghyz+af75\n3XxENPd8xXoRIa7i34xIFgXvU7hxqX517NtIXgJwHHboWfUueXPQUA==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1hlzrpqqgndcthq5m5yj9egfgyet2fzrxwa6ynjzwx2r22uy6m3hqr3rd06"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAySUxYUnY5MDdsMzZwNFVv\nYWhxSU16S2hLcHpPM2dWWTB2ZnM0OXhLMjA0ClFnR1hyNWduM0hRWFhxVld4NWVS\nbnlDMlRPbVNBeUlFbUJka0JkMjQ3NVkKLS0tIE5kZDZGUWFGQXJoc0JRYUgwRjBx\nc2w1RWNGQXc1Smw5TnVGZXlBblcvZXcKDoGAcdVZ8andZq5pNGGfDQfyQ9LW2XOJ\n2mJhLWiYWKei6xEG7eJ+Yl+De4R+EtNN+alp2gJLCjsoiTVd87PVqA==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1sg0rvgyetdcqw7j2x983fh69kdkvqsngpe5x36e5920qa7fze3cqhj4wgx"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFdGRpMVp3N3E5K25VS2Zm\nbTFpQWtMcUN4bytIdDNRT3B0RTNDMzNlRlJNCitvek4vVmlmWFlOc1k4eE91QmJ0\nT3NIMmhyWkh5UGtaTmVqQjBibERqR2cKLS0tIHpwNFlGVzNvQkRxUnAwNlM5NS9B\nb2laYkhrTlBVQ3hOK3RaT0tJaFE4cEUKx3rB2z3XVbXv9PkQtAA7I5QneNi8Jjrp\nBApe2+PFEPKjLq75QYCYZRH6ah08CYx77dD0O561bNL6J6oZCD1YRw==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1swlyyk2rzvevqawyeekv75nx2dz34zpe3xqhkqme26gcgeavy4dqrfpcd8"
},
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBVGV0M09LU3ZZRkV5cVFz\nbTlxbC9hTlNKemJ0LzBDc1U2bnI4Smp1U2tnCmNORzd2SWQyZFFkS1ZDRlJHa2d4\nWGxIaHBlbEZMZ2R0NWdrbHhWMzZHUmMKLS0tIFJ5dnNOUG96TFZybDF1TGdoQ2dC\nbFo1WTVYNlVVeFo4ZndXSytncy95SkkKSGv8EJsVnDCzFpsnzB2bkMds8bW82OUw\nz6uBFRR7T9JbfSHKLYqQRJVSNlu3jleKK+wak+HfGaUPPU4n2K0Qwg==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1vphy2sr6uw4ptsua3gh9khrm2cqyt65t46tusmt44z98qa7q6ymq6prrdl"
}
],
"lastmodified": "2026-10-09T07:44:44Z",
"mac": "ENC[AES256_GCM,data:eK0kYfRDJVVKQ1FK2nTfUl7/Pm3dRRwjvhih1LMncBw6xlF8xLy+/CckucqDFgf+5PSl8Mti1G0eKo4/WoISEtDjYmmuSHM++KsVpKtmk6g7ooco8maUYyh8t7siqYzhBvqVt3ftlrA4kJ5f+BQm41QTGDqk33E7JaNglGRgh+U=,iv:BzwqnsQL6vBVzbJel2TOUxTo7QiLq2ujBskrQ1LIO5I=,tag:pWwtZlcht5eSTjz/CrtM3w==,type:str]",
"version": "3.13.3"
}
}
@@ -0,0 +1 @@
../../../../../sops/users/kurogeek
@@ -0,0 +1 @@
fd6b:1ec3:3bf0:8d8e:ce99:936f:daf0:109b
@@ -0,0 +1 @@
fd6b:1ec3:3bf0:8d8e:ce99:9313:20d:e30e
@@ -0,0 +1 @@
fd6b:1ec3:3bf0:8d8e:ce99:936a:93d7:d67e
@@ -0,0 +1 @@
fd6b:1ec3:3bf0:8d8e:ce99:9375:cdf4:e6de