Compare commits
16 commits
master
...
pkg-ircbot
Author | SHA1 | Date | |
---|---|---|---|
43d852f11d | |||
7a6f7b5cc0 | |||
6982def2c7 | |||
95ee759f75 | |||
253fd2f2f0 | |||
cbf4a3b256 | |||
049f2f669a | |||
a061b6e51a | |||
|
24c2712e71 | ||
|
d54bcec082 | ||
|
dff97b97a5 | ||
|
5e787f49ce | ||
|
9c8abab1e3 | ||
|
a1d2ed677c | ||
|
9c0917027b | ||
|
2343364eac |
524 changed files with 8615 additions and 17439 deletions
3
.dvc/.gitignore
vendored
Normal file
3
.dvc/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/config.local
|
||||
/tmp
|
||||
/cache
|
5
.dvc/config
Normal file
5
.dvc/config
Normal file
|
@ -0,0 +1,5 @@
|
|||
[core]
|
||||
remote = cdn
|
||||
['remote "cdn"']
|
||||
url = s3://content-delivery/assets
|
||||
endpointurl = https://object-storage.privatevoid.net
|
3
.dvcignore
Normal file
3
.dvcignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Add patterns of files dvc should ignore, which could improve
|
||||
# the performance. Learn more at
|
||||
# https://dvc.org/doc/user-guide/dvcignore
|
22
.github/workflows/update-flake-lock.yaml
vendored
Normal file
22
.github/workflows/update-flake-lock.yaml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: Update flake.lock
|
||||
on:
|
||||
workflow_dispatch: # allows manual triggering
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
jobs:
|
||||
lockfile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v16
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y%m%d')"
|
||||
- name: Update flake.lock
|
||||
uses: DeterminateSystems/update-flake-lock@v13
|
||||
with:
|
||||
pr-title: "Updates ${{ steps.date.outputs.date }}"
|
||||
branch: "updates-${{ steps.date.outputs.date }}"
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,5 +3,3 @@ result
|
|||
result-*
|
||||
**/.direnv/
|
||||
.data/
|
||||
.cache/
|
||||
.nixos-test-history
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
perSystem = {
|
||||
options.catalog = lib.mkOption {
|
||||
type = with lib.types; lazyAttrsOf (lazyAttrsOf (lazyAttrsOf (submodule ./target.nix)));
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
{ lib, name, ... }:
|
||||
|
||||
{
|
||||
options = {
|
||||
description = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
actions = lib.mkOption {
|
||||
type = with lib.types; lazyAttrsOf (submodule {
|
||||
options = {
|
||||
description = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
command = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
|
||||
packages = lib.mkOption {
|
||||
type = with lib.types; listOf package;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
imports = [
|
||||
./services.nix
|
||||
./secrets.nix
|
||||
];
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
{ config, lib, withSystem, ... }:
|
||||
|
||||
let
|
||||
inherit (config) cluster hours;
|
||||
in
|
||||
|
||||
{
|
||||
perSystem = { config, pkgs, system, ... }: {
|
||||
catalog.cluster = {
|
||||
secrets = lib.pipe cluster.config.services [
|
||||
(lib.mapAttrsToList (svcName: svcConfig: lib.mapAttrsToList (secretName: secretConfig: {
|
||||
name = "${svcName}/${secretName}";
|
||||
value = {
|
||||
description = "Cluster secret '${secretName}' of service '${svcName}'";
|
||||
actions = let
|
||||
agenixRules = builtins.toFile "agenix-rules-shim.nix" /*nix*/ ''
|
||||
builtins.fromJSON (builtins.readFile (builtins.getEnv "AGENIX_KEYS_JSON"))
|
||||
'';
|
||||
|
||||
mkKeys = secretFile: nodes: builtins.toFile "agenix-keys.json" (builtins.toJSON {
|
||||
"${secretFile}".publicKeys = (map (hour: hours.${hour}.ssh.id.publicKey) nodes) ++ cluster.config.secrets.extraKeys;
|
||||
});
|
||||
|
||||
setupCommands = secretFile: nodes: let
|
||||
agenixKeysJson = mkKeys secretFile nodes;
|
||||
in ''
|
||||
export RULES='${agenixRules}'
|
||||
export AGENIX_KEYS_JSON='${agenixKeysJson}'
|
||||
mkdir -p "$PRJ_ROOT/cluster/secrets"
|
||||
cd "$PRJ_ROOT/cluster/secrets"
|
||||
'';
|
||||
in (lib.optionalAttrs (secretConfig.generate != null) {
|
||||
generateSecret = {
|
||||
description = "Generate this secret";
|
||||
command = if secretConfig.shared then let
|
||||
secretFile = "${svcName}-${secretName}.age";
|
||||
in ''
|
||||
${setupCommands secretFile secretConfig.nodes}
|
||||
${withSystem system secretConfig.generate} | agenix -e '${secretFile}'
|
||||
'' else lib.concatStringsSep "\n" (map (node: let
|
||||
secretFile = "${svcName}-${secretName}-${node}.age";
|
||||
in ''
|
||||
${setupCommands secretFile [ node ]}
|
||||
${withSystem system secretConfig.generate} | agenix -e '${secretFile}'
|
||||
'') secretConfig.nodes);
|
||||
};
|
||||
}) // (if secretConfig.shared then let
|
||||
secretFile = "${svcName}-${secretName}.age";
|
||||
in {
|
||||
editSecret = {
|
||||
description = "Edit this secret";
|
||||
command = ''
|
||||
${setupCommands secretFile secretConfig.nodes}
|
||||
agenix -e '${secretFile}'
|
||||
'';
|
||||
};
|
||||
} else lib.mapAttrs' (name: lib.nameValuePair "editSecretInstance-${name}") (lib.genAttrs secretConfig.nodes (node: let
|
||||
secretFile = "${svcName}-${secretName}-${node}.age";
|
||||
in {
|
||||
description = "Edit this secret for '${node}'";
|
||||
command = ''
|
||||
${setupCommands secretFile [ node ]}
|
||||
agenix -e '${secretFile}'
|
||||
'';
|
||||
})));
|
||||
};
|
||||
}) svcConfig.secrets))
|
||||
lib.concatLists
|
||||
lib.listToAttrs
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (config) cluster flake;
|
||||
in
|
||||
|
||||
{
|
||||
perSystem = { config, pkgs, ... }: {
|
||||
catalog.cluster = {
|
||||
services = lib.mapAttrs (name: svc: {
|
||||
description = "Cluster service: ${name}";
|
||||
actions = let
|
||||
mkDeployAction = { description, agents }: {
|
||||
inherit description;
|
||||
packages = [
|
||||
config.packages.cachix
|
||||
pkgs.tmux
|
||||
];
|
||||
command = let
|
||||
cachixDeployJson = pkgs.writeText "cachix-deploy.json" (builtins.toJSON {
|
||||
agents = lib.genAttrs agents (name: builtins.unsafeDiscardStringContext flake.nixosConfigurations.${name}.config.system.build.toplevel);
|
||||
});
|
||||
in ''
|
||||
set -e
|
||||
echo building ${toString (lib.length agents)} configurations in parallel
|
||||
tmux new-session ${lib.concatStringsSep " split-window " (
|
||||
map (host: let
|
||||
drvPath = builtins.unsafeDiscardStringContext flake.nixosConfigurations.${host}.config.system.build.toplevel.drvPath;
|
||||
in '' 'echo building configuration for ${host}; nix build -L --no-link --store "ssh-ng://${host}" --eval-store auto "${drvPath}^*"'\; '') agents
|
||||
)} select-layout even-vertical
|
||||
|
||||
source ~/.config/cachix/deploy
|
||||
cachix deploy activate ${cachixDeployJson}
|
||||
echo
|
||||
'';
|
||||
};
|
||||
in {
|
||||
deployAll = mkDeployAction {
|
||||
description = "Deploy ALL groups of this service.";
|
||||
agents = lib.unique (lib.concatLists (lib.attrValues svc.nodes));
|
||||
};
|
||||
} // lib.mapAttrs' (group: agents: {
|
||||
name = "deployGroup-${group}";
|
||||
value = mkDeployAction {
|
||||
description = "Deploy the '${group}' group of this service.";
|
||||
inherit agents;
|
||||
};
|
||||
}) svc.nodes;
|
||||
}) cluster.config.services;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,23 +1,18 @@
|
|||
{ lib, depot }:
|
||||
{ lib, hostName }:
|
||||
|
||||
lib.evalModules {
|
||||
specialArgs = {
|
||||
inherit depot;
|
||||
};
|
||||
modules = [
|
||||
# Arbitrary variables to reference across multiple services
|
||||
./lib/vars
|
||||
{ vars = { inherit hostName; }; }
|
||||
|
||||
# Cluster-level port-magic
|
||||
../modules/port-magic
|
||||
|
||||
../tools/inject.nix
|
||||
./lib/load-hosts.nix
|
||||
./lib/services.nix
|
||||
./lib/inject-nixos-config.nix
|
||||
./lib/port-magic-multi.nix
|
||||
./lib/mesh.nix
|
||||
./lib/secrets.nix
|
||||
./lib/testing.nix
|
||||
./lib/lib.nix
|
||||
|
||||
./import-services.nix
|
||||
];
|
||||
|
|
15
cluster/inject.nix
Normal file
15
cluster/inject.nix
Normal file
|
@ -0,0 +1,15 @@
|
|||
hostName:
|
||||
{ lib, ... }:
|
||||
|
||||
let
|
||||
cluster = import ./. { inherit lib hostName; };
|
||||
in
|
||||
|
||||
{
|
||||
_module.args.cluster = {
|
||||
inherit (cluster.config) vars;
|
||||
inherit (cluster.config.vars) hosts;
|
||||
inherit (cluster) config;
|
||||
};
|
||||
imports = cluster.config.out.injectedNixosConfig;
|
||||
}
|
|
@ -2,9 +2,9 @@
|
|||
with lib;
|
||||
|
||||
{
|
||||
options.out = mkOption {
|
||||
description = "Output functions.";
|
||||
type = with types; lazyAttrsOf (functionTo raw);
|
||||
default = const [];
|
||||
options.out.injectedNixosConfig = mkOption {
|
||||
description = "NixOS configuration modules to inject into the host.";
|
||||
type = with types; listOf anything;
|
||||
default = {};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
{
|
||||
options.lib = {
|
||||
forService = lib.mkOption {
|
||||
description = "Enable these definitions for a particular service only.";
|
||||
type = lib.types.functionTo lib.types.raw;
|
||||
readOnly = true;
|
||||
default = service: lib.mkIf (!config.simulacrum || lib.any (s: s == service) config.testConfig.activeServices);
|
||||
};
|
||||
};
|
||||
}
|
9
cluster/lib/load-hosts.nix
Normal file
9
cluster/lib/load-hosts.nix
Normal file
|
@ -0,0 +1,9 @@
|
|||
{ config, lib, ... }:
|
||||
let
|
||||
hosts = import ../../hosts;
|
||||
self = hosts.${config.vars.hostName};
|
||||
others = lib.filterAttrs (_: host: host != self) hosts;
|
||||
in
|
||||
{
|
||||
config.vars.hosts = hosts // { inherit self others; };
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
{
|
||||
hostLinks = lib.pipe config.services [
|
||||
(lib.filterAttrs (_: svc: svc.meshLinks != {}))
|
||||
(lib.mapAttrsToList (svcName: svc:
|
||||
lib.mapAttrsToList (groupName: links:
|
||||
lib.genAttrs svc.nodes.${groupName} (hostName: lib.mapAttrs (_: cfg: { ... }: {
|
||||
imports = [ cfg.link ];
|
||||
ipv4 = config.vars.mesh.${hostName}.meshIp;
|
||||
}) links)
|
||||
) svc.meshLinks
|
||||
))
|
||||
(map lib.mkMerge)
|
||||
lib.mkMerge
|
||||
];
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
options.hostLinks = mkOption {
|
||||
type = types.attrsOf (types.attrsOf (types.submodule ../../modules/port-magic/link.nix));
|
||||
description = "Port Magic links, per host.";
|
||||
default = {};
|
||||
};
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
options.secrets = {
|
||||
extraKeys = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
description = "Additional keys with which to encrypt all secrets.";
|
||||
default = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL5C7mC5S2gM0K6x0L/jNwAeQYbFSzs16Q73lONUlIkL max@TITAN"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMmdWfmAs/0rno8zJlhBFMY2SumnHbTNdZUXJqxgd9ON max@jericho"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
{ config, lib, name, ... }:
|
||||
vars:
|
||||
{ name, config, lib, ... }:
|
||||
with lib;
|
||||
|
||||
let
|
||||
filterGroup = group: hostName: builtins.filter (x: x != hostName) group;
|
||||
serviceName = name;
|
||||
notSelf = x: x != vars.hostName;
|
||||
|
||||
filterGroup = builtins.filter notSelf;
|
||||
in
|
||||
|
||||
{
|
||||
imports = [
|
||||
./services/secrets.nix
|
||||
];
|
||||
|
||||
options = {
|
||||
nodes = mkOption {
|
||||
description = ''
|
||||
|
@ -23,12 +21,12 @@ in
|
|||
* X evaluators, Y smallBuilders, Z bigBuilders
|
||||
etc.
|
||||
'';
|
||||
type = with types; lazyAttrsOf (oneOf [ str (listOf str) ]);
|
||||
type = with types; attrsOf (oneOf [ str (listOf str) ]);
|
||||
default = [];
|
||||
};
|
||||
otherNodes = mkOption {
|
||||
description = "Other nodes in the group.";
|
||||
type = with types; lazyAttrsOf (functionTo (listOf str));
|
||||
type = with types; attrsOf (listOf str);
|
||||
default = [];
|
||||
};
|
||||
nixos = mkOption {
|
||||
|
@ -36,36 +34,6 @@ in
|
|||
type = with types; attrs;
|
||||
default = {};
|
||||
};
|
||||
meshLinks = mkOption {
|
||||
description = "Create host links on the mesh network.";
|
||||
type = types.attrsOf (types.attrsOf (types.submodule {
|
||||
options = {
|
||||
link = mkOption {
|
||||
type = types.deferredModule;
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = {};
|
||||
};
|
||||
simulacrum = {
|
||||
enable = mkEnableOption "testing this service in the Simulacrum";
|
||||
deps = mkOption {
|
||||
description = "Other services to include.";
|
||||
type = with types; listOf str;
|
||||
default = [];
|
||||
};
|
||||
settings = mkOption {
|
||||
description = "NixOS test configuration.";
|
||||
type = types.deferredModule;
|
||||
default = {};
|
||||
};
|
||||
augments = mkOption {
|
||||
description = "Cluster augments (will be propagated).";
|
||||
type = types.deferredModule;
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
config.otherNodes = builtins.mapAttrs (const filterGroup) config.nodes;
|
||||
config.otherNodes = builtins.mapAttrs (_: filterGroup) config.nodes;
|
||||
}
|
||||
|
|
|
@ -2,48 +2,18 @@
|
|||
with lib;
|
||||
|
||||
let
|
||||
getHostConfigurations = hostName: svcName: svcConfig: let
|
||||
serviceConfigs =
|
||||
getHostConfigurations = svcConfig: hostName:
|
||||
lib.mapAttrsToList (groupName: _: svcConfig.nixos.${groupName})
|
||||
(lib.filterAttrs (_: lib.elem hostName) svcConfig.nodes);
|
||||
(lib.filterAttrs (_: v: lib.elem hostName v) svcConfig.nodes);
|
||||
|
||||
secretsConfig = let
|
||||
secrets = lib.filterAttrs (_: secret: lib.any (node: node == hostName) secret.nodes) svcConfig.secrets;
|
||||
in {
|
||||
age.secrets = lib.mapAttrs' (secretName: secretConfig: {
|
||||
name = "cluster-${svcName}-${secretName}";
|
||||
value = {
|
||||
inherit (secretConfig) path mode owner group;
|
||||
file = ../secrets/${svcName}-${secretName}${lib.optionalString (!secretConfig.shared) "-${hostName}"}.age;
|
||||
};
|
||||
}) secrets;
|
||||
|
||||
systemd.services = lib.mkMerge (lib.mapAttrsToList (secretName: secretConfig: lib.genAttrs secretConfig.services (systemdServiceName: {
|
||||
restartTriggers = [ "${../secrets/${svcName}-${secretName}${lib.optionalString (!secretConfig.shared) "-${hostName}"}.age}" ];
|
||||
})) secrets);
|
||||
};
|
||||
in serviceConfigs ++ [
|
||||
secretsConfig
|
||||
];
|
||||
|
||||
introspectionModule._module.args.cluster = {
|
||||
inherit (config) vars;
|
||||
inherit config;
|
||||
};
|
||||
getServiceConfigurations = svcConfig: getHostConfigurations svcConfig config.vars.hostName;
|
||||
in
|
||||
|
||||
{
|
||||
options.services = mkOption {
|
||||
description = "Cluster services.";
|
||||
type = with types; attrsOf (submodule ./service-module.nix);
|
||||
type = with types; attrsOf (submodule (import ./service-module.nix config.vars));
|
||||
default = {};
|
||||
};
|
||||
|
||||
config.out = {
|
||||
injectNixosConfigForServices = services: hostName: (lib.flatten (lib.mapAttrsToList (getHostConfigurations hostName) (lib.getAttrs services config.services))) ++ [
|
||||
introspectionModule
|
||||
];
|
||||
|
||||
injectNixosConfig = config.out.injectNixosConfigForServices (lib.attrNames config.services);
|
||||
};
|
||||
config.out.injectedNixosConfig = lib.flatten (lib.mapAttrsToList (_: getServiceConfigurations) config.services);
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
{ lib, name, ... }:
|
||||
|
||||
let
|
||||
serviceName = name;
|
||||
in
|
||||
|
||||
{
|
||||
options.secrets = lib.mkOption {
|
||||
type = lib.types.lazyAttrsOf (lib.types.submodule ({ config, name, ... }: {
|
||||
options = {
|
||||
shared = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether this secret should be the same on all nodes.";
|
||||
};
|
||||
|
||||
nodes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
generate = lib.mkOption {
|
||||
type = with lib.types; nullOr (functionTo str);
|
||||
description = "Command used to generate this secret.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/run/agenix/cluster-${serviceName}-${name}";
|
||||
};
|
||||
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0400";
|
||||
};
|
||||
|
||||
owner = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "root";
|
||||
};
|
||||
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "root";
|
||||
};
|
||||
|
||||
services = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
description = "Services to restart when this secret changes.";
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = {};
|
||||
};
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
options = {
|
||||
simulacrum = lib.mkOption {
|
||||
description = "Whether we are in the Simulacrum.";
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
testConfig = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
readOnly = true;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
{ depot, lib, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./catalog
|
||||
./simulacrum/checks.nix
|
||||
];
|
||||
|
||||
options.cluster = lib.mkOption {
|
||||
type = lib.types.raw;
|
||||
};
|
||||
|
||||
config.cluster = import ./. {
|
||||
inherit depot lib;
|
||||
};
|
||||
}
|
Binary file not shown.
|
@ -1,13 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 d3WGuA ZLjCSe5wrN6abvvRmQjE+VXtRr+avP/CLPD7djXNr0M
|
||||
g8i9ambJGL2Q+ZLB6c6MxV9BryAgX4qZctJ9qByJ4n8
|
||||
-> ssh-ed25519 P/nEqQ zSGcZuufOAnTkPr74ZjwyISdLlfxBxqgmyWivxq1/Uo
|
||||
gArusBfIfsZ5/gwMYHLzDHTbgVGWDttbi0IAhvclRO4
|
||||
-> ssh-ed25519 YIaSKQ J4Fy0VSjdMPRgzysQptIUKiRR0TAgu0q1BYhtIpGkWU
|
||||
kKzmF3OUbGU40d33R15nMraUDZiFRoz9Z00XjjSk9Jw
|
||||
-> ssh-ed25519 NO562A BNQV8JodzTiNs/V+rFQxcsrhKJ3nRIFtWk6VxHzCRio
|
||||
ZyauAdOrPbADSDdBQoB+39MB2r7Ro4d0XwZIjf2z9Jo
|
||||
-> ssh-ed25519 5/zT0w hdMuyOmNKTlMKPn4w9VQFVXZkJNm1XSPAZ/Zip5WW04
|
||||
wcnur+BRQPqKzpV3vl7pn1VIGRK3GxQEUaQIefrZuI4
|
||||
--- 5AdxXgFmDm2w012QjpJ3gqlbfvkPm8fkEJjm8kV18G0
|
||||
&Ãf§äIT¼-ÿY!ŒÍ,Vu<56>Â9õÿöBFrœŠ´½4–ù™BÕÝ/®UäH˜rþ¸ž #ƒˆç
ÄÝÕº†®UóQ¢ÿŽx$G{ÅŠMà2¡^/˜§¥Éè?12É¿t1©¿í¸&[}nêDAÛlýÑýˆ8uG®éZŽ×b¯èàîåd:@ÿ!Õþ
jîƒÚáÈNµrâlA³~
|
|
@ -1,11 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A GNlG6hVK8HfQKEWmMJwQ3qhmZOv9zSVWP6V7/5LVslc
|
||||
8i6bXmEmU8T9lApB0avHZublAUZiT3wHxmM5CUYxMo4
|
||||
-> ssh-ed25519 5/zT0w emP71+/eiA/GQ7EUekXlcXdQpL3yNVT3llw5hGNerXI
|
||||
gQ9QYqo3/V7AnQjK1MYOclsVX0B2Yg8QLqs5tTaYBFY
|
||||
-> ssh-ed25519 d3WGuA I2JHyhEO3xb9rniTY10FTujaWRDLAtChR7SQzbSw3nU
|
||||
AsNx/YxGHOTuon/ZEyu+s9zJ+OmELXFwcnRyu/XLlp4
|
||||
-> c[ehZ89-grease "^$r q6K1MR <4 '!b
|
||||
L/iRQ+g
|
||||
--- rH4ZWJU4EIRFC4ffXvBbnYS7Y/khTCu2Bu1SJHrOhcs
|
||||
?+1/}$qxë<78>ÕF<C395><w/ÑÑ<>a–y•$º”Žæ{V<>Ômñ¿±yšèKÝl¥¬ÔݦUÛGf(³9tiŒ™^%y¬‰cî`šµ…{»dÒý\T0†GÒXkç*lë;*‚,8AŒªa4'ŠQÍ«Íi¿™yĹåÌZè?¬Ó#ˆ]3}5iÁÉ|\ßØã®p©Ó½¨mÆ—xDæÌ= Àx”³ï#<23>Òƺ±Ÿêã<øWOÈE®v‡P!zÙÛÅÙsñLÃK
|
|
@ -1,11 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A ZP3yjzSbThB4TLzZTQ/numJtFThdJWSe/Q9BMgM1bVQ
|
||||
6OuYa43hZFSWdBISWTFkWNQGhmBbCdJgu6anJA3Zqh0
|
||||
-> ssh-ed25519 5/zT0w YmDcssGTmOawG7ajGWaprSO2wAiYJeTv4MPTmtRIvh0
|
||||
CJungpLxidWgJTe3vyMpryBpnIGotKCuC1KUlQhhYRs
|
||||
-> ssh-ed25519 TCgorQ sVuFjKWVxsFbmzn+jyiW8psOzTneUQGmCZbzJ7/XLRg
|
||||
1vRKXRWxsQ3BceWYbqxerbFz4IO5U0sF93G6dLGjzgk
|
||||
-> 1W#:-grease lN~;YPE^ YAa8 7s BEq(."'
|
||||
HKosiz64wAOAc4QckSNsMC6i4Bq6uxTBuPttJoaUOrJ9sWAL4K8aY8s
|
||||
--- ucYe/fF2tsm2+9HmTOnFLSt6VN3F+gNkXcxYDMWn1bY
|
||||
œó@p¢v/ÆPR[ÿ§»—€d®ºÂüÍå,mµ[/ú@pLË›ê}¯·‚*tÐïÞd|Ô-9!>l}"âöÈÏß›Ê/gÛôL‰Dã<44>“»hÛÝE©<>×»e’9+Ãdï<64>ÒyÀ<79>f½xn‹ŠFÂð;‡ókÌkNè÷æ™9lƒœÍeñŠß—tègŠ®ñ&•0Ë%Ø<1ÌÔôìHaйA™3$ÓôØ`Þ?b¹!¼Å’?<3F>EdÝ7ú¦¸Ÿw
|
Binary file not shown.
|
@ -1,12 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A ejtj6uy0U2VGcZ/TL5Izny/xZ2UyqajHJnPoelogenw
|
||||
JRcAb/P/QxYbVFwmnlqqnEQHOkMTepjgFHEmr7CyCYk
|
||||
-> ssh-ed25519 5/zT0w XyKUhRUuw3jVxEfImymDRv6Yds8IP885AGk1hRdZ4Rc
|
||||
5IrW0varzs44P+25vQe1+88oVNyXTnmzpytO8f2hX4s
|
||||
-> ssh-ed25519 YIaSKQ NKvJ4j+UQk6vdddf5YuGlIxlZPiUY1JdD611RQD2vlQ
|
||||
kkGF2wR5hoOpWM34/48I9EAM0kMI+VZSfOcal5ikGKY
|
||||
-> q9os-grease #6 _f|$T F4q*O ",M\
|
||||
sQRm7N0k+xtMD0a/lg7bif11LYTmo72t/+a3OfIwsXKUInz7Mij21ZMhkBS9NEpg
|
||||
ep7ywonuBh0Sb5ro2FNmcw9tm2p+qQ0/lLeDHCDBsi9rEcC0RD0uxHEJQbykxQ
|
||||
--- W71uGICSIj89KLvZDEVB02LtjNOa6vM9sEfUAk2VltI
|
||||
—å®!#jŠªúÝ<}Åêí‚ñ¿uü£ L•6 ©MãϼVé…`À<1C>rºE£ãÊØ,)>õ<>½ÏC¿Ÿ3¤‡Yèù€T^@s@š²%b³5îýG¼[msF»t+†Î©Ó•&kg*4[úøÊ.æ`Ѭ&ù• *Ÿ§×óÇøÄTvÄÕ³è 8μNöRó‰‚á¿Žsª7{›}Š]y "ŠÝ!E|í0°´Nýyiý'úsöÌî:<3A>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,9 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A jNUNRaIQC1DUBiacnWc3xjMUAxnAgiyJhRA74cof3Ec
|
||||
oZZq1AQ3F0nvrk7KpinLWgT3cIoCYZ5R1s0us69OI8E
|
||||
-> ssh-ed25519 5/zT0w FmoxaTg75/xaDmSOHL5Xs6QOr5rLG/sr5TmPMfkOqxw
|
||||
XXQrFxxt5GOzXgJoPY8U71NSYi/IWmL3QrenvOuQ43Q
|
||||
-> ssh-ed25519 YIaSKQ ++dqG+dr8ie+4sPW7L+eVkXvOVvM+/oBR722S2sQsSg
|
||||
879pmnhOtZ/MiMUwDlyujykQXNmCepI2FSU2QcvvkrA
|
||||
--- QcvlVdv2fYMKmT/aCpTjdmGJ+9KnUvZCZNtl7WhgCbw
|
||||
ï!jÊwŸ~×f%ÝJ>˜H ³·2ü9¬¥.VhþÅ·<C385>²«O!$iÄ<>ÝžÔ<>4\_̆J¸„šÀT>²J£‘î8Y´\ÁI³kÕýïk—tŒG(ÃAO¦#Ùš“#Ü(·LœøÍáô’0éh=[ÈRîw•Uj¸iVý2ÁÕ(ìÊBGgÔ„^L7fÍÊ«"zVµ<56>nË)ÑõË÷9½ï›<IäõúÃÍw1Š
|
Binary file not shown.
|
@ -1,11 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A eB0Rkoz721eI1UlyAhHWIrBnTEFoh6z3UL24EljaNzA
|
||||
dNsoal+y68XM4HXRyg1PUmrWilW1n3h78TmTcqHFEjc
|
||||
-> ssh-ed25519 5/zT0w SF16JelBZe0vZtzNEHiEfprJOqzoyxhTH3ldQdbo5wE
|
||||
95wJNWQEGqHj4Pknnk1RrgWPOqZOhlNsSvFTv8rfc08
|
||||
-> ssh-ed25519 YIaSKQ 68vS4sQGTDEaTVVxfs/xeTv379MQ3JE7iyLb1PbUuis
|
||||
1Bh53X0QFednXw74lQ+FbqNDkLBra9rx6nOybcD3FiQ
|
||||
--- HIcPirpTTtlUUGEemDXND/nwiWs4BEhM4rYX18mx71E
|
||||
箜_Ÿvw©\ˆ¯j2æVrK(™á2åÚ@ξ€;Y®AQAƒlMÛá[ÙÁW â—ßƀы<v#"ùóBŒ’O€™É^†©¦-ø¡+ž*m}›¦<>ª\“ª¡gÒ¹'kÓ2I~T¾w’M|¼jó¬˜+*BÖ%æ°xx‘‘€Ó¸õ{Ž O™;Fd„M“
|
||||
ÝPÙEB¡mãdBý¡¿¨[•¼í5Þf˜‰ü#öL- ¢³.4gŽ”FnÀ£q¬òv<C3B2>SV¹¥°÷¤êYÉkä·ï@ÓçlRn
|
||||
!¸'mÿSGìqóÊÖ“0dY1ïL!Jñðä üIÿw
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,12 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A 2Su0u03W90TKuR0wg1/dcokIbTzO5eATBmkFPyqfJG0
|
||||
IhBAWy5YYFFOqG9hc+AkVrKewTls84SFV9Kz/lOTV2U
|
||||
-> ssh-ed25519 5/zT0w YsyFCW1FsiGwiYJNYCITlLWk6Y5dR3K5v+gJqlsWQTg
|
||||
vtR1GCT2zrHNco/yPvMqQmlPyDja53lSRsO1DmnCSlo
|
||||
-> ssh-ed25519 P/nEqQ c8l4fOuvZn9V8+6vpRpGNGldEi4iA+5qVg1B+jArU1w
|
||||
zgS0urO8MZYo8cZq5Nz/R1x9cZ0vZgppJx6X5UecJ0s
|
||||
-> ?^lS,zDo-grease ^ZMN! V*+oK^9 GyJ[
|
||||
ZATLlHQ+kFjStI2ykQXq+KhvAR+XeW+POj6cJ59awzpMwq8JGbyaE1m5Cq8XA6u3
|
||||
xFE6
|
||||
--- 3JfCfv5CJYKGuvnbvoymhHhfkM99NkYMdGOL3Xicga8
|
||||
ðíçqÂ`ë#Ññ›„oq6üÄÑZÃõ˜<>Žh$wH"©läNøØ£¨IÛL3ä¯uY‹WŽœ<11>›T¹À*G<>GÂx¦nD2IÈ
ù«y+]ßT{gäð©<C3B0>ìÓinœÖÈçßEa¥ìœk¸zοP ”M…
|
Binary file not shown.
|
@ -1,13 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A a2L3S1TjwcnIzL1HIrpwJSOE8Eg6hpgqWsFdXM0F7D8
|
||||
+1ZQR5AVTdyc3G47yBc17qCHVfHAChc8xgo5/dPiCR0
|
||||
-> ssh-ed25519 5/zT0w VGbJ45NOODZu+z0ZoKibiTcKmHn1kSl9eE2Hl+nC2jA
|
||||
U8L3eqGtIqM+QLFUKuARoQ527BGCy3Unba4bAmspnRA
|
||||
-> ssh-ed25519 ygudAg 3arSWho21Yv0hZlO1xAdtWlqY/ZS4mYzbRzYicd061A
|
||||
gvMaXTeJ4zit0WxwMhX2nBCGQF4lJ7MxM9RBOEqSqA4
|
||||
-> }Y-grease
|
||||
MmJNND/ycD5UGgt80hcIhay+fUQKmI3thTj6u6rx3KrPMxsW6tDTd/sYJYmZBy4k
|
||||
oOuaS+ZeG3pM4Qy9tAFkKh4q2gbwx1mNbysy3GeQIQ
|
||||
--- 1k5+oNWHtRqQC5kr04aCxaKglweG5Nc9aKkXB1JnQfg
|
||||
<EFBFBD>€;ÌK…dòët¶¥0ŸMf›
SËWQý{E,ZäÔ¸ ᪗H›]ô-B¸îª,ˆôkaåþa§D5H¶¢.›ø
|
||||
†úéM \›æØ<C3A6>ÖêÀo¾°=ŒÕ®¤†dt‰x½(ÅÜf/¡ì<>;K§Û\?FLŸ
|
|
@ -1,12 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A 9Os91rQ4j/7/AyLMi2bngHI6aEln1Ij1rJh63xPjeQA
|
||||
cpmJRRIL+j9wHYbNSLzbXmpnZAc40+Og1vcWGyJMUkM
|
||||
-> ssh-ed25519 5/zT0w vajc7L8iJoodwX4oIgYyY/TAd0TWUNL2wl6wMyeNLi4
|
||||
QMe/bKmjUypzQHDdxoTkA/HDZypF+hByf99bahE73EU
|
||||
-> ssh-ed25519 FfIUuQ 7pwwH1jSFSNayCLUk8lir1UKOyunozrXHDA4vYqLQjo
|
||||
LsMeAhUGlZCNipaECYWE2oHPku8otsAFHV9GWIrtOg0
|
||||
-> s*r|b-grease Yu M>1\\ M!frVhk%
|
||||
jub17NjQWtGOyIFnF5na4ize1ifOjv6Nv6aqAa+ZJQHREUjPr2D7Rd2Fi6oyIRFo
|
||||
xWV0WDab7iWL
|
||||
--- n432BjqdbuNkeP9eW0TDEUyho88/RRdZ9TUKcWlVsok
|
||||
§â™n<EFBFBD>Ø
€1;U9ì(•koT™®·Ã«·–{}¤nÉêãgüÞnãíÝ€›B½LZj¯°¦zM'“T«ôÞZñÎëAø͸=Ô¾?T(<28>°Ìøæ ¶;<3B>êá“Òظ“ Ø´¦ÝÆ8¿<IÕE8BÊØN6žº$t®›
|
|
@ -1,15 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A nJxCLQ1WNjjDuk+x1SQK6QLxru9EHNnYC9BcTPbnejs
|
||||
FWW5PAcc5kV5H6ImN2tEKZs6wtDVF3Gb8aQdRBU5XPA
|
||||
-> ssh-ed25519 5/zT0w dBOD8ygBCyuH9+Cvg/GN+gCoQMP0StROLaWv7qPOeHc
|
||||
/Omz/8G4H6/wdb8TZOuI7u8fkwvMmKMp0EAsHxayCs0
|
||||
-> ssh-ed25519 d3WGuA t1kgNXHn1VYgbRC3oNDlKDlhlIbTsiS3mBWK9fjTO2g
|
||||
ryak3Nz8t+tJhhx4N3VS9sDV+dpijG0fgiJQOMiTgQc
|
||||
-> ssh-ed25519 YIaSKQ baRoE08c1ncfJOqvdo3KlJCyQcf2tlIl0gdCXqeHARg
|
||||
0sW4yu00A2uLxc3QWNVmlzoxV+6YuouIjkniIVvf/KI
|
||||
-> /Zshlxv%-grease +I-jlP0> *AP?2Ie l-,[cd}
|
||||
8E4RtrINNlnRc3nma3XFC22jfL2L571O4YRgSFQ125jNj9K3RwI1CTag+4Vpkoyn
|
||||
vx7pyk6csFlgpAfi6HjZQhy6SaLDLuivgg+5l7movEQQVFphKvZGXb1b3OXBPnw7
|
||||
|
||||
--- joBBM2uJbY81OgACM4fzeivBoYa+or+txZczR7FHQ28
|
||||
¿Œˆ.zê€k`ô/*ZÌIÕ…ý7àq•+”ã„ÚéÃæÈi<äÝ¢¹Ÿ£?p÷ú&ºÀ¦®„4{Gºƒf=NlͳêüñøNËÔ²`‰.ëIüº<ÐŒ‡#-<2D>\%UZ0Ü°êRU8u
|
Binary file not shown.
|
@ -1,15 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A QOWutzolRXJOngLYzsJTCdLb05XK5bxnIePA12iM/wM
|
||||
Fu9Q2pedDHGx5iIaZX31GMZmLlZlzfub64clEFHeJjA
|
||||
-> ssh-ed25519 5/zT0w WzQIh2tozhSF1p6QRVtMTz//wozc3SFRmmqv+GPPj0Y
|
||||
Ddf1we/UO19IstJe8XL7IU42ZjYCHbxdpwUSsBuNajs
|
||||
-> ssh-ed25519 d3WGuA 7qNzTg58PuEIKTXTCDuFwXUgyetdqReJq3MDOVynHRE
|
||||
cEevu4v+p1e6UNNSn/H1t56ut0xfM5Yok79OnGNtdHU
|
||||
-> ssh-ed25519 YIaSKQ YilDeBJ/ovJgeCbVANV5WIpYL3M17ktKH7Y/ziGcAAg
|
||||
gKrDxm4uNeR7BCbdepxdFYEL61gd2v1HYfiQ1mT02vY
|
||||
-> E~-grease 0;
|
||||
eqHwlFqZ/7Acq6anIU6kFg+rNZnL7el2YIU2DrCugZhKGBbxzqQMjRsq0A0F1z19
|
||||
C903hOEFwD7rGuo
|
||||
--- LyTH8wHncE/blz5X1Pi4A9r8iTmc/FXqofQ5JMNZFlg
|
||||
|
||||
G×íŽaðH¨(gF®1
Ö¯ýÛÏààJ0õÈÒÜ<C392>÷„€Rê;ZÌ3{;Q±p|Pžñ¶à6Ntªùë\pÝÐjgZ$»¤iyÛÅašk£C"U‚ÂQœY<C593>Uñ_Jx
|
Binary file not shown.
|
@ -1,15 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A o0R34LvRy19TseKFBi6iJVJSpuPWamIlL1UnX95+yVU
|
||||
9yjfDbf7J9q/L2Z8OkFlOcniYNfO9YJBdtNkLyQAzF4
|
||||
-> ssh-ed25519 5/zT0w AqcfbKIO1vE0TjkDvZOkCcMeRCz5ATfQZyoKecoDWQE
|
||||
beYLRlS/ZzteQ1MNhyGuIenuEHSRqkzYJRasomThBLU
|
||||
-> ssh-ed25519 FfIUuQ 9JeHQPQgOYSzA2cjR6jwisZYPRRYGQMSyOW49LVEo30
|
||||
TAd1otmjEo1CvOVX3gZe2rk6Rk/IEjF2DllpQ9+o6ak
|
||||
-> ssh-ed25519 d3WGuA 1RNgW2d+Nh66Li4hAsP7/3ATZsqHWL+9pwNqPej1ykw
|
||||
tN6e8kBNz4tknvWBrVoQ6nABbeglac6AVUlz32ZFMzA
|
||||
-> ssh-ed25519 P/nEqQ oHqCnNvCWIprBbVy0w2LdHBaJllbNwD+WbdAgIFrhww
|
||||
6Dgnv/HyYaFzAiRUySqYVjBPScLMK8M9Roo8UCqMGEM
|
||||
-> 4Vf|uj93-grease x5,kZ; -xgrjD8
|
||||
6Gw1SIrH9N0GVksuPQm46tD/7Ckn6vkG5Z9CDhu4hj4YO1X8ug
|
||||
--- eo6VHBS0rJXNXA4MFGBtVfJRQ7hNNJ7PMeMjvE1Hms8
|
||||
‘7<EFBFBD>¸ATº<>ÖŸ@OXåø?$ýÛ“XeÞ€<>{T|P†.3;EºÌ3mLÛã"o“´"õcèí—”#ü,"Í¥CtÒô½;¥ÂˆÒ³IÚR FOócD"âúK;¯{HÛÝ×ký™.d[sƒ·/¼R!à‹vk.®®W–‘°ñÿãºóç×<C3A7>ƒ6{Íþ
°òn<C3B2>È_M,¶½¬6o`Óô£?×@…ŠRX¨ñù´€()É<>UPëâ o9qÙFJûˆÆ’ÂúDkŒ#‚{D‰+[pÞu½õÌkúÊÎlMVêm+™ kDiŸ‡ó”l¤œûT=·ji6.ªUS¦–ö³óŽ\Æ-s€¦b«!eɳ‰:¿/—°NgŒSï—«¸
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,12 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A rE85lK37XeM803mXkugmTjfAp3LNqKy2yuGGbY4IOAM
|
||||
nDielwqyuaW72OKiUBgFPWK45aZhh768+MskQ5+vhUs
|
||||
-> ssh-ed25519 5/zT0w QxXHVLpk2qeXjO8c3a0cQ1oKk3fUn9+yIoHAK1hLYgQ
|
||||
d4s/F2ck8Z4AsCQReghxj+M0JjBYKoMpfU+K21AzwFg
|
||||
-> ssh-ed25519 TCgorQ lqg5aPJuj5NPEAgAaw52lwpQ++eWPxO4BITdpLKoZFg
|
||||
KS0kRB2K/+/+U2xfr2VE09XdjVvIflTweU93Vy7Okr8
|
||||
-> ?).-grease =%LA 5cVQvduw
|
||||
gs9TPdbaRJVf50LDiUdlg7Vr4LUfg2Kj2bPAbN2f2z4LKDnSbWHkJ6B3EfOMDxTN
|
||||
KmX8mGCi7QBGOfb1EY3h5cDgteBXiLN4aLh6kpCe0F3/DQ
|
||||
--- vLjmBMfCrvOuF1ww5UcHQAmBUo0LgIuJKcNEDlOCZ3g
|
||||
ß<EFBFBD>&„îd!¾Žqƒ©H<oÄžˆ×“5ç屃бÝ0&Ÿ<>ý⬻¯3~Ù´ð5Œ<35>§Ž÷Ñ¡“Ko)å6³üWÜ°‹
|
|
@ -1,12 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A BNIU8M5X5C4LSiie6S4zVraFQAsyGKAv7BwLVIXHiFM
|
||||
LLcXZ7tiTUnN+tJLwqqs1hLZ8usCDWqNVGr1lAn5OQs
|
||||
-> ssh-ed25519 5/zT0w H/SGf0oYVg/JCd07bicWL1LWQwExr0gbi+gV1j7Fy2M
|
||||
yHjguPtS8ItpY+pAR3lLVpXQxq7d3cuQYU5DHs2qjMc
|
||||
-> ssh-ed25519 P/nEqQ z1us0mTbOuLrkI7n6doG+JVFAuqwZvC0dEfdGauM+Fg
|
||||
P/tKnt5gZ66HAWR0/pqpmJMHp6hLbcjwE3BhO9NCkZY
|
||||
-> ((I-grease
|
||||
r66LwGiqumMp/NlcnLgOaxZ7cfQMBCr4Rq9aJdjUck69113hNf4orC/bGVCDhmdu
|
||||
s1cSHPVw1hys
|
||||
--- FxWSO98U5IDaGPs57hzO70gVN/ELN0/UxKKmIoxadks
|
||||
1ÊnûEHvóî_QíÄV†7¬Çæ•Ãܲé¶m¡z2'ÛÎ¥¯zWÚ)¼Ôç.»!ãi#¬TXÎT‰k[Fy
üˆEë!>á¨tÁ !‹‚*Ã
|
|
@ -1,12 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A pt5GwvS/t0gmDzyqNAgGa0bbg1fUUGCGtGOh8+kFYiM
|
||||
6t53t5IArPIMI5bZFc83S1OzBUb5HJ2BnYBoIks6tdU
|
||||
-> ssh-ed25519 5/zT0w iudKEK9eZVrY2cMJLExyL+hxX0X9ObK6Qru6hEkoCWY
|
||||
6ro91JnfQta7GhmCmPGlKK7AH/cHTHJWpWjvzf7DqJc
|
||||
-> ssh-ed25519 YIaSKQ XBhAf0RK8EFGSZU1alUn9ySRNa4fBN3rDWYKChUhRSc
|
||||
j52b16yy/LJIsBmwYF1Xc0yl71kx3fPswUdM6UrRDWg
|
||||
-> y)UX@-grease
|
||||
Wxs/GGbg7NiGP3KBWqBpunBdaJATNV2kITZ/Qcwx43NIlN//tV/C4721brrspcQ2
|
||||
UW14hhkN0mOn6xach2EnT71+vIPD3V/nc4bF
|
||||
--- bi81AZsfd3VWHugOdOcCf4HwjRpy5GeA8eSNZG+xelY
|
||||
™,Éêhݯý8Zþ7“ÿõ?<3F>ý‡rC„9^ Ê–ô›†—°EñddK¥èì×”BêJé¥C(dIãò$,ØM9øªÊZ|»É
|
|
@ -1,12 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A rdUnhWrA+Y4kORXQj3EXN2g0ocT+fCgWrWUzng4H2h0
|
||||
GpDWguEAJbruVKLsyg1UrajayaBTRqV4keuXOlUN4Dw
|
||||
-> ssh-ed25519 5/zT0w XhGp6wmN3PiZtYa7z5aTbTOesYsJ0ldJ9FAydDOBHjE
|
||||
KJKLJs5W9eVgyIxWvd7PGTCKF6+GKhfjCf/sS9oghKY
|
||||
-> ssh-ed25519 FfIUuQ H1Y6kiQrhvhXMFiZ5S1aKFV/squ7NaqduuaCk3T9dms
|
||||
8zn45DGkEH+vtkCjsnOlxeiZ+cEW/71bCYyj449axW0
|
||||
-> ZxL9h,-grease h7Z
|
||||
1EWFRzEk6ikC2LnZLuB4Z3n69SHr/AoxBZHjsha2K1DYKJspfb+NCrNVkC1A9F0t
|
||||
SllAo58gJsWkKdpyoKaE2nrV3SXoREMlcQvq/Z2X9WpR+A
|
||||
--- IxR5wj2vdqY/Tcsur39cFEICvfxmo2OrcdQFR4LLJRw
|
||||
[‚{{ËÈg=<•Ùi—“dJ9‹3ïEÔ;ƒ¨=ÙFeøgAXÞX\#¯¦”.<2E>%æÂðáó§=Ö<>^HT…c>)<29>q|¿{â7Íü
|
|
@ -1,60 +0,0 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
let
|
||||
lift = config;
|
||||
in
|
||||
|
||||
{
|
||||
nowhere.names = {
|
||||
"acme-v02.api.letsencrypt.org" = "stepCa";
|
||||
"api.buypass.com" = "stepCa";
|
||||
};
|
||||
|
||||
nodes.nowhere = { config, ... }: {
|
||||
links.stepCa.protocol = "https";
|
||||
|
||||
environment.etc.step-ca-password.text = "";
|
||||
|
||||
services = {
|
||||
step-ca = {
|
||||
enable = true;
|
||||
address = config.links.stepCa.ipv4;
|
||||
inherit (config.links.stepCa) port;
|
||||
intermediatePasswordFile = "/etc/step-ca-password";
|
||||
settings = {
|
||||
root = "${lift.nowhere.certs.ca}/ca.pem";
|
||||
crt = "${lift.nowhere.certs.intermediate}/cert.pem";
|
||||
key = "${lift.nowhere.certs.intermediate}/cert-key.pem";
|
||||
address = config.links.stepCa.tuple;
|
||||
db = {
|
||||
type = "badgerv2";
|
||||
dataSource = "/var/lib/step-ca/db";
|
||||
};
|
||||
authority.provisioners = [
|
||||
{
|
||||
type = "ACME";
|
||||
name = "snakeoil";
|
||||
challenges = [
|
||||
"dns-01"
|
||||
"http-01"
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
nginx.virtualHosts = {
|
||||
"acme-v02.api.letsencrypt.org".locations."/".extraConfig = ''
|
||||
rewrite /directory /acme/snakeoil/directory break;
|
||||
'';
|
||||
"api.buypass.com".locations."/".extraConfig = ''
|
||||
rewrite /acme/directory /acme/snakeoil/directory break;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
defaults.environment.etc."dummy-secrets/acmeDnsApiKey".text = "ACME_DNS_DIRECT_STATIC_KEY=simulacrum";
|
||||
defaults.environment.etc."dummy-secrets/acmeDnsDirectKey".text = "ACME_DNS_DIRECT_STATIC_KEY=simulacrum";
|
||||
defaults.environment.etc."dummy-secrets/acmeDnsDbCredentials".text = "PGPASSWORD=simulacrum";
|
||||
}
|
|
@ -1,82 +1,10 @@
|
|||
{ cluster, config, depot, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
authoritativeServers = map
|
||||
(node: cluster.config.hostLinks.${node}.dnsAuthoritative.tuple)
|
||||
cluster.config.services.dns.nodes.authoritative;
|
||||
|
||||
execScript = pkgs.writeShellScript "acme-dns-exec" ''
|
||||
action="$1"
|
||||
subdomain="''${2%.${depot.lib.meta.domain}.}"
|
||||
key="$3"
|
||||
umask 77
|
||||
source "$EXEC_ENV_FILE"
|
||||
headersFile="$(mktemp)"
|
||||
echo "X-Direct-Key: $ACME_DNS_DIRECT_STATIC_KEY" > "$headersFile"
|
||||
case "$action" in
|
||||
present)
|
||||
for i in {1..5}; do
|
||||
${pkgs.curl}/bin/curl -X POST -s -f -H "@$headersFile" \
|
||||
"${cluster.config.links.acmeDnsApi.url}/update" \
|
||||
--data '{"subdomain":"'"$subdomain"'","txt":"'"$key"'"}' && break
|
||||
sleep 5
|
||||
done
|
||||
;;
|
||||
esac
|
||||
'';
|
||||
in
|
||||
{ cluster, config, pkgs, ... }:
|
||||
|
||||
{
|
||||
age.secrets.acmeDnsApiKey = {
|
||||
file = ../dns/acme-dns-direct-key.age;
|
||||
owner = "acme";
|
||||
};
|
||||
age.secrets.pdns-api-key-acme = cluster.config.vars.pdns-api-key-secret // { owner = "acme"; };
|
||||
|
||||
security.acme.acceptTerms = true;
|
||||
security.acme.maxConcurrentRenewals = 0;
|
||||
security.acme.defaults = {
|
||||
email = depot.lib.meta.adminEmail;
|
||||
extraLegoFlags = lib.flatten [
|
||||
(map (x: [ "--dns.resolvers" x ]) authoritativeServers)
|
||||
"--dns-timeout" "30"
|
||||
];
|
||||
credentialsFile = pkgs.writeText "acme-exec-config" ''
|
||||
EXEC_PATH=${execScript}
|
||||
EXEC_ENV_FILE=${config.age.secrets.acmeDnsApiKey.path}
|
||||
security.acme.defaults.credentialsFile = pkgs.writeText "acme-pdns-credentials" ''
|
||||
PDNS_API_URL=${cluster.config.links.powerdns-api.url}
|
||||
PDNS_API_KEY_FILE=${config.age.secrets.pdns-api-key-acme.path}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services = lib.mapAttrs' (name: value: {
|
||||
name = "acme-${name}";
|
||||
value = {
|
||||
distributed.enable = value.dnsProvider != null;
|
||||
preStart = let
|
||||
serverList = lib.pipe authoritativeServers [
|
||||
(map (x: "@${x}"))
|
||||
(map (lib.replaceStrings [":53"] [""]))
|
||||
lib.escapeShellArgs
|
||||
];
|
||||
domainList = lib.pipe ([ value.domain ] ++ value.extraDomainNames) [
|
||||
(map (x: "${x}."))
|
||||
(map (lib.replaceStrings ["*"] ["x"]))
|
||||
lib.unique
|
||||
lib.escapeShellArgs
|
||||
];
|
||||
in ''
|
||||
echo Testing availability of authoritative DNS servers
|
||||
for i in {1..60}; do
|
||||
${pkgs.dig}/bin/dig +short ${serverList} ${domainList} >/dev/null && break
|
||||
echo Retry [$i/60]
|
||||
sleep 10
|
||||
done
|
||||
echo Available
|
||||
'';
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartMaxDelaySec = 30;
|
||||
RestartSteps = 5;
|
||||
RestartMode = "direct";
|
||||
};
|
||||
};
|
||||
}) config.security.acme.certs;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
services.acme-client = {
|
||||
nodes.client = [ "checkmate" "grail" "thunderskin" "VEGAS" "prophet" ];
|
||||
nodes.client = [ "VEGAS" "prophet" ];
|
||||
nixos.client = ./client.nix;
|
||||
simulacrum.augments = ./augment.nix;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
{ config, cluster, depot, lib, ... }:
|
||||
with depot.lib.nginx;
|
||||
{
|
||||
links = {
|
||||
atticNixStoreInternalRedirect.protocol = "http";
|
||||
garageNixStoreInternalRedirect.protocol = "http";
|
||||
};
|
||||
|
||||
security.acme.certs."cache.${depot.lib.meta.domain}" = {
|
||||
dnsProvider = "exec";
|
||||
webroot = lib.mkForce null;
|
||||
};
|
||||
|
||||
services.nginx.upstreams = {
|
||||
nar-serve.extraConfig = ''
|
||||
random;
|
||||
server ${config.links.nar-serve-self.tuple} fail_timeout=0;
|
||||
server ${config.links.nar-serve-nixos-org.tuple} fail_timeout=0;
|
||||
'';
|
||||
nix-store.servers = {
|
||||
"${config.links.garageNixStoreInternalRedirect.tuple}" = {
|
||||
fail_timeout = 0;
|
||||
};
|
||||
"${config.links.atticNixStoreInternalRedirect.tuple}" = {
|
||||
fail_timeout = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
services.nginx.appendHttpConfig = ''
|
||||
proxy_cache_path /var/cache/nginx/nixstore levels=1:2 keys_zone=nixstore:10m max_size=10g inactive=24h use_temp_path=off;
|
||||
'';
|
||||
services.nginx.virtualHosts = {
|
||||
"cache.${depot.lib.meta.domain}" = vhosts.basic // {
|
||||
locations = {
|
||||
"= /".return = "302 /404";
|
||||
"/" = {
|
||||
proxyPass = "http://nix-store";
|
||||
extraConfig = ''
|
||||
proxy_next_upstream error http_500 http_502 http_404;
|
||||
'';
|
||||
};
|
||||
"/nix/store" = {
|
||||
proxyPass = "http://nar-serve";
|
||||
extraConfig = ''
|
||||
proxy_next_upstream error http_500 http_404;
|
||||
'';
|
||||
};
|
||||
};
|
||||
extraConfig = ''
|
||||
proxy_cache nixstore;
|
||||
proxy_cache_use_stale error timeout http_500 http_502;
|
||||
proxy_cache_lock on;
|
||||
proxy_cache_key $request_uri;
|
||||
proxy_cache_valid 200 24h;
|
||||
'';
|
||||
};
|
||||
"garage-nix-store.internal.${depot.lib.meta.domain}" = {
|
||||
serverName = "127.0.0.1";
|
||||
listen = [
|
||||
{
|
||||
addr = "127.0.0.1";
|
||||
inherit (config.links.garageNixStoreInternalRedirect) port;
|
||||
}
|
||||
];
|
||||
locations."/" = {
|
||||
proxyPass = with cluster.config.links.garageWeb; "${protocol}://nix-store.${hostname}";
|
||||
recommendedProxySettings = false;
|
||||
extraConfig = ''
|
||||
proxy_set_header Host "nix-store.${cluster.config.links.garageWeb.hostname}";
|
||||
'';
|
||||
};
|
||||
};
|
||||
"attic-nix-store.internal.${depot.lib.meta.domain}" = {
|
||||
serverName = "127.0.0.1";
|
||||
listen = [
|
||||
{
|
||||
addr = "127.0.0.1";
|
||||
inherit (config.links.atticNixStoreInternalRedirect) port;
|
||||
}
|
||||
];
|
||||
locations."/" = {
|
||||
proxyPass = "https://cache-api.${depot.lib.meta.domain}/nix-store$request_uri";
|
||||
recommendedProxySettings = false;
|
||||
extraConfig = ''
|
||||
proxy_set_header Host "cache-api.${depot.lib.meta.domain}";
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
{ config, depot, ... }:
|
||||
|
||||
{
|
||||
services.attic = {
|
||||
nodes = {
|
||||
monolith = [ "VEGAS" "prophet" ];
|
||||
server = [ "VEGAS" "grail" "prophet" ];
|
||||
};
|
||||
nixos = {
|
||||
monolith = [
|
||||
./server.nix
|
||||
];
|
||||
server = [
|
||||
./server.nix
|
||||
./binary-cache.nix
|
||||
./nar-serve.nix
|
||||
];
|
||||
};
|
||||
meshLinks.server.attic.link.protocol = "http";
|
||||
secrets = let
|
||||
inherit (config.services.attic) nodes;
|
||||
in {
|
||||
serverToken = {
|
||||
nodes = nodes.server;
|
||||
};
|
||||
dbCredentials = {
|
||||
nodes = nodes.server;
|
||||
owner = "atticd";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
garage = config.lib.forService "attic" {
|
||||
keys.attic.locksmith = {
|
||||
nodes = config.services.attic.nodes.server;
|
||||
owner = "atticd";
|
||||
format = "aws";
|
||||
};
|
||||
buckets.attic = {
|
||||
allow.attic = [ "read" "write" ];
|
||||
};
|
||||
};
|
||||
|
||||
dns.records = let
|
||||
serverAddrs = map
|
||||
(node: depot.hours.${node}.interfaces.primary.addrPublic)
|
||||
config.services.attic.nodes.server;
|
||||
in config.lib.forService "attic" {
|
||||
cache.target = serverAddrs;
|
||||
};
|
||||
|
||||
ways = config.lib.forService "attic" {
|
||||
cache-api = {
|
||||
consulService = "atticd";
|
||||
extras.extraConfig = ''
|
||||
client_max_body_size 4G;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
{ cluster, config, depot, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (cluster.config.services.attic) secrets;
|
||||
|
||||
link = cluster.config.hostLinks.${config.networking.hostName}.attic;
|
||||
|
||||
isMonolith = lib.elem config.networking.hostName cluster.config.services.attic.nodes.monolith;
|
||||
in
|
||||
|
||||
{
|
||||
services.locksmith.waitForSecrets.atticd = [ "garage-attic" ];
|
||||
|
||||
services.atticd = {
|
||||
enable = true;
|
||||
package = depot.inputs.attic.packages.attic-server;
|
||||
|
||||
environmentFile = secrets.serverToken.path;
|
||||
mode = if isMonolith then "monolithic" else "api-server";
|
||||
|
||||
settings = {
|
||||
listen = link.tuple;
|
||||
|
||||
chunking = {
|
||||
nar-size-threshold = 0;
|
||||
min-size = 0;
|
||||
avg-size = 0;
|
||||
max-size = 0;
|
||||
};
|
||||
|
||||
compression.type = "none";
|
||||
|
||||
database.url = "postgresql://attic@${cluster.config.links.patroni-pg-access.tuple}/attic";
|
||||
|
||||
storage = {
|
||||
type = "s3";
|
||||
region = "us-east-1";
|
||||
endpoint = cluster.config.links.garageS3.url;
|
||||
bucket = "attic";
|
||||
};
|
||||
|
||||
garbage-collection = {
|
||||
interval = "2 weeks";
|
||||
default-retention-period = "3 months";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
users.atticd = {
|
||||
isSystemUser = true;
|
||||
group = "atticd";
|
||||
home = "/var/lib/atticd";
|
||||
createHome = true;
|
||||
};
|
||||
groups.atticd = {};
|
||||
};
|
||||
|
||||
systemd.services.atticd = {
|
||||
after = [ "postgresql.service" ];
|
||||
distributed = lib.mkIf isMonolith {
|
||||
enable = true;
|
||||
registerService = "atticd";
|
||||
};
|
||||
serviceConfig = {
|
||||
DynamicUser = lib.mkForce false;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ];
|
||||
SystemCallFilter = lib.mkAfter [ "@resources" ];
|
||||
};
|
||||
environment = {
|
||||
AWS_SHARED_CREDENTIALS_FILE = "/run/locksmith/garage-attic";
|
||||
PGPASSFILE = secrets.dbCredentials.path;
|
||||
};
|
||||
};
|
||||
|
||||
consul.services.atticd = {
|
||||
mode = if isMonolith then "manual" else "direct";
|
||||
definition = {
|
||||
name = "atticd";
|
||||
id = "atticd-${config.services.atticd.mode}";
|
||||
address = link.ipv4;
|
||||
inherit (link) port;
|
||||
checks = [
|
||||
{
|
||||
name = "Attic Server";
|
||||
id = "service:atticd:backend";
|
||||
interval = "5s";
|
||||
http = link.url;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{ depot, ... }:
|
||||
|
||||
{
|
||||
services.bitwarden = {
|
||||
nodes.host = [ "VEGAS" ];
|
||||
nixos.host = ./host.nix;
|
||||
};
|
||||
|
||||
dns.records.keychain.target = [ depot.hours.VEGAS.interfaces.primary.addrPublic ];
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{ cluster, depot, ... }:
|
||||
|
||||
{
|
||||
services.cachix-agent = {
|
||||
enable = true;
|
||||
credentialsFile = cluster.config.services.cachix-deploy-agent.secrets.token.path;
|
||||
package = depot.packages.cachix;
|
||||
};
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
services.cachix-deploy-agent = { config, ... }: {
|
||||
nodes.agent = [ "checkmate" "grail" "prophet" "VEGAS" "thunderskin" ];
|
||||
nixos.agent = ./agent.nix;
|
||||
secrets.token = {
|
||||
nodes = config.nodes.agent;
|
||||
shared = false;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{ depot, ... }:
|
||||
|
||||
{
|
||||
dns.records = let
|
||||
cdnShieldAddr = [ depot.hours.VEGAS.interfaces.primary.addrPublic ];
|
||||
in {
|
||||
"fonts-googleapis-com.cdn-shield".target = cdnShieldAddr;
|
||||
"fonts-gstatic-com.cdn-shield".target = cdnShieldAddr;
|
||||
"cdnjs-cloudflare-com.cdn-shield".target = cdnShieldAddr;
|
||||
"wttr-in.cdn-shield".target = cdnShieldAddr;
|
||||
};
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
services.certificates = {
|
||||
nodes = {
|
||||
internal-wildcard = [ "checkmate" "grail" "thunderskin" "VEGAS" "prophet" ];
|
||||
};
|
||||
nixos = {
|
||||
internal-wildcard = [
|
||||
./internal-wildcard.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{ config, lib, pkgs, depot, ... }:
|
||||
|
||||
let
|
||||
inherit (depot.lib.meta) domain;
|
||||
|
||||
extraGroups = [ "nginx" ]
|
||||
++ lib.optional config.services.kanidm.enableServer "kanidm";
|
||||
in
|
||||
|
||||
{
|
||||
security.acme.certs."internal.${domain}" = {
|
||||
domain = "*.internal.${domain}";
|
||||
extraDomainNames = [ "*.internal.${domain}" ];
|
||||
dnsProvider = "exec";
|
||||
group = "nginx";
|
||||
postRun = ''
|
||||
${pkgs.acl}/bin/setfacl -Rb .
|
||||
${lib.concatStringsSep "\n" (
|
||||
map (group: "${pkgs.acl}/bin/setfacl -Rm g:${group}:rX .") extraGroups
|
||||
)}
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
services.chant = {
|
||||
nodes.listener = config.services.consul.nodes.agent;
|
||||
nixos.listener = [
|
||||
./listener.nix
|
||||
];
|
||||
simulacrum.deps = [ "consul" ];
|
||||
};
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
consul = config.links.consulAgent;
|
||||
|
||||
validTargets = lib.pipe config.systemd.services [
|
||||
(lib.filterAttrs (name: value: value.chant.enable))
|
||||
lib.attrNames
|
||||
];
|
||||
|
||||
validTargetsJson = pkgs.writeText "chant-targets.json" (builtins.toJSON validTargets);
|
||||
|
||||
eventHandler = pkgs.writers.writePython3 "chant-listener-event-handler" {
|
||||
flakeIgnore = [ "E501" ];
|
||||
} ''
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import base64
|
||||
|
||||
validTargets = set()
|
||||
with open("${validTargetsJson}", "r") as f:
|
||||
validTargets = set(json.load(f))
|
||||
|
||||
events = json.load(sys.stdin)
|
||||
|
||||
cacheDir = os.getenv("CACHE_DIRECTORY", "/var/cache/chant")
|
||||
|
||||
indexFile = f"{cacheDir}/index"
|
||||
|
||||
oldIndex = "old-index"
|
||||
if os.path.isfile(indexFile):
|
||||
with open(indexFile, "r") as f:
|
||||
oldIndex = f.readline()
|
||||
|
||||
newIndex = os.getenv("CONSUL_INDEX", "no-index")
|
||||
|
||||
if oldIndex != newIndex:
|
||||
triggers = set()
|
||||
for event in events:
|
||||
if event["Name"].startswith("chant:"):
|
||||
target = event["Name"].removeprefix("chant:")
|
||||
if target not in validTargets:
|
||||
print(f"Skipping invalid target: {target}")
|
||||
continue
|
||||
with open(f"/run/chant/{target}", "wb") as f:
|
||||
if event["Payload"] is not None:
|
||||
f.write(base64.b64decode(event["Payload"]))
|
||||
triggers.add(target)
|
||||
|
||||
for trigger in triggers:
|
||||
subprocess.run(["${config.systemd.package}/bin/systemctl", "start", f"{trigger}.service"])
|
||||
|
||||
with open(indexFile, "w") as f:
|
||||
f.write(newIndex)
|
||||
'';
|
||||
in
|
||||
{
|
||||
systemd.services.chant-listener = {
|
||||
description = "Chant Listener";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requires = [ "consul-ready.service" ];
|
||||
after = [ "consul-ready.service" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${config.services.consul.package}/bin/consul watch --type=event ${eventHandler}";
|
||||
|
||||
RuntimeDirectory = "chant";
|
||||
RuntimeDirectoryMode = "0700";
|
||||
CacheDirectory = "chant";
|
||||
CacheDirectoryMode = "0700";
|
||||
|
||||
RestartSec = 60;
|
||||
Restart = "always";
|
||||
IPAddressDeny = [ "any" ];
|
||||
IPAddressAllow = [ consul.ipv4 ];
|
||||
};
|
||||
environment = {
|
||||
CONSUL_HTTP_ADDR = consul.tuple;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
{ config, cluster, depot, ... }:
|
||||
|
||||
let
|
||||
inherit (depot.lib.meta) domain;
|
||||
inherit (config.networking) hostName;
|
||||
inherit (cluster.config) hostLinks;
|
||||
cfg = cluster.config.services.consul;
|
||||
|
||||
hl = hostLinks.${hostName}.consul;
|
||||
in
|
||||
|
||||
{
|
||||
links.consulAgent.protocol = "http";
|
||||
|
||||
services.consul = {
|
||||
enable = true;
|
||||
webUi = true;
|
||||
package = depot.packages.consul;
|
||||
extraConfig = {
|
||||
datacenter = "eu-central";
|
||||
domain = "sd-magic.${domain}.";
|
||||
recursors = [ "127.0.0.1" cluster.config.links.dnsResolver.ipv4 ];
|
||||
server = true;
|
||||
node_name = config.networking.hostName;
|
||||
bind_addr = hl.ipv4;
|
||||
ports.serf_lan = hl.port;
|
||||
retry_join = map (hostName: hostLinks.${hostName}.consul.tuple) (cfg.otherNodes.agent hostName);
|
||||
bootstrap_expect = builtins.length cfg.nodes.agent;
|
||||
addresses.http = config.links.consulAgent.ipv4;
|
||||
ports.http = config.links.consulAgent.port;
|
||||
};
|
||||
};
|
||||
|
||||
services.grafana-agent.settings.integrations.consul_exporter = {
|
||||
enabled = true;
|
||||
instance = hostName;
|
||||
server = config.links.consulAgent.url;
|
||||
};
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.consul;
|
||||
in
|
||||
|
||||
{
|
||||
hostLinks = lib.genAttrs cfg.nodes.agent (hostName: {
|
||||
consul = {
|
||||
ipv4 = config.vars.mesh.${hostName}.meshIp;
|
||||
};
|
||||
});
|
||||
services.consul = {
|
||||
nodes = {
|
||||
agent = [ "checkmate" "grail" "thunderskin" "VEGAS" "prophet" ];
|
||||
ready = config.services.consul.nodes.agent;
|
||||
};
|
||||
nixos = {
|
||||
agent = [
|
||||
./agent.nix
|
||||
./remote-api.nix
|
||||
];
|
||||
ready = ./ready.nix;
|
||||
};
|
||||
simulacrum = {
|
||||
enable = true;
|
||||
deps = [ "wireguard" ];
|
||||
settings = ./test.nix;
|
||||
};
|
||||
};
|
||||
|
||||
dns.records."consul-remote.internal".consulService = "consul-remote";
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
consulReady = pkgs.writers.writeHaskellBin "consul-ready" {
|
||||
libraries = with pkgs.haskellPackages; [ aeson http-conduit watchdog ];
|
||||
} ''
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
import Control.Watchdog
|
||||
import Control.Exception
|
||||
import System.IO
|
||||
import Network.HTTP.Simple
|
||||
import Data.Aeson
|
||||
|
||||
flushLogger :: WatchdogLogger String
|
||||
flushLogger taskErr delay = do
|
||||
defaultLogger taskErr delay
|
||||
hFlush stdout
|
||||
|
||||
data ConsulHealth = ConsulHealth {
|
||||
healthy :: Bool
|
||||
}
|
||||
|
||||
instance FromJSON ConsulHealth where
|
||||
parseJSON (Object v) = ConsulHealth <$> (v .: "Healthy")
|
||||
|
||||
handleException ex = case ex of
|
||||
(SomeException _) -> return $ Left "Consul is not active"
|
||||
|
||||
main :: IO ()
|
||||
main = watchdog $ do
|
||||
setInitialDelay 300_000
|
||||
setMaximumDelay 30_000_000
|
||||
setLoggingAction flushLogger
|
||||
watch $ handle handleException $ do
|
||||
res <- httpJSON "${config.links.consulAgent.url}/v1/operator/autopilot/health"
|
||||
case getResponseBody res of
|
||||
ConsulHealth True -> return $ Right ()
|
||||
ConsulHealth False -> return $ Left "Consul is unhealthy"
|
||||
'';
|
||||
in
|
||||
|
||||
{
|
||||
systemd.services.consul-ready = {
|
||||
description = "Wait for Consul";
|
||||
requires = lib.mkIf config.services.consul.enable [ "consul.service" ];
|
||||
after = lib.mkIf config.services.consul.enable [ "consul.service" ];
|
||||
serviceConfig = {
|
||||
ExecStart = lib.getExe consulReady;
|
||||
DynamicUser = true;
|
||||
TimeoutStartSec = "5m";
|
||||
Type = "oneshot";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
{ config, depot, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (depot.lib.meta) domain;
|
||||
frontendDomain = "consul-remote.internal.${domain}";
|
||||
|
||||
inherit (config.reflection.interfaces.vstub) addr;
|
||||
in
|
||||
|
||||
{
|
||||
services.nginx.virtualHosts.${frontendDomain} = depot.lib.nginx.vhosts.proxy config.links.consulAgent.url // {
|
||||
listenAddresses = lib.singleton addr;
|
||||
enableACME = false;
|
||||
useACMEHost = "internal.${domain}";
|
||||
};
|
||||
|
||||
consul.services.consul-remote = {
|
||||
unit = "consul";
|
||||
mode = "external";
|
||||
definition = {
|
||||
name = "consul-remote";
|
||||
address = addr;
|
||||
port = 443;
|
||||
checks = [
|
||||
{
|
||||
name = "Frontend";
|
||||
id = "service:consul-remote:frontend";
|
||||
http = "https://${addr}/v1/status/leader";
|
||||
tls_server_name = frontendDomain;
|
||||
header.Host = lib.singleton frontendDomain;
|
||||
interval = "60s";
|
||||
}
|
||||
{
|
||||
name = "Backend";
|
||||
id = "service:consul-remote:backend";
|
||||
http = "${config.links.consulAgent.url}/v1/status/leader";
|
||||
interval = "30s";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
defaults.options.services.locksmith = lib.mkSinkUndeclaredOptions { };
|
||||
|
||||
testScript = ''
|
||||
import json
|
||||
|
||||
start_all()
|
||||
|
||||
with subtest("should form cluster"):
|
||||
nodes = [ n for n in machines if n != nowhere ]
|
||||
for machine in nodes:
|
||||
machine.succeed("systemctl start consul-ready.service")
|
||||
for machine in nodes:
|
||||
consulConfig = json.loads(machine.succeed("cat /etc/consul.json"))
|
||||
addr = consulConfig["addresses"]["http"]
|
||||
port = consulConfig["ports"]["http"]
|
||||
setEnv = f"CONSUL_HTTP_ADDR={addr}:{port}"
|
||||
memberList = machine.succeed(f"{setEnv} consul members --status=alive")
|
||||
for machine2 in nodes:
|
||||
assert machine2.name in memberList
|
||||
'';
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
garage = {
|
||||
buckets.content-delivery.web.enable = true;
|
||||
};
|
||||
|
||||
ways.cdn.bucket = "content-delivery";
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A 9n5IirzhNBIPRj9Gir+/yQhFH830sgfezsqY5Ulzz3o
|
||||
VItDDdgfTFcvSq/QpIqTHnfr1VHqfI6nPz+WWKYQjHw
|
||||
-> ssh-ed25519 5/zT0w MfBZrd8wJjoProwdPqsS9CZ9aYNTXgrYviFDwuchQVM
|
||||
8WKPYO+i1ZSkPYDrHVJ5Pclj2hEzqwAtf31Agzei444
|
||||
-> ssh-ed25519 TCgorQ 3QYtSx/2eiFp54W60F8FlERfHx+DUfnXXfugiXNPECg
|
||||
pBx3If3qihD//Aq8hDWCt+U1tiWoCLUDcg/RyVCD0D0
|
||||
-> ssh-ed25519 P/nEqQ NImm+vKuL50G2kdD2svmfkwsovmryCSyKyhnZ0duDDo
|
||||
U0PTKHiCj4SxomnJdgubo+3sStSE+YwvCnrRl7aAS1Q
|
||||
-> ssh-ed25519 FfIUuQ SRgJoBIoW71SiXuHqlnGqRG5AKUrnQy0ecwznGEGTHA
|
||||
a0IS3hjMln1tWEjo30A6gYtaV7TJSY4SZDarhahMoLk
|
||||
-> ssh-ed25519 d3WGuA 0qVNcrYe53Wo46zFJs6UZtX0dq7TUy72WGdGpLqB3yo
|
||||
jTHE9PfhRw5lbBlfznS+ThkSsab3ioearf91xyPBfdQ
|
||||
-> ssh-ed25519 YIaSKQ CCcBlAOms2aSkB6pws6tN+4Gf551idI9Zq0rokd0P1c
|
||||
/3oFp6hf+jggurbcuu0cXdDL8lr6m/LTHEeNgiJt2gg
|
||||
-> K&wn-grease ,Ewz Jc+dQQRp NU~.
|
||||
FvDOuTGNaLuCfDelsrRbthjuJT9fBZAQ+kz+7Stoc2wciXV1YpCcOYDHSF38OwRF
|
||||
X/pyjVudbJKS0Mphda6phw
|
||||
--- 3JFwCzeJsIgRkTpmy9MAvQ64BCZoa98kNKOuT57WI6Y
|
||||
&ÀO¿¹¸p ž-ÚP¶.+"<22>ðjÔG«
|
||||
ëÇÐs<>gnz[t
‘ØóÄD÷•RŽÄ½±šmÃl<!Çê6;³Ù÷<C399>†8{ vmvJJ;lR<6C>×[Yà3˜XPËÜ<C38B>ÈPCÿè¯&¦àåYû×2ÃǤxVúÈF{zäQ‹hnW*I$é;°Yc¨@7Ö-k4—À§xãͶx¿µ% RÝ<52>¤$z|»Ê“ñœ¹¯<C2B9>ëñ3
|
112
cluster/services/dns/admin.nix
Normal file
112
cluster/services/dns/admin.nix
Normal file
|
@ -0,0 +1,112 @@
|
|||
{ cluster, config, hosts, inputs, lib, pkgs, tools, ... }:
|
||||
|
||||
let
|
||||
inherit (tools.meta) domain;
|
||||
inherit (config.links) pdnsAdmin;
|
||||
inherit (cluster.config) vars;
|
||||
|
||||
pdns-api = cluster.config.links.powerdns-api;
|
||||
|
||||
dataDirUI = "/srv/storage/private/powerdns-admin";
|
||||
|
||||
translateConfig = withQuotes: cfg: let
|
||||
pythonValue = val: if lib.isString val then "'${val}'"
|
||||
else if lib.isAttrs val && val ? file then "[(f.read().strip('\\n'), f.close()) for f in [open('${val.file}')]][0][0]"
|
||||
else if lib.isAttrs val && val ? env then "__import__('os').getenv('${val.env}')"
|
||||
else if lib.isBool val then (if val then "True" else "False")
|
||||
else if lib.isInt val then toString val
|
||||
else throw "translateConfig: unsupported value type";
|
||||
|
||||
quote = str: if withQuotes then pythonValue str else str;
|
||||
|
||||
configList = lib.mapAttrsToList (n: v: "${n}=${quote v}") cfg;
|
||||
in lib.concatStringsSep "\n" configList;
|
||||
|
||||
login = x: "https://login.${domain}/auth/realms/master/protocol/openid-connect/${x}";
|
||||
in {
|
||||
age.secrets = {
|
||||
pdns-admin-oidc-secrets = {
|
||||
file = ./pdns-admin-oidc-secrets.age;
|
||||
mode = "0400";
|
||||
};
|
||||
pdns-admin-salt = {
|
||||
file = ./pdns-admin-salt.age;
|
||||
mode = "0400";
|
||||
owner = "powerdnsadmin";
|
||||
group = "powerdnsadmin";
|
||||
};
|
||||
pdns-admin-secret = {
|
||||
file = ./pdns-admin-secret.age;
|
||||
mode = "0400";
|
||||
owner = "powerdnsadmin";
|
||||
group = "powerdnsadmin";
|
||||
};
|
||||
pdns-api-key = vars.pdns-api-key-secret // { owner = "powerdnsadmin"; };
|
||||
};
|
||||
|
||||
links.pdnsAdmin.protocol = "http";
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [ 53 ];
|
||||
allowedUDPPorts = [ 53 ];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${dataDirUI}' 0700 powerdnsadmin powerdnsadmin - -"
|
||||
];
|
||||
|
||||
services.powerdns = {
|
||||
enable = true;
|
||||
extraConfig = translateConfig false {
|
||||
api = "yes";
|
||||
webserver-allow-from = "127.0.0.1, ${vars.meshNet.cidr}";
|
||||
webserver-address = pdns-api.ipv4;
|
||||
webserver-port = pdns-api.portStr;
|
||||
api-key = "$scrypt$ln=14,p=1,r=8$ZRgztsniH1y+F7P/RkXq/w==$QTil5kbJPzygpeQRI2jgo5vK6fGol9YS/NVR95cmWRs=";
|
||||
};
|
||||
};
|
||||
|
||||
services.powerdns-admin = {
|
||||
enable = true;
|
||||
secretKeyFile = config.age.secrets.pdns-admin-secret.path;
|
||||
saltFile = config.age.secrets.pdns-admin-salt.path;
|
||||
extraArgs = [ "-b" pdnsAdmin.tuple ];
|
||||
config = translateConfig true {
|
||||
SQLALCHEMY_DATABASE_URI = "sqlite:///${dataDirUI}/pda.db";
|
||||
PDNS_VERSION = pkgs.pdns.version;
|
||||
PDNS_API_URL = pdns-api.url;
|
||||
PDNS_API_KEY.file = config.age.secrets.pdns-api-key.path;
|
||||
|
||||
SIGNUP_ENABLED = false;
|
||||
OIDC_OAUTH_ENABLED = true;
|
||||
OIDC_OAUTH_KEY = "net.privatevoid.dnsadmin1";
|
||||
OIDC_OAUTH_SECRET.env = "OIDC_OAUTH_SECRET";
|
||||
OIDC_OAUTH_SCOPE = "openid profile email roles";
|
||||
|
||||
OIDC_OAUTH_API_URL = login "";
|
||||
OIDC_OAUTH_TOKEN_URL = login "token";
|
||||
OIDC_OAUTH_AUTHORIZE_URL = login "auth";
|
||||
OIDC_OAUTH_LOGOUT_URL = login "logout";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.powerdns-admin.serviceConfig = {
|
||||
BindPaths = [
|
||||
dataDirUI
|
||||
config.age.secrets.pdns-api-key.path
|
||||
];
|
||||
EnvironmentFile = config.age.secrets.pdns-admin-oidc-secrets.path;
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."dnsadmin.${domain}" = lib.recursiveUpdate
|
||||
(tools.nginx.vhosts.proxy pdnsAdmin.url)
|
||||
# backend sends really big headers for some reason
|
||||
# increase buffer size accordingly
|
||||
{
|
||||
locations."/".extraConfig = ''
|
||||
proxy_busy_buffers_size 512k;
|
||||
proxy_buffers 4 512k;
|
||||
proxy_buffer_size 256k;
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -1,50 +1,20 @@
|
|||
{ cluster, config, depot, lib, pkgs, ... }:
|
||||
{ cluster, config, hosts, inputs, lib, pkgs, tools, ... }:
|
||||
|
||||
let
|
||||
inherit (config.reflection) interfaces;
|
||||
inherit (depot.lib.meta) domain;
|
||||
inherit (config.networking) hostName;
|
||||
inherit (hosts.${config.networking.hostName}) interfaces;
|
||||
|
||||
link = cluster.config.hostLinks.${hostName}.dnsAuthoritative;
|
||||
patroni = cluster.config.links.patroni-pg-access;
|
||||
inherit (cluster.config.hostLinks.${hostName}) acmeDnsApi;
|
||||
|
||||
otherDnsServers = lib.pipe (cluster.config.services.dns.otherNodes.authoritative hostName) [
|
||||
(map (node: cluster.config.hostLinks.${node}.dnsAuthoritative.tuple))
|
||||
(lib.concatStringsSep " ")
|
||||
];
|
||||
|
||||
recordsList = lib.mapAttrsToList (lib.const lib.id) cluster.config.dns.records;
|
||||
recordsPartitioned = lib.partition (record: record.rewrite.target == null) recordsList;
|
||||
|
||||
staticRecords = let
|
||||
escape = type: {
|
||||
TXT = builtins.toJSON;
|
||||
}.${type} or lib.id;
|
||||
|
||||
recordName = record: {
|
||||
"@" = "${record.root}.";
|
||||
}.${record.name} or "${record.name}.${record.root}.";
|
||||
in lib.flatten (
|
||||
map (record: map (target: "${recordName record} ${record.type} ${escape record.type target}") record.target) recordsPartitioned.right
|
||||
);
|
||||
|
||||
rewrites = map (record: let
|
||||
maybeEscapeRegex = str: if record.rewrite.type == "regex" then "${lib.escapeRegex str}$" else str;
|
||||
in "rewrite stop name ${record.rewrite.type} ${record.name}${maybeEscapeRegex ".${record.root}."} ${record.rewrite.target}. answer auto") recordsPartitioned.wrong;
|
||||
|
||||
rewriteConf = pkgs.writeText "coredns-rewrites.conf" ''
|
||||
rewrite stop type DS DS
|
||||
rewrite stop type NS NS
|
||||
rewrite stop type SOA SOA
|
||||
${lib.concatStringsSep "\n" rewrites}
|
||||
'';
|
||||
translateConfig = cfg: let
|
||||
configList = lib.mapAttrsToList (n: v: "${n}=${v}") cfg;
|
||||
in lib.concatStringsSep "\n" configList;
|
||||
in {
|
||||
links.localAuthoritativeDNS = {};
|
||||
|
||||
age.secrets = {
|
||||
acmeDnsDirectKey = {
|
||||
file = ./acme-dns-direct-key.age;
|
||||
pdns-db-credentials = {
|
||||
file = ./pdns-db-credentials.age;
|
||||
mode = "0400";
|
||||
owner = "pdns";
|
||||
group = "pdns";
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -53,92 +23,17 @@ in {
|
|||
allowedUDPPorts = [ 53 ];
|
||||
};
|
||||
|
||||
services.acme-dns = {
|
||||
services.powerdns = {
|
||||
enable = true;
|
||||
package = depot.packages.acme-dns;
|
||||
settings = {
|
||||
general = {
|
||||
listen = config.links.localAuthoritativeDNS.tuple;
|
||||
inherit domain;
|
||||
nsadmin = "hostmaster.${domain}";
|
||||
nsname = "eu1.ns.${domain}";
|
||||
records = staticRecords;
|
||||
};
|
||||
api = {
|
||||
ip = acmeDnsApi.ipv4;
|
||||
inherit (acmeDnsApi) port;
|
||||
};
|
||||
database = {
|
||||
engine = "postgres";
|
||||
connection = "postgres://acmedns@${patroni.tuple}/acmedns?sslmode=disable";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.locksmith.waitForSecrets.acme-dns = [
|
||||
"patroni-acmedns"
|
||||
];
|
||||
|
||||
systemd.services.acme-dns.serviceConfig.EnvironmentFile = with config.age.secrets; [
|
||||
"/run/locksmith/patroni-acmedns"
|
||||
acmeDnsDirectKey.path
|
||||
];
|
||||
|
||||
services.coredns = {
|
||||
enable = true;
|
||||
config = ''
|
||||
.:${link.portStr} {
|
||||
bind ${interfaces.primary.addr}
|
||||
chaos "Private Void DNS" info@privatevoid.net
|
||||
cache {
|
||||
success 4000 86400
|
||||
denial 0
|
||||
prefetch 3
|
||||
serve_stale 86400s verify
|
||||
}
|
||||
template ANY DS {
|
||||
rcode NXDOMAIN
|
||||
}
|
||||
forward service.eu-central.sd-magic.${domain} 127.0.0.1:8600
|
||||
forward addr.eu-central.sd-magic.${domain} 127.0.0.1:8600
|
||||
import ${rewriteConf}
|
||||
forward . ${config.links.localAuthoritativeDNS.tuple} ${otherDnsServers} {
|
||||
policy sequential
|
||||
}
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.coredns = {
|
||||
after = [ "acme-dns.service" ];
|
||||
serviceConfig = {
|
||||
MemoryMax = "200M";
|
||||
MemorySwapMax = "50M";
|
||||
CPUQuota = "25%";
|
||||
};
|
||||
};
|
||||
|
||||
consul.services = {
|
||||
authoritative-dns = {
|
||||
unit = "acme-dns";
|
||||
definition = {
|
||||
name = "authoritative-dns-backend";
|
||||
address = config.links.localAuthoritativeDNS.ipv4;
|
||||
port = config.links.localAuthoritativeDNS.port;
|
||||
checks = lib.singleton {
|
||||
interval = "60s";
|
||||
tcp = config.links.localAuthoritativeDNS.tuple;
|
||||
};
|
||||
};
|
||||
};
|
||||
acme-dns.definition = {
|
||||
name = "acme-dns";
|
||||
address = acmeDnsApi.ipv4;
|
||||
port = acmeDnsApi.port;
|
||||
checks = lib.singleton {
|
||||
interval = "60s";
|
||||
http = "${acmeDnsApi.url}/health";
|
||||
};
|
||||
extraConfig = translateConfig {
|
||||
launch = "gpgsql";
|
||||
local-address = interfaces.primary.addr;
|
||||
gpgsql-host = patroni.ipv4;
|
||||
gpgsql-port = patroni.portStr;
|
||||
gpgsql-dbname = "powerdns";
|
||||
gpgsql-user = "powerdns";
|
||||
gpgsql-extra-connection-parameters = "passfile=${config.age.secrets.pdns-db-credentials.path}";
|
||||
version-string = "Private Void DNS";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
{ cluster, lib, ... }:
|
||||
|
||||
let
|
||||
recursors = lib.pipe (cluster.config.services.dns.nodes.coredns) [
|
||||
(map (node: cluster.config.hostLinks.${node}.dnsResolverBackend.ipv4))
|
||||
];
|
||||
in
|
||||
{ cluster, ... }:
|
||||
|
||||
{
|
||||
networking.nameservers = [ cluster.config.links.dnsResolver.ipv4 ] ++ recursors;
|
||||
networking.nameservers = [ cluster.config.links.dnsResolver.ipv4 ];
|
||||
}
|
||||
|
|
|
@ -1,23 +1,10 @@
|
|||
{ cluster, config, depot, lib, ... }:
|
||||
{ config, hosts, inputs, pkgs, tools, ... }:
|
||||
|
||||
let
|
||||
inherit (config.reflection) interfaces;
|
||||
inherit (depot.lib.meta) domain;
|
||||
inherit (config.networking) hostName;
|
||||
|
||||
link = cluster.config.hostLinks.${hostName}.dnsResolver;
|
||||
backend = cluster.config.hostLinks.${hostName}.dnsResolverBackend;
|
||||
|
||||
otherRecursors = lib.pipe (cluster.config.services.dns.otherNodes.coredns hostName) [
|
||||
(map (node: cluster.config.hostLinks.${node}.dnsResolverBackend.tuple))
|
||||
(lib.concatStringsSep " ")
|
||||
];
|
||||
|
||||
authoritativeServers = map
|
||||
(node: cluster.config.hostLinks.${node}.dnsAuthoritative.tuple)
|
||||
cluster.config.services.dns.nodes.authoritative;
|
||||
|
||||
inherit (depot.packages) stevenblack-hosts;
|
||||
inherit (hosts.${config.networking.hostName}) interfaces;
|
||||
inherit (tools.meta) domain;
|
||||
inherit (config.links) localRecursor;
|
||||
inherit (inputs.self.packages.${pkgs.system}) stevenblack-hosts;
|
||||
dot = config.security.acme.certs."securedns.${domain}";
|
||||
in
|
||||
|
||||
|
@ -30,22 +17,16 @@ in
|
|||
};
|
||||
|
||||
systemd.services.coredns = {
|
||||
after = (lib.optional (interfaces ? vstub) "network-addresses-vstub.service") ++ [
|
||||
"acme-selfsigned-securedns.${domain}.service"
|
||||
];
|
||||
before = [ "acme-securedns.${domain}.service" ];
|
||||
wants = [ "acme-finished-securedns.${domain}.target" ];
|
||||
serviceConfig = {
|
||||
LoadCredential = [
|
||||
after = [ "network-addresses-vstub.service" ];
|
||||
serviceConfig.LoadCredential = [
|
||||
"dot-cert.pem:${dot.directory}/fullchain.pem"
|
||||
"dot-key.pem:${dot.directory}/key.pem"
|
||||
];
|
||||
ExecReload = lib.mkForce [];
|
||||
};
|
||||
};
|
||||
|
||||
security.acme.certs."securedns.${domain}" = {
|
||||
dnsProvider = "exec";
|
||||
group = "nginx";
|
||||
webroot = "/var/lib/acme/acme-challenge";
|
||||
# using a different ACME provider because Android Private DNS is fucky
|
||||
server = "https://api.buypass.com/acme/directory";
|
||||
reloadServices = [
|
||||
|
@ -56,29 +37,23 @@ in
|
|||
services.coredns = {
|
||||
enable = true;
|
||||
config = ''
|
||||
(localresolver) {
|
||||
. {
|
||||
bind ${interfaces.vstub.addr}
|
||||
bind 127.0.0.1
|
||||
hosts ${stevenblack-hosts} {
|
||||
fallthrough
|
||||
}
|
||||
chaos "Private Void DNS" info@privatevoid.net
|
||||
forward hyprspace. 127.43.104.80:11355
|
||||
forward ${domain}. ${lib.concatStringsSep " " authoritativeServers} {
|
||||
policy random
|
||||
}
|
||||
forward . ${backend.tuple} ${otherRecursors} {
|
||||
policy sequential
|
||||
}
|
||||
}
|
||||
.:${link.portStr} {
|
||||
${lib.optionalString (interfaces ? vstub) "bind ${interfaces.vstub.addr}"}
|
||||
bind 127.0.0.1
|
||||
bind ${link.ipv4}
|
||||
import localresolver
|
||||
forward . ${localRecursor.tuple}
|
||||
}
|
||||
tls://.:853 {
|
||||
bind ${interfaces.primary.addr}
|
||||
tls {$CREDENTIALS_DIRECTORY}/dot-cert.pem {$CREDENTIALS_DIRECTORY}/dot-key.pem
|
||||
import localresolver
|
||||
hosts ${stevenblack-hosts} {
|
||||
fallthrough
|
||||
}
|
||||
chaos "Private Void DNS" info@privatevoid.net
|
||||
forward . ${localRecursor.tuple}
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
@ -88,27 +63,12 @@ in
|
|||
dnssecValidation = "process";
|
||||
forwardZones = {
|
||||
# optimize queries against our own domain
|
||||
"${domain}" = lib.concatStringsSep ";" authoritativeServers;
|
||||
"${domain}" = interfaces.primary.addr;
|
||||
};
|
||||
dns = {
|
||||
inherit (backend) port;
|
||||
address = backend.ipv4;
|
||||
allowFrom = [ "127.0.0.1" cluster.config.vars.meshNet.cidr "10.100.3.0/24" ];
|
||||
};
|
||||
};
|
||||
|
||||
consul.services.securedns = {
|
||||
unit = "coredns";
|
||||
mode = "external";
|
||||
definition = rec {
|
||||
name = "securedns";
|
||||
address = interfaces.primary.addrPublic;
|
||||
port = 853;
|
||||
checks = lib.singleton {
|
||||
name = "SecureDNS";
|
||||
tcp = "${address}:${toString port}";
|
||||
interval = "30s";
|
||||
};
|
||||
inherit (localRecursor) port;
|
||||
address = localRecursor.ipv4;
|
||||
allowFrom = [ "127.0.0.1" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,80 +1,38 @@
|
|||
{ config, depot, lib, ... }:
|
||||
{ config, ... }:
|
||||
|
||||
let
|
||||
inherit (depot) hours;
|
||||
cfg = config.services.dns;
|
||||
inherit (config.vars) hosts;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./options.nix
|
||||
./nodes.nix
|
||||
./ns-records.nix
|
||||
];
|
||||
|
||||
vars.pdns-api-key-secret = {
|
||||
file = ./pdns-api-key.age;
|
||||
mode = "0400";
|
||||
};
|
||||
links = {
|
||||
dnsResolver = {
|
||||
ipv4 = hours.VEGAS.interfaces.vstub.addr;
|
||||
ipv4 = hosts.VEGAS.interfaces.vstub.addr;
|
||||
port = 53;
|
||||
};
|
||||
acmeDnsApi = {
|
||||
hostname = "acme-dns-challenge.internal.${depot.lib.meta.domain}";
|
||||
powerdns-api = {
|
||||
ipv4 = config.vars.mesh.VEGAS.meshIp;
|
||||
protocol = "http";
|
||||
};
|
||||
};
|
||||
hostLinks = lib.mkMerge [
|
||||
(lib.genAttrs cfg.nodes.authoritative (node: {
|
||||
dnsAuthoritative = {
|
||||
ipv4 = hours.${node}.interfaces.primary.addrPublic;
|
||||
port = 53;
|
||||
};
|
||||
acmeDnsApi = {
|
||||
ipv4 = config.vars.mesh.${node}.meshIp;
|
||||
inherit (config.links.acmeDnsApi) port;
|
||||
protocol = "http";
|
||||
};
|
||||
}))
|
||||
(lib.genAttrs cfg.nodes.coredns (node: {
|
||||
dnsResolver = {
|
||||
ipv4 = config.vars.mesh.${node}.meshIp;
|
||||
port = 53;
|
||||
};
|
||||
}))
|
||||
(lib.genAttrs cfg.nodes.coredns (node: {
|
||||
dnsResolverBackend = {
|
||||
ipv4 = config.vars.mesh.${node}.meshIp;
|
||||
};
|
||||
}))
|
||||
];
|
||||
services.dns = {
|
||||
nodes = {
|
||||
authoritative = [ "VEGAS" "checkmate" "prophet" ];
|
||||
coredns = [ "checkmate" "VEGAS" ];
|
||||
client = [ "checkmate" "grail" "thunderskin" "VEGAS" "prophet" ];
|
||||
master = [ "VEGAS" ];
|
||||
slave = [ "prophet" ];
|
||||
coredns = [ "VEGAS" ];
|
||||
client = [ "VEGAS" "prophet" ];
|
||||
};
|
||||
nixos = {
|
||||
authoritative = ./authoritative.nix;
|
||||
master = [
|
||||
./authoritative.nix
|
||||
./admin.nix
|
||||
];
|
||||
slave = ./authoritative.nix;
|
||||
coredns = ./coredns.nix;
|
||||
client = ./client.nix;
|
||||
};
|
||||
simulacrum = {
|
||||
enable = true;
|
||||
deps = [ "consul" "acme-client" "patroni" ];
|
||||
settings = ./test.nix;
|
||||
};
|
||||
};
|
||||
|
||||
patroni = {
|
||||
databases.acmedns = {};
|
||||
users.acmedns = {
|
||||
locksmith = {
|
||||
nodes = config.services.dns.nodes.authoritative;
|
||||
format = "envFile";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dns.records = {
|
||||
securedns.consulService = "securedns";
|
||||
"acme-dns-challenge.internal".consulService = "acme-dns";
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
{ depot, lib, ... }:
|
||||
|
||||
{
|
||||
dns.records = lib.mapAttrs' (name: hour: {
|
||||
name = lib.toLower "${name}.${hour.enterprise.subdomain}";
|
||||
value = {
|
||||
type = "A";
|
||||
target = [ hour.interfaces.primary.addrPublic ];
|
||||
};
|
||||
}) depot.gods.fromLight;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
{ config, depot, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.dns;
|
||||
|
||||
nsNodes = lib.imap1 (idx: node: {
|
||||
name = "eu${toString idx}.ns";
|
||||
value = {
|
||||
type = "A";
|
||||
target = [ depot.hours.${node}.interfaces.primary.addrPublic ];
|
||||
};
|
||||
}) cfg.nodes.authoritative;
|
||||
in
|
||||
|
||||
{
|
||||
dns.records = lib.mkMerge [
|
||||
(lib.listToAttrs nsNodes)
|
||||
{
|
||||
NS = {
|
||||
name = "@";
|
||||
type = "NS";
|
||||
target = map (ns: "${ns.name}.${depot.lib.meta.domain}.") nsNodes;
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
{ depot, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
recordType = types.submodule ({ config, name, ... }: {
|
||||
options = {
|
||||
root = mkOption {
|
||||
type = types.str;
|
||||
default = depot.lib.meta.domain;
|
||||
};
|
||||
consulServicesRoot = mkOption {
|
||||
type = types.str;
|
||||
default = "service.eu-central.sd-magic.${depot.lib.meta.domain}";
|
||||
};
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
type = mkOption {
|
||||
type = types.enum [ "A" "CNAME" "AAAA" "NS" "MX" "SOA" "TXT" ];
|
||||
default = "A";
|
||||
};
|
||||
target = mkOption {
|
||||
type = with types; listOf str;
|
||||
};
|
||||
ttl = mkOption {
|
||||
type = types.ints.unsigned;
|
||||
default = 86400;
|
||||
};
|
||||
|
||||
consulService = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
};
|
||||
rewrite = {
|
||||
target = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
};
|
||||
type = mkOption {
|
||||
type = types.enum [ "exact" "substring" "prefix" "suffix" "regex" ];
|
||||
default = "exact";
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
rewrite.target = mkIf (config.consulService != null) "${config.consulService}.${config.consulServicesRoot}";
|
||||
};
|
||||
});
|
||||
in
|
||||
|
||||
{
|
||||
options.dns = {
|
||||
records = mkOption {
|
||||
type = with types; attrsOf recordType;
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
}
|
BIN
cluster/services/dns/pdns-admin-oidc-secrets.age
Normal file
BIN
cluster/services/dns/pdns-admin-oidc-secrets.age
Normal file
Binary file not shown.
11
cluster/services/dns/pdns-admin-salt.age
Normal file
11
cluster/services/dns/pdns-admin-salt.age
Normal file
|
@ -0,0 +1,11 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A d/YNanH/cHoFLPp8WcCXHh/LQLRwaUa95JiRLbgb8RI
|
||||
UPEHpnHHTU6dGKi2MbApEspcpt1lFtFZ4XJjShL7OoE
|
||||
-> ssh-ed25519 5/zT0w Rv9ZS5P2Eca3npPLR7yym/XTRSDfVmgRwH1pAGR79T8
|
||||
4A/KXc2wxxokfDAwWYf0ZTUEzQ8ldkC+zRNZY3KjBTs
|
||||
-> ssh-ed25519 d3WGuA 2R0kaVjuhU3wT9pjj214zkEaHYNSlMxf9Z+MfBssHwY
|
||||
EU5LWk6xfohWM/3sAqYtUvFmRgIPxOLXHnlqbsQ3+ok
|
||||
-> -|(-grease W=cc~ O2q5
|
||||
FZzh/ZwDS2EqvVZ9NErmUwCMN72op1Qy
|
||||
--- Ducan3ugRJC3dmWLr7+FKok+WmInOgOzW0ccYeqAFAQ
|
||||
Ì•ãÆ*Q. SC<53>ûf¹‰*`5<>„ÑÖw"~ÍxwÜ*–ã\‹êÙ"²ÅtŒ '’É0ï™<C3AF>L£ï
|
12
cluster/services/dns/pdns-admin-secret.age
Normal file
12
cluster/services/dns/pdns-admin-secret.age
Normal file
|
@ -0,0 +1,12 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A hUR+UdHnpazhANM8DKToI5Th3lv1aAuxZ1IQKvCOv34
|
||||
PvsiSym8YdleDULLnWuTs1x08KO3EmAg/AAjulgrgqE
|
||||
-> ssh-ed25519 5/zT0w qMXS2xLOLv/+l6brG11i+3FwHdrhlmxZBNtBiU9hu2g
|
||||
BlFYPvH4mFJRMHTlHwnBdJb6QcugylwZuT5bgSKcQa0
|
||||
-> ssh-ed25519 d3WGuA k2fRQ3+HyZP+bb/gkVKQqUmbITJLPm9tGp67DbRfiCs
|
||||
RX9CACfYpYKvSqyfXjvEokTGsp4+ECQBD8i1ehD5xRg
|
||||
-> IB@F$9G-grease
|
||||
cXRgUVdIPGEjft1CJA
|
||||
--- si16Det/GwF7GLHLt0ha8v4rFFeJXyhEylIiqzZVAK8
|
||||
Ö°å¤pÐǺ#ê4^©—
~u
UuçaòQ´™Bâj˜(N)qÃ<"¤%ì’,V9û5ZÔh§#W«[»ò¶”"Mÿ&”îäøÖýá+%Œ«„SQ€B÷Þ›ÕÀèÕyàÜî<aéó]P‚$´Ä±B¨½qQÑÉQ‡M‰TËt°
|
||||
·s¹mÿ~qW–Ö«çêõÜ×Ì=.Q“"ù”–Þø¶ÏnqRk<52>=ÏcÿçüßÃqv¢¾>#ŠÏ«²tïwq,÷ »3YyIq}Ê“ì>sgíz™ûs±Þ ¸Æ†FÄPê|ÍüÅ¡=ùÃþ~KQR,DZuÐ+ÕºZGHëa=‹©;ÀõC.ÏuVShÅ$Và€AË9Ð=
?•¢
|
14
cluster/services/dns/pdns-api-key.age
Normal file
14
cluster/services/dns/pdns-api-key.age
Normal file
|
@ -0,0 +1,14 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A 1bb8hrQQunFIVGRUkg9QrAKof4tAAV8D1KRavicuaFY
|
||||
y6X5aiD66+jBrfA9u3k3UrWQSEAd9yadLa0j2LgS9GY
|
||||
-> ssh-ed25519 5/zT0w Ke3Ymtxr2Kvp4piYChcTfBxNnBO4MlYPxVMNqC6z5X4
|
||||
CHzxDO2LIUHNjCr4O3hkA/9MSD6LXD3YUQVhtCKp3So
|
||||
-> ssh-ed25519 d3WGuA qPlzxEKYYe7QYBhsQuO/MI5IikwqjXVZutQL23aq2WQ
|
||||
esQyjMbfaWYmtg9CAcHY/WJnsPZiZPITufIQ5TkGzEc
|
||||
-> ssh-ed25519 6YMlxg YajTOEPRSF/oMzlU4D0DC7DiBL22OxLBNTUN77wMwRY
|
||||
RNZFOE7KV6UffoFUTcdnfrgz6wptvkvkZfR7H3GKs0g
|
||||
-> hI!f;r}-grease |@)
|
||||
PVxV1yo85W0ceDE3nuYqdfYoRIQkwyp1VK1WIkxQjBMIs5kIx0Fw+WjdHT6wv/HH
|
||||
Xh+qLqlgBfehoy28KZ52gsNRlDHN
|
||||
--- +rOxrlTfSf4+mvHe8WZB1+xT0Jo/YxzEbEbIyOpLOS0
|
||||
4´éì ·†É. ß„þÞÄp—ûZ¨¶ïO)üB<C3BC>~¬ac£ˆTv†•Ú{_¥…UÛÜÕÄÖ<C384>Ä8=U¶P¹î0¾a"A¾TV…<56>„Ür1Ôèn¦<6E>NÜé{
7¦À¥HòdþÏîu×çïðÚHgæe{{0d²ëM‹Xy›ÐóÁ™^<5E>4RWoÌm¾çzßç–-)Jö9!7.ÏoуҊ®9ôe+a´&+‰‡]z}κ4R×çš‚)ñÕŠ[<5B>ž4¯ûAÖÀ±Oã3D/¯’X¿Ä@7ýÁTRÀÒµç6Âã€]‹/ ß½$ê_Hàj‰w°ØØK(ý<>ᓦ9<C2A6>npŽ;HõhbÞÚ2€›Ç<1F>Yw;W‘æÄ0<C384>‚I6¿¾
|
15
cluster/services/dns/pdns-db-credentials.age
Normal file
15
cluster/services/dns/pdns-db-credentials.age
Normal file
|
@ -0,0 +1,15 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 NO562A /puQyDz+IepPqd8PTnD+YUp1obCh4gO1/7GGKaUAcRk
|
||||
ydUvPiZyl9OZ3gVlhDxPkFf4PiPmQQtFoL9PK4pcA5Y
|
||||
-> ssh-ed25519 5/zT0w MUEtkOeKzCBDBjoJjfkRnTmwl3pa0Qo07EKOpQp8RTQ
|
||||
rUEyKc2BiQIxoPxTpoDPDfeJie2cfocwyT/VmIQcqL8
|
||||
-> ssh-ed25519 d3WGuA 0pGUj+2YukJEgbIZ++iUKwfCp8yGiU2iUuyk0NFlYSg
|
||||
/O3DY0EO3odBXvQNM14PdkrRmCLHOIvOIdgTsq5jBsQ
|
||||
-> ssh-ed25519 6YMlxg Us2lOVb1NCc8yTqT/pe8UEb6qNePw7onN7J2z0mkb0s
|
||||
WWtatJOs1krHZxmFddhmDOEfMNvGKBPFdAUnekKaccE
|
||||
-> H7;a-ODO-grease <iF?
|
||||
3z2aQlpJcCIhfAyzypdzUXfLZGGhYM7Rgp0uVOZYH7V1p/jspvJhrFyWKpcjfblj
|
||||
qyjXRWASpQzl4jT5g1oUFB8u8A
|
||||
--- lVvMLw/p/T07fWOaz/+IGn7uQgfb9BwXE8Wnh4F3JzA
|
||||
äZÅî™å<¼ÔÝõŒ?EIÂãHÂç+*ïéM¹»œjk?/BÎâ4`ù7S^’t<>‡¨ÄU®`Soý1xjÒ®õz7D>ÄßñÀÜNy¾˜!`X@)ÿݱ?n6ú§(<ªãƒÀ|bG¨M…àµBd¼3Ú§—$±i#qåhðgP…j¸Þ¼äÒ…VT¯¸ :¡Å½Óõ¬và{Ïùâªò“‡ÿ´û?4Ï&0<>5O%D|u{<7B>ç*“2û¾„B!à'(.0Ö<EFBFBD>Ó°ßKg‚±Ñ¼ƒ"&LKq•<DÛòóÔqµ[;x²Pœ»]<5D>}ŽkÛëලXdfú›Óïy_ï
|
||||
LÌC?†éhH(c¥I•¥<E280A2>½ÁyZÏ¿ãÐ|}#Å:w
vìÇ5A¾ ÍzÈËrí÷:¥½› ÷
|
|
@ -1,35 +0,0 @@
|
|||
{ cluster, ... }:
|
||||
|
||||
let
|
||||
inherit (cluster._module.specialArgs.depot.lib.meta) domain;
|
||||
in
|
||||
{
|
||||
nodes.nowhere = { pkgs, ... }: {
|
||||
passthru = cluster;
|
||||
environment.systemPackages = [
|
||||
pkgs.knot-dns
|
||||
pkgs.openssl
|
||||
];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import json
|
||||
nodeNames = json.loads('${builtins.toJSON cluster.config.services.dns.nodes.authoritative}')
|
||||
dotNames = json.loads('${builtins.toJSON cluster.config.services.dns.nodes.coredns}')
|
||||
nodes = [ n for n in machines if n.name in nodeNames ]
|
||||
dotServers = [ n for n in machines if n.name in dotNames ]
|
||||
|
||||
start_all()
|
||||
|
||||
with subtest("should allow external name resolution for own domain"):
|
||||
for node in nodes:
|
||||
node.wait_for_unit("coredns.service")
|
||||
nowhere.wait_until_succeeds("[[ $(kdig +short securedns.${domain} | wc -l) -ne 0 ]]", timeout=60)
|
||||
nowhere.fail("[[ $(kdig +short example.com | wc -l) -ne 0 ]]")
|
||||
|
||||
with subtest("should have valid certificate on DoT endpoint"):
|
||||
for node in dotServers:
|
||||
node.wait_for_unit("acme-finished-securedns.${domain}.target")
|
||||
nowhere.wait_until_succeeds("openssl </dev/null s_client -connect securedns.${domain}:853 -verify_return_error -strict -verify_hostname securedns.${domain}", timeout=60)
|
||||
'';
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{ depot, ... }:
|
||||
|
||||
{
|
||||
services.fbi = {
|
||||
nodes.host = [ "VEGAS" ];
|
||||
nixos.host = ./host.nix;
|
||||
};
|
||||
|
||||
dns.records = let
|
||||
fbiAddr = [ depot.hours.VEGAS.interfaces.primary.addrPublic ];
|
||||
in {
|
||||
fbi-index.target = fbiAddr;
|
||||
fbi-requests.target = fbiAddr;
|
||||
radarr.target = fbiAddr;
|
||||
sonarr.target = fbiAddr;
|
||||
};
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
{ lib, ... }:
|
||||
|
||||
{
|
||||
ways.registry.static = { depot, pkgs, ... }: pkgs.writeTextDir "flake-registry.json" (let
|
||||
flakes = {
|
||||
depot = {
|
||||
type = "tarball";
|
||||
url = "https://forge.${depot.lib.meta.domain}/${depot.lib.meta.domain}/depot/archive/master.tar.gz";
|
||||
};
|
||||
depot-nixpkgs = {
|
||||
type = "github";
|
||||
owner = "NixOS";
|
||||
repo = "nixpkgs";
|
||||
inherit (depot.inputs.nixpkgs.sourceInfo) rev narHash lastModified;
|
||||
};
|
||||
blank = {
|
||||
type = "github";
|
||||
owner = "divnix";
|
||||
repo = "blank";
|
||||
inherit (depot.inputs.blank.sourceInfo) rev narHash lastModified;
|
||||
};
|
||||
} // import ./extra-flakes.nix;
|
||||
in builtins.toJSON {
|
||||
version = 2;
|
||||
flakes = lib.pipe flakes [
|
||||
(lib.attrsToList)
|
||||
(map (f: {
|
||||
from = {
|
||||
type = "indirect";
|
||||
id = f.name;
|
||||
};
|
||||
to = f.value;
|
||||
}))
|
||||
];
|
||||
});
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
let
|
||||
github = owner: repo: {
|
||||
type = "github";
|
||||
inherit owner repo;
|
||||
};
|
||||
in {
|
||||
# own
|
||||
hyprspace = github "hyprspace" "hyprspace";
|
||||
ai = github "nixified-ai" "flake";
|
||||
nix-super = github "privatevoid-net" "nix-super";
|
||||
nixpak = github "nixpak" "nixpak";
|
||||
|
||||
# other
|
||||
nix = github "NixOS" "nix";
|
||||
flake-parts = github "hercules-ci" "flake-parts";
|
||||
home-manager = github "nix-community" "home-manager";
|
||||
dream2nix = github "nix-community" "dream2nix";
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
{ config, depot, ... }:
|
||||
|
||||
{
|
||||
services.forge = {
|
||||
nodes.server = [ "VEGAS" ];
|
||||
nixos.server = ./server.nix;
|
||||
meshLinks.server.forge.link.protocol = "http";
|
||||
secrets = with config.services.forge.nodes; {
|
||||
oidcSecret = {
|
||||
nodes = server;
|
||||
owner = "forgejo";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
ways = let
|
||||
host = builtins.head config.services.forge.nodes.server;
|
||||
in config.lib.forService "forge" {
|
||||
forge.target = config.hostLinks.${host}.forge.url;
|
||||
};
|
||||
|
||||
patroni = config.lib.forService "forge" {
|
||||
databases.forge = {};
|
||||
users.forge.locksmith = {
|
||||
nodes = config.services.forge.nodes.server;
|
||||
format = "raw";
|
||||
};
|
||||
};
|
||||
|
||||
garage = config.lib.forService "forge" {
|
||||
keys.forgejo.locksmith.nodes = config.services.forge.nodes.server;
|
||||
buckets.forgejo.allow.forgejo = [ "read" "write" ];
|
||||
};
|
||||
|
||||
monitoring.blackbox.targets.forge = config.lib.forService "forge" {
|
||||
address = "https://forge.${depot.lib.meta.domain}/api/v1/version";
|
||||
module = "https2xx";
|
||||
};
|
||||
|
||||
dns.records = config.lib.forService "forge" {
|
||||
"ssh.forge".target = map
|
||||
(node: depot.hours.${node}.interfaces.primary.addrPublic)
|
||||
config.services.forge.nodes.server;
|
||||
};
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
{ cluster, config, depot, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (depot.lib.meta) domain;
|
||||
inherit (cluster.config.services.forge) secrets;
|
||||
|
||||
patroni = cluster.config.links.patroni-pg-access;
|
||||
|
||||
host = "forge.${domain}";
|
||||
|
||||
link = cluster.config.hostLinks.${config.networking.hostName}.forge;
|
||||
|
||||
exe = lib.getExe config.services.forgejo.package;
|
||||
in
|
||||
|
||||
{
|
||||
system.ascensions.forgejo = {
|
||||
requiredBy = [ "forgejo.service" ];
|
||||
before = [ "forgejo.service" ];
|
||||
incantations = i: [
|
||||
(i.execShell "chown -R forgejo:forgejo /srv/storage/private/forge")
|
||||
(i.execShell "rm -rf /srv/storage/private/forge/data/{attachments,lfs,avatars,repo-avatars,repo-archive,packages,actions_log,actions_artifacts}")
|
||||
];
|
||||
};
|
||||
|
||||
services.locksmith.waitForSecrets.forgejo = [
|
||||
"garage-forgejo-id"
|
||||
"garage-forgejo-secret"
|
||||
"patroni-forge"
|
||||
];
|
||||
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
package = depot.packages.forgejo;
|
||||
stateDir = "/srv/storage/private/forge";
|
||||
database = {
|
||||
createDatabase = false;
|
||||
type = "postgres";
|
||||
host = patroni.ipv4;
|
||||
inherit (patroni) port;
|
||||
name = "forge";
|
||||
user = "forge";
|
||||
passwordFile = "/run/locksmith/patroni-forge";
|
||||
};
|
||||
settings = {
|
||||
DEFAULT = {
|
||||
APP_NAME = "The Forge";
|
||||
};
|
||||
server = {
|
||||
DOMAIN = host;
|
||||
ROOT_URL = "https://${host}/";
|
||||
PROTOCOL = link.protocol;
|
||||
HTTP_ADDR = link.ipv4;
|
||||
HTTP_PORT = link.port;
|
||||
SSH_DOMAIN = "ssh.${host}";
|
||||
};
|
||||
oauth2_client = {
|
||||
REGISTER_EMAIL_CONFIRM = false;
|
||||
ENABLE_AUTO_REGISTRATION = true;
|
||||
ACCOUNT_LINKING = "auto";
|
||||
UPDATE_AVATAR = true;
|
||||
};
|
||||
session.COOKIE_SECURE = true;
|
||||
service = {
|
||||
DISABLE_REGISTRATION = false;
|
||||
ALLOW_ONLY_INTERNAL_REGISTRATION = false;
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
|
||||
};
|
||||
storage = {
|
||||
STORAGE_TYPE = "minio";
|
||||
MINIO_ENDPOINT = cluster.config.links.garageS3.hostname;
|
||||
MINIO_BUCKET = "forgejo";
|
||||
MINIO_USE_SSL = true;
|
||||
MINIO_BUCKET_LOOKUP = "path";
|
||||
};
|
||||
log."logger.xorm.MODE" = "";
|
||||
# enabling this will leak secrets to the log
|
||||
database.LOG_SQL = false;
|
||||
};
|
||||
secrets = {
|
||||
storage = {
|
||||
MINIO_ACCESS_KEY_ID = "/run/locksmith/garage-forgejo-id";
|
||||
MINIO_SECRET_ACCESS_KEY = "/run/locksmith/garage-forgejo-secret";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.forgejo.preStart = let
|
||||
providerName = "PrivateVoidAccount";
|
||||
args = lib.escapeShellArgs [
|
||||
"--name" providerName
|
||||
"--provider" "openidConnect"
|
||||
"--key" "net.privatevoid.forge1"
|
||||
"--auto-discover-url" "https://login.${domain}/auth/realms/master/.well-known/openid-configuration"
|
||||
"--group-claim-name" "groups"
|
||||
"--admin-group" "/forge_admins@${domain}"
|
||||
];
|
||||
in lib.mkAfter /*bash*/ ''
|
||||
providerId="$(${exe} admin auth list | ${pkgs.gnugrep}/bin/grep -w '${providerName}' | cut -f1)"
|
||||
if [[ -z "$providerId" ]]; then
|
||||
FORGEJO_ADMIN_OAUTH2_SECRET="$(< ${secrets.oidcSecret.path})" ${exe} admin auth add-oauth ${args}
|
||||
else
|
||||
FORGEJO_ADMIN_OAUTH2_SECRET="$(< ${secrets.oidcSecret.path})" ${exe} admin auth update-oauth --id "$providerId" ${args}
|
||||
fi
|
||||
'';
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue