Feature/building portal vm on vultr #59
@@ -1,6 +1,9 @@
|
|||||||
# Generate AGE keys from SSH keys with:
|
# Generate AGE keys from SSH keys with:
|
||||||
# ssh-keygen -A
|
# ssh-keygen -A
|
||||||
# nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
|
# nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
|
||||||
|
# update keys after addin/removing a key
|
||||||
|
# nix-shell -p sops --run "sops updatekeys users/secrets.yaml" users/secrets.yaml
|
||||||
|
|
||||||
keys:
|
keys:
|
||||||
- &admin_richie age1u8zj599elqqvcmhxn8zuwrufsz8w8w366d3ayrljjejljt2q45kq8mxw9c # cspell:disable-line
|
- &admin_richie age1u8zj599elqqvcmhxn8zuwrufsz8w8w366d3ayrljjejljt2q45kq8mxw9c # cspell:disable-line
|
||||||
|
|
||||||
@@ -8,6 +11,7 @@ keys:
|
|||||||
- &system_brain age1jhf7vm0005j60mjq63696frrmjhpy8kpc2d66mw044lqap5mjv4snmwvwm # cspell:disable-line
|
- &system_brain age1jhf7vm0005j60mjq63696frrmjhpy8kpc2d66mw044lqap5mjv4snmwvwm # cspell:disable-line
|
||||||
- &system_jeeves age13lmqgc3jvkyah5e3vcwmj4s5wsc2akctcga0lpc0x8v8du3fxprqp4ldkv # cspell:disable-line
|
- &system_jeeves age13lmqgc3jvkyah5e3vcwmj4s5wsc2akctcga0lpc0x8v8du3fxprqp4ldkv # cspell:disable-line
|
||||||
- &system_rhapsody age1ufnewppysaq2wwcl4ugngjz8pfzc5a35yg7luq0qmuqvctajcycs5lf6k4 # cspell:disable-line
|
- &system_rhapsody age1ufnewppysaq2wwcl4ugngjz8pfzc5a35yg7luq0qmuqvctajcycs5lf6k4 # cspell:disable-line
|
||||||
|
- &system_portal_1 age1vyav6kxtvt3z4vtnkkjj38eu8hlts5m7ygyckhskvalg2gpjk52su53d0a # cspell:disable-line
|
||||||
|
|
||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: users/secrets\.yaml$
|
- path_regex: users/secrets\.yaml$
|
||||||
@@ -18,3 +22,4 @@ creation_rules:
|
|||||||
- *system_brain
|
- *system_brain
|
||||||
- *system_jeeves
|
- *system_jeeves
|
||||||
- *system_rhapsody
|
- *system_rhapsody
|
||||||
|
- *system_portal_1
|
||||||
|
|||||||
@@ -17,16 +17,11 @@
|
|||||||
./nix.nix
|
./nix.nix
|
||||||
./programs.nix
|
./programs.nix
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./snapshot_manager.nix
|
|
||||||
];
|
];
|
||||||
|
|
||||||
boot = {
|
boot = {
|
||||||
tmp.useTmpfs = true;
|
tmp.useTmpfs = lib.mkDefault true;
|
||||||
kernelPackages = lib.mkDefault pkgs.linuxPackages_6_12;
|
kernelPackages = lib.mkDefault pkgs.linuxPackages_6_12;
|
||||||
zfs = {
|
|
||||||
package = lib.mkDefault pkgs.zfs_2_4;
|
|
||||||
forceImportRoot = lib.mkDefault false;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
hardware.enableRedistributableFirmware = true;
|
hardware.enableRedistributableFirmware = true;
|
||||||
@@ -50,16 +45,6 @@
|
|||||||
|
|
||||||
# firmware update
|
# firmware update
|
||||||
fwupd.enable = true;
|
fwupd.enable = true;
|
||||||
|
|
||||||
snapshot_manager = {
|
|
||||||
enable = lib.mkDefault true;
|
|
||||||
PYTHONPATH = "${inputs.self}/";
|
|
||||||
};
|
|
||||||
|
|
||||||
zfs = {
|
|
||||||
trim.enable = lib.mkDefault true;
|
|
||||||
autoScrub.enable = lib.mkDefault true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
powerManagement.powertop.enable = lib.mkDefault true;
|
powerManagement.powertop.enable = lib.mkDefault true;
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
nix.settings = {
|
||||||
|
trusted-substituters = [ "http://jeeves:5000" ];
|
||||||
|
substituters = [ "http://jeeves:5000/?priority=1&want-mass-query=true" ];
|
||||||
|
trusted-public-keys = [ "cache.tmmworkshop.com:jHffkpgbmEdstQPoihJPYW9TQe6jnQbWR2LqkNGV3iA=" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.tailscale = {
|
||||||
|
enable = true;
|
||||||
|
openFirewall = true;
|
||||||
|
authKeyFile = config.sops.secrets.tailscale_auth_key.path;
|
||||||
|
|
||||||
|
# OAuth client secrets create ephemeral nodes by default. NixOS machines
|
||||||
|
# are persistent and should enroll without interactive device approval.
|
||||||
|
authKeyParameters = {
|
||||||
|
ephemeral = false;
|
||||||
|
preauthorized = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
extraUpFlags = [ "--advertise-tags=tag:nixos" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
sops = {
|
||||||
|
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
|
|
||||||
|
secrets.tailscale_auth_key = {
|
||||||
|
sopsFile = "${inputs.self}/users/secrets.yaml";
|
||||||
|
owner = "root";
|
||||||
|
mode = "0400";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
imports = [ ./snapshot.nix ];
|
||||||
|
|
||||||
|
boot.zfs = {
|
||||||
|
package = lib.mkDefault pkgs.zfs_2_4;
|
||||||
|
forceImportRoot = lib.mkDefault false;
|
||||||
|
};
|
||||||
|
|
||||||
|
services = {
|
||||||
|
snapshot_manager = {
|
||||||
|
enable = lib.mkDefault true;
|
||||||
|
PYTHONPATH = "${inputs.self}/";
|
||||||
|
};
|
||||||
|
|
||||||
|
zfs = {
|
||||||
|
trim.enable = lib.mkDefault true;
|
||||||
|
autoScrub.enable = lib.mkDefault true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Generated
+21
@@ -1,5 +1,25 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"disko": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1781152676,
|
||||||
|
"narHash": "sha256-RxWs5ND31KzTG7wvMM+PMfUjyNpmIEr999lqNARaM5o=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "disko",
|
||||||
|
"rev": "ff8702b4de27f72b4c78573dfb89ec74e36abdf1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "disko",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"firefox-addons": {
|
"firefox-addons": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -124,6 +144,7 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"disko": "disko",
|
||||||
"firefox-addons": "firefox-addons",
|
"firefox-addons": "firefox-addons",
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"nixos-hardware": "nixos-hardware",
|
"nixos-hardware": "nixos-hardware",
|
||||||
|
|||||||
@@ -35,6 +35,11 @@
|
|||||||
url = "github:Mic92/sops-nix";
|
url = "github:Mic92/sops-nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
disko = {
|
||||||
|
url = "github:nix-community/disko";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
|
|||||||
@@ -6,15 +6,17 @@
|
|||||||
"${inputs.self}/users/steve"
|
"${inputs.self}/users/steve"
|
||||||
"${inputs.self}/common/global"
|
"${inputs.self}/common/global"
|
||||||
"${inputs.self}/common/optional/docker.nix"
|
"${inputs.self}/common/optional/docker.nix"
|
||||||
"${inputs.self}/common/optional/scanner.nix"
|
|
||||||
"${inputs.self}/common/optional/monitoring-agent.nix"
|
"${inputs.self}/common/optional/monitoring-agent.nix"
|
||||||
|
"${inputs.self}/common/optional/nvidia.nix"
|
||||||
|
"${inputs.self}/common/optional/scanner.nix"
|
||||||
"${inputs.self}/common/optional/steam.nix"
|
"${inputs.self}/common/optional/steam.nix"
|
||||||
"${inputs.self}/common/optional/syncthing_base.nix"
|
"${inputs.self}/common/optional/syncthing_base.nix"
|
||||||
"${inputs.self}/common/optional/systemd-boot.nix"
|
"${inputs.self}/common/optional/systemd-boot.nix"
|
||||||
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/update.nix"
|
"${inputs.self}/common/optional/update.nix"
|
||||||
"${inputs.self}/common/optional/yubikey.nix"
|
"${inputs.self}/common/optional/yubikey.nix"
|
||||||
"${inputs.self}/common/optional/zerotier.nix"
|
"${inputs.self}/common/optional/zerotier.nix"
|
||||||
"${inputs.self}/common/optional/nvidia.nix"
|
"${inputs.self}/common/optional/zfs"
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
./syncthing.nix
|
./syncthing.nix
|
||||||
./llms.nix
|
./llms.nix
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
"${inputs.self}/common/optional/ssh_decrypt.nix"
|
"${inputs.self}/common/optional/ssh_decrypt.nix"
|
||||||
"${inputs.self}/common/optional/syncthing_base.nix"
|
"${inputs.self}/common/optional/syncthing_base.nix"
|
||||||
"${inputs.self}/common/optional/systemd-boot.nix"
|
"${inputs.self}/common/optional/systemd-boot.nix"
|
||||||
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/update.nix"
|
"${inputs.self}/common/optional/update.nix"
|
||||||
"${inputs.self}/common/optional/zerotier.nix"
|
"${inputs.self}/common/optional/zerotier.nix"
|
||||||
|
"${inputs.self}/common/optional/zfs"
|
||||||
./docker
|
./docker
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
./programs.nix
|
./programs.nix
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ in
|
|||||||
"${inputs.self}/common/optional/monitoring-agent.nix"
|
"${inputs.self}/common/optional/monitoring-agent.nix"
|
||||||
"${inputs.self}/common/optional/ssh_decrypt.nix"
|
"${inputs.self}/common/optional/ssh_decrypt.nix"
|
||||||
"${inputs.self}/common/optional/syncthing_base.nix"
|
"${inputs.self}/common/optional/syncthing_base.nix"
|
||||||
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/update.nix"
|
"${inputs.self}/common/optional/update.nix"
|
||||||
"${inputs.self}/common/optional/zerotier.nix"
|
"${inputs.self}/common/optional/zerotier.nix"
|
||||||
|
"${inputs.self}/common/optional/zfs"
|
||||||
./monitoring
|
./monitoring
|
||||||
./docker
|
./docker
|
||||||
./services
|
./services
|
||||||
./web_services
|
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
./networking.nix
|
./networking.nix
|
||||||
./programs.nix
|
./programs.nix
|
||||||
|
|||||||
@@ -17,8 +17,17 @@
|
|||||||
allowedTCPPorts = [ ];
|
allowedTCPPorts = [ ];
|
||||||
allowedUDPPorts = [ ];
|
allowedUDPPorts = [ ];
|
||||||
};
|
};
|
||||||
|
interfaces.tailscale0.allowedTCPPorts = [
|
||||||
|
5000 # Nix binary cache
|
||||||
|
8000 # Audiobookshelf
|
||||||
|
8001 # Norn Sight
|
||||||
|
8002 # Gems
|
||||||
|
8091 # Share
|
||||||
|
8096 # Jellyfin HTTP
|
||||||
|
];
|
||||||
allowedTCPPorts = [
|
allowedTCPPorts = [
|
||||||
8070
|
6443 # Gitea HTTP
|
||||||
|
8070 # ebook searcher
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
useNetworkd = true;
|
useNetworkd = true;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ in
|
|||||||
services.audiobookshelf = {
|
services.audiobookshelf = {
|
||||||
enable = true;
|
enable = true;
|
||||||
port = 8000;
|
port = 8000;
|
||||||
|
host = "0.0.0.0";
|
||||||
};
|
};
|
||||||
systemd.services.audiobookshelf.serviceConfig.WorkingDirectory =
|
systemd.services.audiobookshelf.serviceConfig.WorkingDirectory =
|
||||||
lib.mkForce "${vars.docker_configs}/audiobookshelf";
|
lib.mkForce "${vars.docker_configs}/audiobookshelf";
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ in
|
|||||||
Type = "simple";
|
Type = "simple";
|
||||||
User = "gems";
|
User = "gems";
|
||||||
Group = "gems";
|
Group = "gems";
|
||||||
ExecStart = "${pkgs.my_python}/bin/python -m python.gems.main --host 127.0.0.1 --port 8002";
|
ExecStart = "${pkgs.my_python}/bin/python -m python.gems.main --host 0.0.0.0 --port 8002";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = "5s";
|
RestartSec = "5s";
|
||||||
StandardOutput = "journal";
|
StandardOutput = "journal";
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.firewall.allowedTCPPorts = [
|
networking.firewall.allowedTCPPorts = [
|
||||||
6443
|
|
||||||
2223
|
2223
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -27,10 +26,14 @@ in
|
|||||||
};
|
};
|
||||||
service.DISABLE_REGISTRATION = true;
|
service.DISABLE_REGISTRATION = true;
|
||||||
server = {
|
server = {
|
||||||
DOMAIN = "tmmworkshop.com";
|
DOMAIN = "gitea.tmmworkshop.com";
|
||||||
ROOT_URL = "https://gitea.tmmworkshop.com/";
|
ROOT_URL = "https://gitea.tmmworkshop.com/";
|
||||||
HTTP_PORT = 6443;
|
HTTP_PORT = 6443;
|
||||||
|
BUILTIN_SSH_SERVER_USER = "gitea";
|
||||||
|
SSH_USER = "gitea";
|
||||||
|
SSH_DOMAIN = "gitea.tmmworkshop.com";
|
||||||
SSH_PORT = 2223;
|
SSH_PORT = 2223;
|
||||||
|
SSH_LISTEN_HOST = "0.0.0.0";
|
||||||
SSH_LISTEN_PORT = 2223;
|
SSH_LISTEN_PORT = 2223;
|
||||||
START_SSH_SERVER = true;
|
START_SSH_SERVER = true;
|
||||||
PUBLIC_URL_DETECTION = "auto";
|
PUBLIC_URL_DETECTION = "auto";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ in
|
|||||||
{
|
{
|
||||||
services.jellyfin = {
|
services.jellyfin = {
|
||||||
enable = true;
|
enable = true;
|
||||||
openFirewall = true;
|
openFirewall = false;
|
||||||
dataDir = "${vars.services}/jellyfin";
|
dataDir = "${vars.services}/jellyfin";
|
||||||
cacheDir = "${vars.services}/jellyfin/cache";
|
cacheDir = "${vars.services}/jellyfin/cache";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ in
|
|||||||
package = pkgs.nix-serve-ng;
|
package = pkgs.nix-serve-ng;
|
||||||
enable = true;
|
enable = true;
|
||||||
secretKeyFile = "${vars.secrets}/services/nix-cache/cache-priv-key.pem";
|
secretKeyFile = "${vars.secrets}/services/nix-cache/cache-priv-key.pem";
|
||||||
openFirewall = true;
|
openFirewall = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
zpool = ["root_pool", "storage", "media"]
|
zpool = ["root_pool", "storage", "media"]
|
||||||
services = [
|
services = [
|
||||||
"audiobookshelf",
|
"audiobookshelf",
|
||||||
"haproxy",
|
|
||||||
"docker",
|
"docker",
|
||||||
"jellyfin",
|
"jellyfin",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
let
|
|
||||||
domains = [
|
|
||||||
"audiobookshelf"
|
|
||||||
"cache"
|
|
||||||
"gitea"
|
|
||||||
"gems"
|
|
||||||
"jellyfin"
|
|
||||||
"share"
|
|
||||||
];
|
|
||||||
extraDomains = [ "www.norn-sight.com" ];
|
|
||||||
|
|
||||||
makeCert = name: {
|
|
||||||
name = "${name}.tmmworkshop.com";
|
|
||||||
value = {
|
|
||||||
webroot = "/var/lib/acme/.challenges";
|
|
||||||
group = "acme";
|
|
||||||
reloadServices = [ "haproxy.service" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
makeExtraCert = name: {
|
|
||||||
inherit name;
|
|
||||||
value = {
|
|
||||||
webroot = "/var/lib/acme/.challenges";
|
|
||||||
group = "acme";
|
|
||||||
reloadServices = [ "haproxy.service" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
acmeServices =
|
|
||||||
map (domain: "acme-${domain}.tmmworkshop.com.service") domains
|
|
||||||
++ map (domain: "acme-${domain}.service") extraDomains;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
users.users.haproxy.extraGroups = [ "acme" ];
|
|
||||||
|
|
||||||
security.acme = {
|
|
||||||
acceptTerms = true;
|
|
||||||
defaults.email = "Richie@tmmworkshop.com";
|
|
||||||
certs = builtins.listToAttrs ((map makeCert domains) ++ (map makeExtraCert extraDomains));
|
|
||||||
};
|
|
||||||
|
|
||||||
# Minimal nginx to serve ACME HTTP-01 challenge files for HAProxy
|
|
||||||
services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
virtualHosts."acme-challenge" = {
|
|
||||||
listen = [
|
|
||||||
{
|
|
||||||
addr = "127.0.0.1";
|
|
||||||
port = 8402;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
locations."/.well-known/acme-challenge/" = {
|
|
||||||
root = "/var/lib/acme/.challenges";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Ensure the challenge directory exists with correct permissions
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d /var/lib/acme/.challenges 0750 acme acme - -"
|
|
||||||
"d /var/lib/acme/.challenges/.well-known 0750 acme acme - -"
|
|
||||||
"d /var/lib/acme/.challenges/.well-known/acme-challenge 0750 acme acme - -"
|
|
||||||
];
|
|
||||||
|
|
||||||
users.users.nginx.extraGroups = [ "acme" ];
|
|
||||||
|
|
||||||
# HAProxy needs certs to exist before it can bind :443.
|
|
||||||
# NixOS's acme module generates self-signed placeholders on first boot
|
|
||||||
# via acme-<domain>.service — just make HAProxy wait for them.
|
|
||||||
systemd.services.haproxy = {
|
|
||||||
after = acmeServices;
|
|
||||||
wants = acmeServices;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
imports =
|
|
||||||
let
|
|
||||||
files = builtins.attrNames (builtins.readDir ./.);
|
|
||||||
nixFiles = builtins.filter (name: lib.hasSuffix ".nix" name && name != "default.nix") files;
|
|
||||||
in
|
|
||||||
map (file: ./. + "/${file}") nixFiles;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
|
|
||||||
# Global robots.txt served by HAProxy for every vhost (see haproxy.cfg).
|
|
||||||
environment.etc."haproxy/robots.txt".source = ./robots.txt;
|
|
||||||
|
|
||||||
services.haproxy = {
|
|
||||||
enable = true;
|
|
||||||
config = builtins.readFile ./haproxy.cfg;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# portal_1
|
||||||
|
|
||||||
|
Minimal NixOS target for a Vultr VM, installed with nixos-anywhere. The Nix
|
||||||
|
flake target is `portal_1`; the machine hostname is `portal-1` because DNS
|
||||||
|
hostnames cannot contain underscores.
|
||||||
|
|
||||||
|
## Before deploying
|
||||||
|
|
||||||
|
1. Confirm the VM's system disk is `/dev/vda`. If it is not, update both
|
||||||
|
references in `disk-config.nix`.
|
||||||
|
2. Confirm the SSH public key in `default.nix` is the key that should have
|
||||||
|
administrator access.
|
||||||
|
3. Boot the VM into a NixOS installer or another nixos-anywhere-compatible
|
||||||
|
Linux rescue environment with root SSH access. Keep this environment
|
||||||
|
running while completing the SOPS bootstrap below.
|
||||||
|
|
||||||
|
## Bootstrap SOPS
|
||||||
|
|
||||||
|
Use the rescue environment's SSH host key as the permanent portal identity.
|
||||||
|
Replace `VM_IP` below:
|
||||||
|
|
||||||
|
```console
|
||||||
|
ssh root@VM_IP 'cat /etc/ssh/ssh_host_ed25519_key.pub' | \
|
||||||
|
nix shell nixpkgs#ssh-to-age --command ssh-to-age
|
||||||
|
```
|
||||||
|
|
||||||
|
This prints an `age1...` recipient; it does not copy the private key. Add the
|
||||||
|
recipient to `.sops.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- &system_portal_1 age1...
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add `*system_portal_1` to the age recipients for
|
||||||
|
`users/secrets.yaml`. Re-encrypt the existing file for the new recipient and
|
||||||
|
add the Tailscale key:
|
||||||
|
|
||||||
|
```console
|
||||||
|
nix shell nixpkgs#sops --command sops updatekeys users/secrets.yaml
|
||||||
|
nix shell nixpkgs#sops --command sops users/secrets.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the OAuth client secret from the `Auth Keys: Write` credential in the SOPS
|
||||||
|
editor and save it:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tailscale_auth_key: tskey-client-...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
From the repository root, replace `VM_IP` with the VM's public IP:
|
||||||
|
|
||||||
|
```console
|
||||||
|
nix run github:nix-community/nixos-anywhere -- \
|
||||||
|
--copy-host-keys --flake .#portal_1 root@VM_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
This repartitions `/dev/vda`, so anything already on that disk is erased. The
|
||||||
|
layout reserves 8 GiB for swap and assigns the remaining space to the root
|
||||||
|
filesystem.
|
||||||
|
|
||||||
|
`--copy-host-keys` preserves the same private SSH host key at
|
||||||
|
`/etc/ssh/ssh_host_ed25519_key` on the installed system. SOPS-Nix converts that
|
||||||
|
key to an age identity during activation. After the reboot, connect as
|
||||||
|
`richie` and verify that automatic Tailscale enrollment succeeded:
|
||||||
|
|
||||||
|
```console
|
||||||
|
ssh -p 278 richie@VM_IP
|
||||||
|
sudo tailscale status
|
||||||
|
```
|
||||||
|
|
||||||
|
The installed OpenSSH service listens on port 278. Port 22 is served by
|
||||||
|
Endlessh and will not provide an SSH login.
|
||||||
|
|
||||||
|
HAProxy uses the same frontend, routing, and rate-limiting configuration as
|
||||||
|
Jeeves. Portal manages the ACME certificates for the existing public domains;
|
||||||
|
their DNS records must resolve to Portal for HTTP-01 issuance and renewal.
|
||||||
|
|
||||||
|
The application backends still use Jeeves' original `127.0.0.1` addresses.
|
||||||
|
Replace them with the corresponding Tailscale addresses before directing
|
||||||
|
application traffic through Portal. Ports 80 and 443 are allowed through the
|
||||||
|
firewall.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
inputs.disko.nixosModules.disko
|
||||||
|
"${inputs.self}/users/richie"
|
||||||
|
"${inputs.self}/common/global"
|
||||||
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
|
./disk-config.nix
|
||||||
|
./haproxy
|
||||||
|
];
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = "x86_64-linux";
|
||||||
|
|
||||||
|
boot = {
|
||||||
|
# Avoid consuming the VM's limited memory for /tmp.
|
||||||
|
tmp.useTmpfs = false;
|
||||||
|
|
||||||
|
# The Vultr system disk and NIC are exposed as virtio devices.
|
||||||
|
initrd.availableKernelModules = [
|
||||||
|
"virtio_pci"
|
||||||
|
"virtio_blk"
|
||||||
|
"virtio_scsi"
|
||||||
|
"sd_mod"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
networking = {
|
||||||
|
hostName = "portal-1";
|
||||||
|
useDHCP = lib.mkDefault true;
|
||||||
|
|
||||||
|
firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowedTCPPorts = [ 278 ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.openssh.ports = [ 278 ];
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
curl
|
||||||
|
htop
|
||||||
|
vim
|
||||||
|
];
|
||||||
|
|
||||||
|
# The VM uses its regular swap instead of compressed RAM swap.
|
||||||
|
zramSwap.enable = false;
|
||||||
|
|
||||||
|
time.timeZone = "Etc/UTC";
|
||||||
|
|
||||||
|
system.stateVersion = "24.05";
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
# Vultr's first virtio disk is normally /dev/vda. Change this before
|
||||||
|
# deployment if the selected image exposes its system disk differently.
|
||||||
|
disko.devices.disk.main = {
|
||||||
|
type = "disk";
|
||||||
|
device = "/dev/vda";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
bios = {
|
||||||
|
size = "1M";
|
||||||
|
type = "EF02";
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP = {
|
||||||
|
size = "512M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
mountOptions = [
|
||||||
|
"fmask=0077"
|
||||||
|
"dmask=0077"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
swap = {
|
||||||
|
size = "8G";
|
||||||
|
content = {
|
||||||
|
type = "swap";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Install GRUB for both legacy BIOS and EFI-capable Vultr plans.
|
||||||
|
boot.loader = {
|
||||||
|
grub = {
|
||||||
|
enable = true;
|
||||||
|
devices = [ "/dev/vda" ];
|
||||||
|
efiSupport = true;
|
||||||
|
efiInstallAsRemovable = true;
|
||||||
|
};
|
||||||
|
efi.canTouchEfiVariables = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
let
|
||||||
|
domains = [
|
||||||
|
"audiobookshelf"
|
||||||
|
"cache"
|
||||||
|
"gitea"
|
||||||
|
"gems"
|
||||||
|
"jellyfin"
|
||||||
|
"share"
|
||||||
|
];
|
||||||
|
extraDomains = [ "www.norn-sight.com" ];
|
||||||
|
|
||||||
|
makeCert = name: {
|
||||||
|
name = "${name}.tmmworkshop.com";
|
||||||
|
value = {
|
||||||
|
webroot = "/var/lib/acme/.challenges";
|
||||||
|
group = "acme";
|
||||||
|
reloadServices = [ "haproxy.service" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
makeExtraCert = name: {
|
||||||
|
inherit name;
|
||||||
|
value = {
|
||||||
|
webroot = "/var/lib/acme/.challenges";
|
||||||
|
group = "acme";
|
||||||
|
reloadServices = [ "haproxy.service" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
acmeServices =
|
||||||
|
map (domain: "acme-${domain}.tmmworkshop.com.service") domains
|
||||||
|
++ map (domain: "acme-${domain}.service") extraDomains;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
80
|
||||||
|
443
|
||||||
|
2223
|
||||||
|
];
|
||||||
|
|
||||||
|
# Global robots.txt served by HAProxy for every vhost (see haproxy.cfg).
|
||||||
|
environment.etc."haproxy/robots.txt".source = ./robots.txt;
|
||||||
|
|
||||||
|
services = {
|
||||||
|
haproxy = {
|
||||||
|
enable = true;
|
||||||
|
config = builtins.readFile ./haproxy.cfg;
|
||||||
|
};
|
||||||
|
nginx = {
|
||||||
|
enable = true;
|
||||||
|
virtualHosts."acme-challenge" = {
|
||||||
|
listen = [
|
||||||
|
{
|
||||||
|
addr = "127.0.0.1";
|
||||||
|
port = 8402;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
locations."/.well-known/acme-challenge/" = {
|
||||||
|
root = "/var/lib/acme/.challenges";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
security.acme = {
|
||||||
|
acceptTerms = true;
|
||||||
|
defaults.email = "Richie@tmmworkshop.com";
|
||||||
|
certs = builtins.listToAttrs ((map makeCert domains) ++ (map makeExtraCert extraDomains));
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd = {
|
||||||
|
services.haproxy = {
|
||||||
|
# HAProxy needs the initial certificates and Tailscale connectivity before
|
||||||
|
# it can serve requests to its backends.
|
||||||
|
after = acmeServices ++ [ "tailscaled-autoconnect.service" ];
|
||||||
|
wants = acmeServices ++ [ "tailscaled-autoconnect.service" ];
|
||||||
|
};
|
||||||
|
tmpfiles.rules = [
|
||||||
|
"d /var/lib/acme/.challenges 0750 acme acme - -"
|
||||||
|
"d /var/lib/acme/.challenges/.well-known 0750 acme acme - -"
|
||||||
|
"d /var/lib/acme/.challenges/.well-known/acme-challenge 0750 acme acme - -"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users = {
|
||||||
|
haproxy.extraGroups = [ "acme" ];
|
||||||
|
nginx.extraGroups = [ "acme" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -110,6 +110,14 @@ frontend ContentSwitching
|
|||||||
use_backend gems if host_gems
|
use_backend gems if host_gems
|
||||||
use_backend norn_sight if host_norn_sight
|
use_backend norn_sight if host_norn_sight
|
||||||
|
|
||||||
|
# Proxy Gitea's built-in SSH server over Tailscale to Jeeves. Port 22 remains
|
||||||
|
# available to Endlessh on Portal.
|
||||||
|
frontend GiteaSSH
|
||||||
|
bind *:2223 v4v6
|
||||||
|
mode tcp
|
||||||
|
option tcplog
|
||||||
|
default_backend gitea_ssh
|
||||||
|
|
||||||
# Stick-table only (no servers): tracks per-IP request rate to Gitea's compare
|
# Stick-table only (no servers): tracks per-IP request rate to Gitea's compare
|
||||||
# endpoint over a 5-minute window so the frontend can cap it at 1 per 5 min.
|
# endpoint over a 5-minute window so the frontend can cap it at 1 per 5 min.
|
||||||
backend st_compare
|
backend st_compare
|
||||||
@@ -124,31 +132,35 @@ backend acme_challenge
|
|||||||
|
|
||||||
backend audiobookshelf_nodes
|
backend audiobookshelf_nodes
|
||||||
mode http
|
mode http
|
||||||
server server 127.0.0.1:8000
|
server server jeeves:8000
|
||||||
|
|
||||||
backend cache_nodes
|
backend cache_nodes
|
||||||
mode http
|
mode http
|
||||||
server server 127.0.0.1:5000
|
server server jeeves:5000
|
||||||
|
|
||||||
backend jellyfin
|
backend jellyfin
|
||||||
mode http
|
mode http
|
||||||
option forwardfor
|
option forwardfor
|
||||||
server jellyfin 127.0.0.1:8096
|
server jellyfin jeeves:8096
|
||||||
|
|
||||||
backend share_nodes
|
backend share_nodes
|
||||||
mode http
|
mode http
|
||||||
server server 127.0.0.1:8091
|
server server jeeves:8091
|
||||||
|
|
||||||
backend gitea
|
backend gitea
|
||||||
mode http
|
mode http
|
||||||
server server 127.0.0.1:6443
|
server server jeeves:6443
|
||||||
|
|
||||||
|
backend gitea_ssh
|
||||||
|
mode tcp
|
||||||
|
server gitea jeeves:2223
|
||||||
|
|
||||||
backend gems
|
backend gems
|
||||||
mode http
|
mode http
|
||||||
option forwardfor
|
option forwardfor
|
||||||
timeout server 1h
|
timeout server 1h
|
||||||
server gems 127.0.0.1:8002
|
server gems jeeves:8002
|
||||||
|
|
||||||
backend norn_sight
|
backend norn_sight
|
||||||
mode http
|
mode http
|
||||||
server server 127.0.0.1:8001
|
server server jeeves:8001
|
||||||
@@ -8,8 +8,10 @@
|
|||||||
"${inputs.self}/common/optional/steam.nix"
|
"${inputs.self}/common/optional/steam.nix"
|
||||||
"${inputs.self}/common/optional/syncthing_base.nix"
|
"${inputs.self}/common/optional/syncthing_base.nix"
|
||||||
"${inputs.self}/common/optional/systemd-boot.nix"
|
"${inputs.self}/common/optional/systemd-boot.nix"
|
||||||
|
"${inputs.self}/common/optional/tailscale.nix"
|
||||||
"${inputs.self}/common/optional/yubikey.nix"
|
"${inputs.self}/common/optional/yubikey.nix"
|
||||||
"${inputs.self}/common/optional/zerotier.nix"
|
"${inputs.self}/common/optional/zerotier.nix"
|
||||||
|
"${inputs.self}/common/optional/zfs"
|
||||||
./hardware.nix
|
./hardware.nix
|
||||||
./open_webui.nix
|
./open_webui.nix
|
||||||
./programs.nix
|
./programs.nix
|
||||||
|
|||||||
@@ -1,22 +1,7 @@
|
|||||||
{ config, ... }:
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./cli
|
./minimal.nix
|
||||||
./programs.nix
|
./programs.nix
|
||||||
./ssh_config.nix
|
./ssh_config.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
programs = {
|
|
||||||
home-manager.enable = true;
|
|
||||||
git.enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
home = {
|
|
||||||
username = "richie";
|
|
||||||
homeDirectory = "/home/${config.home.username}";
|
|
||||||
stateVersion = "24.05";
|
|
||||||
sessionVariables = {
|
|
||||||
FLAKE = "$HOME/dotfiles";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{ config, pkgs, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./cli
|
||||||
|
];
|
||||||
|
|
||||||
|
programs = {
|
||||||
|
home-manager.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
home = {
|
||||||
|
username = "richie";
|
||||||
|
homeDirectory = "/home/${config.home.username}";
|
||||||
|
stateVersion = "24.05";
|
||||||
|
sessionVariables.FLAKE = "$HOME/dotfiles";
|
||||||
|
packages = with pkgs; [
|
||||||
|
# cli
|
||||||
|
btop
|
||||||
|
eza
|
||||||
|
ripgrep
|
||||||
|
starship
|
||||||
|
tmux
|
||||||
|
# networking
|
||||||
|
wget
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,8 +3,6 @@
|
|||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
# cli
|
# cli
|
||||||
bat
|
bat
|
||||||
btop
|
|
||||||
eza
|
|
||||||
fd
|
fd
|
||||||
ffmpegthumbnailer
|
ffmpegthumbnailer
|
||||||
fzf
|
fzf
|
||||||
@@ -18,9 +16,6 @@
|
|||||||
p7zip
|
p7zip
|
||||||
poppler
|
poppler
|
||||||
rar
|
rar
|
||||||
ripgrep
|
|
||||||
starship
|
|
||||||
tmux
|
|
||||||
unzip
|
unzip
|
||||||
yazi
|
yazi
|
||||||
zoxide
|
zoxide
|
||||||
@@ -37,7 +32,6 @@
|
|||||||
# networking
|
# networking
|
||||||
iperf3
|
iperf3
|
||||||
nmap
|
nmap
|
||||||
wget
|
|
||||||
# python
|
# python
|
||||||
ruff
|
ruff
|
||||||
uv
|
uv
|
||||||
|
|||||||
@@ -43,6 +43,18 @@
|
|||||||
IdentityFile = "~/.ssh/id_ed25519";
|
IdentityFile = "~/.ssh/id_ed25519";
|
||||||
Port = 922;
|
Port = 922;
|
||||||
};
|
};
|
||||||
|
portal-1 = {
|
||||||
|
HostName = "portal-1";
|
||||||
|
User = "richie";
|
||||||
|
IdentityFile = "~/.ssh/id_ed25519";
|
||||||
|
Port = 278;
|
||||||
|
};
|
||||||
|
"gitea.tmmworkshop.com" = {
|
||||||
|
HostName = "gitea.tmmworkshop.com";
|
||||||
|
User = "gitea";
|
||||||
|
IdentityFile = "~/.ssh/id_ed25519";
|
||||||
|
Port = 2223;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../home/minimal.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
+39
-37
@@ -1,62 +1,64 @@
|
|||||||
richie_password: ENC[AES256_GCM,data:DMi3M8aqrQ60APIofr8wJMh+VZ14hLRxz6jWZgzswr0pV/QVSX53ShBFr90ruO3mucOLYv0l+bI31covfqMAhXWBJp9wUgtC2Q==,iv:qgtn30hZfIL4dBnQSLkjbo7zPJA4m9TR0f52sTFc0v4=,tag:ydLbcGyXjv0fE+4b5ECX5w==,type:str]
|
richie_password: ENC[AES256_GCM,data:DMi3M8aqrQ60APIofr8wJMh+VZ14hLRxz6jWZgzswr0pV/QVSX53ShBFr90ruO3mucOLYv0l+bI31covfqMAhXWBJp9wUgtC2Q==,iv:qgtn30hZfIL4dBnQSLkjbo7zPJA4m9TR0f52sTFc0v4=,tag:ydLbcGyXjv0fE+4b5ECX5w==,type:str]
|
||||||
math_password: ENC[AES256_GCM,data:ykiSr3iBHrShJarEQSJ/zuXbCPcbW2oUpaAjblu1V15ufFKVSMZM94LlpMiCYtN9cYBLs98hcMeajJbvgbwT5emPHthy9+TJDw==,iv:1TJEUo0ishqFAZiUE1473yR3RT6Gbtqt4zM+C1a1KEk=,tag:pR6jyIj+bu3XaSx5yIHSmA==,type:str]
|
math_password: ENC[AES256_GCM,data:ykiSr3iBHrShJarEQSJ/zuXbCPcbW2oUpaAjblu1V15ufFKVSMZM94LlpMiCYtN9cYBLs98hcMeajJbvgbwT5emPHthy9+TJDw==,iv:1TJEUo0ishqFAZiUE1473yR3RT6Gbtqt4zM+C1a1KEk=,tag:pR6jyIj+bu3XaSx5yIHSmA==,type:str]
|
||||||
|
tailscale_auth_key: ENC[AES256_GCM,data:SZEfpVnqA84AQLA/geGsPNZfsWBktlM3Zrm7OpuSS/hCWWEKtSIbRYyovH68h8tQJBQFpv18n4oXJe9eAr6Mog==,iv:x1m6XPS/h818qNRcO+uqYjgTfd/FkxHHaSHHJrLyrp4=,tag:eOpcJIhzUWFzXTuRdS9S6w==,type:str]
|
||||||
|
tailscale_oauth_client_id: ENC[AES256_GCM,data:+cWxKlLIcJtsLhqmZUNLVDs=,iv:qrTjhLfu82pzbZnU6BrK1n+Ncbm2Cl3MMCSTKCJ6H8U=,tag:s/N5XpLwNH1gZ+LzbqRr2g==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBObHhkaFlnaG4zaTZtbkIw
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEMjQvSzZSY08vRlI0dzBP
|
||||||
TkxSQnMxbDNwUVo4R1VYRDNKVFRDUE9kb0ZjCnpWWElJUVNuNFBsMzZod1ZQY0Fa
|
YytpVlpPSGFHaTI5b2xzUkFFdEY0bVhQdGgwClM3UmM1OElpcEZTcTB0NHpSZ2d6
|
||||||
VXgxNjZ0TUJ3cEt2OTFWL2dGY0txVHMKLS0tIHNQU3Rsanh4dkc3ZElsai9YMFdH
|
aVBpcjVIaDk3Tko1cm4xYjdkYVQ1L1EKLS0tIE1aQW95U0JIVGc4MGloSXRXc3ZS
|
||||||
cXUzVmFxTUVIOWZVR2Fpa2crdWsrdlkKwdGLfbKWc25qfBKyd/cawiUWv9iepKHN
|
a3EvU1dJdmxOeVBIS28yT3dHd2ZEYWcKv5TIqRRQs3AXER1WHEx8wotyXr2Ktxsu
|
||||||
EOp/LdH2GbCfnQSVbxi28ukLHxWqOLdqMm8xSni/Of2PXvMnpdyCyQ==
|
dwldwEHyDNqwf7UgGOZu7JueeZ7y2KC8KZ7ixIdrhVaoxuLEvCg7VA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1u8zj599elqqvcmhxn8zuwrufsz8w8w366d3ayrljjejljt2q45kq8mxw9c
|
recipient: age1u8zj599elqqvcmhxn8zuwrufsz8w8w366d3ayrljjejljt2q45kq8mxw9c
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6R0djMTArVmFySE5DMnFr
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByaE5jb3p3VzZ1dE05Wmlx
|
||||||
WFdBUERFbE1HRVVFd0oyaXJ2eU5HUStBUFN3CnR3ckZ2bkpGZFFScHQwTlBZYTMv
|
c1ZoWGhTcTRHRE9oMWtkaCtBY1l1a2QyWWlvCk9MM2FmcEZubXR1REpOcHZ6aHVE
|
||||||
cTBXbGVoTXlWczhId05QVjZDRzFqRjQKLS0tIDNIcVBydHYwTGRnVU1mWWk4WTlR
|
UEkzbllZdkxPYzlTMHNod3VWNnhoaTgKLS0tIGVCdEpqUzhFM0swRjVjb0Z6ckFN
|
||||||
eVlwQWgxSG5SdmFrWTlOcFo5eXZONWMKgx4huoSnbkRq0wQbsYgsWUKDTxDGNvYR
|
UzlYb1U4UGJqWE9oczdlNVh1S1lqL3cKSKxaJFhun2ee45I1jC55YKNdfYB8MsS+
|
||||||
anVMQg+c7PwDlk1V4JQZ4WrYLx63Ep5qDjGlN/Ssf2Vo6rAuuKetcA==
|
dgFOFCvn/4uEcUyKZbUf0STjbIZ5eMHl8NVRTupI4tftGiE559M5Wg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1q47vup0tjhulkg7d6xwmdsgrw64h4ax3la3evzqpxyy4adsmk9fs56qz3y
|
recipient: age1q47vup0tjhulkg7d6xwmdsgrw64h4ax3la3evzqpxyy4adsmk9fs56qz3y
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2QjZYejFYbCswQjdmaDA0
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2U01jTVZQT1RRRU1nQmVv
|
||||||
L2ZqUUhtYU12YlpISmxueHQzRG5YL0tQNXh3CndGamMwRzYvUzkvaE9DVnMwTkNC
|
Z0lnOGJLN1c0UkNLbnJjV29na3Rub0xzbXhNCjY0aFNuUnFDUnZheDRHT3R5L3Fv
|
||||||
cFMxczZuOUorS3dnVUpjaVdCUjhLZ2MKLS0tIGlDeGRGa3dRbWgzT3NVYjBaYUJ4
|
ZkJ3QkF2RENKRVdUYjRzRXMrNklvN2cKLS0tIFJzK2o1MmVTWHZMWmVDWlgzeGY5
|
||||||
VW5yeFlvWUZ5MVpNZHA5M1VXR1hxU1kKqii08/MB2aabgP4RQs1ry8AxmFqB8Mn+
|
VDg4OGl0NGFYY2VNZGY5UkR1cVdWY0UK1qa3swdfgUw1tnnThRf1sl9f2zTP/d/d
|
||||||
m7B0u64aziKXLSl0u471wqgD+YGRwNcajXT2pHCy8QWLznzvIMSrxA==
|
iwcenOEryXatVMJjZVWrLC5C7MwtKz+pIBXa98yaSpCDLqjAMwOCPg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1jhf7vm0005j60mjq63696frrmjhpy8kpc2d66mw044lqap5mjv4snmwvwm
|
recipient: age1jhf7vm0005j60mjq63696frrmjhpy8kpc2d66mw044lqap5mjv4snmwvwm
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrSUc3VnllVDFZTm1jRnlP
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTREI2UEZwS0Y0SmEvK1F4
|
||||||
ZDBqelhkeHliZ1VlcjVnblQyeFlWclZTWkZjCjhJQk5EWkVoQjdoMHg4Zko4OU1C
|
Y1hZZjRVWWxMT29mc3ZXcHFDclRUYi96LzA4CkIwZ2h4T2JQRW1OL2hmRTFHWkFY
|
||||||
NWFnVTIwV3RYbTZSeWttaVRPbjNrMG8KLS0tIDZWUHJSbVVlR0lKUDRtc3VqOTc1
|
RHZqQUFqU3hvNlRBZHRiSThPVUE3TG8KLS0tIHh0Z2tlNXREczJsa1g2ekZyZjBt
|
||||||
cGJ3NDBLem9FNUpnbStYRTlqQStHV2sKwxPe4nTULsU0mVeUh8mhr2KX9U0iT5dL
|
dW41aVA3VEVqa1NDMHM0T3FKZFNTN0kKGQPRUMSRtibFVFRZR/igsjgxgDo4ogga
|
||||||
zvHldoQG6mZHgtHK6XI5AQJYf+zUW66OKqNSxAnn+BM20QkAQVZNVw==
|
dUHBRU65r41er+Wk3lKdtbXJX1g83K8ay7540hNXheOhxFi57trrzA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age13lmqgc3jvkyah5e3vcwmj4s5wsc2akctcga0lpc0x8v8du3fxprqp4ldkv
|
recipient: age13lmqgc3jvkyah5e3vcwmj4s5wsc2akctcga0lpc0x8v8du3fxprqp4ldkv
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0NU9ac2FuRHI1dkQycmc0
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrV1Z2MGNPK28wTStRSUZW
|
||||||
YlhGK29UeTdiZEZXcWtPUW4rMis4Z2NWYWpJCkVldEdMc3ZTaDFidHpaZk5mM283
|
dmFMaUt5MENHeUpVcDVxQnZhd1o5RmJwOWwwCittaGNrcW1hV25VaGJaTEhZRFBJ
|
||||||
bTd0RlN5SHVabGhjeXFnSThydnVoem8KLS0tIElWclYvTmtkb0wyNDlKT3ZEckwx
|
Y2toWEpFWFZYT0plb2lkTlJCS2Mwb3MKLS0tIHlkVjVESWFsbWlVQ0xVQ0llclF2
|
||||||
L2NObzZadlJ5d3MyeGRqKy95L3BOMFEKtoswi6r2TmCZzngUkiGQV5TTsuzisMFS
|
L1FMbC91YjJsR3BGRjJ0QVQ1MDdBbEUKNnOI9+H1LJcvVcwl9uQMmFp35y5RB+Zl
|
||||||
5QI0aQZwhexqUMvbPuajYKvcPj+D6a2xaxbL3TBRLjOrFmcp5J7/YA==
|
Mo3z3unweQtS+jiUn7RyVs5daG1E2tfihu2CQDWaktuCuunMpt3Wew==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1l272y8udvg60z7edgje42fu49uwt4x2gxn5zvywssnv9h2krms8s094m4k
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByUlVjM2xpc281bHZzOGVo
|
|
||||||
L3VEclhJZDd5SS9mazFiTk9DcmxMaWxPT213ClNPWERKQU03OWk0OEVIY05ib2VG
|
|
||||||
WXhiZDhuMDZ5ZmFPWHB5RTFKYmpkVzAKLS0tIGppUndCb25wb2dyV01YbENrWjdU
|
|
||||||
TmMvWVpobnl0eXBIOGQwMW5BSlhJTUkKzua1artJWbZlKfzv27xfZJeBpntBYwUf
|
|
||||||
c8i1gNlvRwkhFAlrWcKR65vgyxsO3rbkLJRkcwG/q4hHj9zBeC/K2A==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1ufnewppysaq2wwcl4ugngjz8pfzc5a35yg7luq0qmuqvctajcycs5lf6k4
|
recipient: age1ufnewppysaq2wwcl4ugngjz8pfzc5a35yg7luq0qmuqvctajcycs5lf6k4
|
||||||
lastmodified: "2026-06-29T20:19:44Z"
|
- enc: |
|
||||||
mac: ENC[AES256_GCM,data:GIvQxWt4tZGn0fyiXVtxGFQQoNcFUgilF+/PSz50exVrmzsS0XQUk/TIDFHaQR9jlJI50jqlyc1rBHgjnqC2oPHhPWaaVhgF18vQI55rGKdymNFjsHnaCkblFVdR1RJm0FSB2Ri6y5k8tfN3ywiwromJRz4NYzr1hbmr36azfg4=,iv:V3jspeYt/d2wy13gUrQmPGARm0hxwvSL/mocJAUofdw=,tag:vARoUzBWTJKkONDGoQdzNQ==,type:str]
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqR0hUT3hxTzRwTVhuTm9E
|
||||||
|
TUFtNFFCYWNUNUNVbHNaNXQ0aEVBa04vSEU0CkRPVzhuYUsrM0wxZjRDdkxvTVA2
|
||||||
|
UW9CSjlIQW5yenhaUi9mbEdLYlhWYk0KLS0tIGRXMm9Wa3l4UWxqcGl6ZlBZV3E0
|
||||||
|
Z3YrbUYrSmV0NHNTeTdhMkJEdytwSmMKGLpe5+p4FGJNHy6SVgnsDbEsiP0J8lW/
|
||||||
|
0+3X3LQoL0f1S2L5DjexF/H0NGvWjAPaGzDNB6V50VxGDabEIVqjvQ==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1vyav6kxtvt3z4vtnkkjj38eu8hlts5m7ygyckhskvalg2gpjk52su53d0a
|
||||||
|
lastmodified: "2026-08-25T15:35:53Z"
|
||||||
|
mac: ENC[AES256_GCM,data:4/WuSmUb2ONlB56dgbwwekZ8h7E1vvPzPxYCvNM8N68nt1nb9JxoMeAX9PktmljAarYLQJd1O5+49ZEph3Gzx9LrwvWnm9asq0UeeJi08x7wdQDcawNFlBFzXF2qlgxU1JssNHZZOB+5nYH4t8vFzPTq/9BqoifPR2+pLBYhUQI=,iv:N/gVgeWT2/1bfHuR6TanyDBijWmXAqyACXpnwfwDuVA=,tag:pQ4ct4G7SARz2wyjOc7AKQ==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.13.1
|
version: 3.13.3
|
||||||
|
|||||||
Reference in New Issue
Block a user