feat: moar nix

This commit is contained in:
nullishamy 2025-06-18 21:41:53 +01:00
parent 7e3bf4d6f3
commit 2025eb74a4
Signed by: amy
SSH key fingerprint: SHA256:WmV0uk6WgAQvDJlM8Ld4mFPHZo02CLXXP5VkwQ5xtyk
23 changed files with 1408 additions and 15 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
.direnv/ .direnv/
.terraform*
tf/

View file

@ -1,3 +1,27 @@
# nixlab # nixlab
The Nix part of my homelab, just messing around The Nix part of my homelab, just messing around
## boostrap
TODO: Figure out if addNetworking is required still
- Set `addNetworking` to `false` in `config/tf.nix`
- `just deploy`
- Wait for the infra to build
- Start the containers and log in to them
- Redeploy infra with `addNetworking`
- Put the `host-key.pub` into `~/.ssh/authorized_keys` on each host
- (This wil fail) `just deploy`
- Fetch the SSH keys of the hosts and put them in `secrets/secrets.nix`
- Remember to add it to `systems`!
- Rekey the secrets with `just rekey`
- `just deploy`
- Wait for the deploy
- Set the IPs properly in `flake.nix` (using the TF config as a ref)
- Reboot the nodes
## ref
- https://nixos.wiki/wiki/Proxmox_Linux_Container
- https://github.com/ryantm/agenix
- https://registry.terraform.io/providers/Telmate/proxmox/latest/docs/resources/lxc
- https://terranix.org/documentation/flakes.html
- https://discourse.nixos.org/t/qbittorrent-headless-service-module/32397

140
config/nix01.nix Normal file
View file

@ -0,0 +1,140 @@
{ modulesPath, pkgs, config, lib, ... }:
{
imports = [
# Include the default lxd configuration.
"${modulesPath}/virtualisation/proxmox-lxc.nix"
# Include the container-specific autogenerated configuration.
./lxd.nix
./services/sharkey.nix
];
networking = {
dhcpcd.enable = false;
useDHCP = false;
useHostResolvConf = false;
firewall.enable = false;
nameservers = ["192.168.1.155" "1.1.1.1"];
};
environment.systemPackages = with pkgs; [
git
curl
vim
];
services.postgresql = {
enable = true;
enableTCPIP = true;
ensureDatabases = [ "authentik" "blog" "forgejo" "infisical" "sharkey" ];
ensureUsers = [
{
name = "authentik";
ensureDBOwnership = true;
}
{
name = "blog";
ensureDBOwnership = true;
}
{
name = "forgejo";
ensureDBOwnership = true;
}
{
name = "infisical";
ensureDBOwnership = true;
}
{
name = "sharkey";
ensureDBOwnership = true;
}
];
authentication = pkgs.lib.mkOverride 10 ''
# type database DBuser auth-method
local all all trust
# ipv4
host all all 127.0.0.1/32 trust
# ipv6
host all all ::1/128 trust
# LAN
host all all 192.168.0.0/16 trust
'';
};
services.calibre-server = {
enable = true;
auth = {
enable = true;
userDb = "/var/lib/calibre-server/.config/calibre/server-users.sqlite";
};
libraries = [
"/var/lib/calibre-server"
];
};
services.pgadmin = {
enable = true;
initialEmail = "hello@amyerskine.me";
initialPasswordFile = config.age.secrets."pgadmin.password".path;
};
services.nginx.enable = true;
services.nginx.virtualHosts."pg.nix01.cluster" = {
locations."/" = {
proxyPass = "http://127.0.0.1:5050";
proxyWebsockets = true;
};
};
services.nginx.virtualHosts."sharkey.nix01.cluster" = {
locations."/" = {
proxyPass = "http://127.0.0.1:3001";
proxyWebsockets = true;
};
};
services.nginx.virtualHosts."calibre.nix01.cluster" = {
locations."/" = {
proxyPass = "http://127.0.0.1:8080";
proxyWebsockets = true;
};
};
services.sharkey = {
enable = true;
domain = "fedi.amy.mov";
package = (pkgs.callPackage ./services/sharkey-pkg.nix {});
database = {
passwordFile = config.age.secrets."sharkey.dbpass".path;
};
redis = {
passwordFile = config.age.secrets."sharkey.redispass".path;
};
meilisearch = {
createLocally = false;
};
settings = {
id = "aidx";
port = 3001;
maxNoteLength = 8192;
maxFileSize = 1024 * 1024 * 1024;
proxyRemoteFiles = true;
# at the suggestion of Sharkey maintainers,
# this allows the server to run multiple workers
# and without this (and postgres tuning), the instance runs slowly
clusterLimit = 3;
signToActivityPubGet = true;
CheckActivityPubGetSigned = false;
};
};
system.stateVersion = "24.11"; # Did you read the comment?
}

90
config/nix02.nix Normal file
View file

@ -0,0 +1,90 @@
{ modulesPath, pkgs, unstable, config, ... }:
{
imports = [
# Include the default lxd configuration.
"${modulesPath}/virtualisation/proxmox-lxc.nix"
# Include the container-specific autogenerated configuration.
./lxd.nix
];
networking = {
dhcpcd.enable = false;
useDHCP = false;
useHostResolvConf = false;
firewall.enable = false;
nameservers = ["192.168.1.155" "1.1.1.1"];
};
environment.systemPackages = with pkgs; [
git
curl
vim
];
services.nginx = {
enable = true;
};
services.nginx.virtualHosts."forgejo.nix02.cluster" = {
locations."/" = {
proxyPass = "http://127.0.0.1:8312";
proxyWebsockets = true;
};
};
services.nginx.virtualHosts."forge.amy.mov" = {
locations."/" = {
proxyPass = "http://127.0.0.1:8312";
proxyWebsockets = true;
};
};
services.forgejo = {
enable = true;
package = unstable.forgejo;
settings = {
server = {
HTTP_PORT = 8312;
ROOT_URL = "https://forge.amy.mov";
};
};
database = {
createDatabase = false;
type = "postgres";
host = "nix01.cluster";
name = "forgejo";
user = "forgejo";
passwordFile = config.age.secrets."forgejo.dbpass".path;
};
};
services.authentik = {
enable = true;
environmentFile = config.age.secrets."authentik.env".path;
nginx = {
enable = true;
enableACME = false;
host = "auth.nix02.cluster";
};
createDatabase = false;
settings = {
postgresql = {
host = "nix01.cluster";
user = "authentik";
password = "authentik";
name = "authentik";
};
disable_startup_analytics = true;
avatars = "initials";
};
};
system.stateVersion = "24.11"; # Did you read the comment?
}

View file

@ -7,6 +7,8 @@
# Include the container-specific autogenerated configuration. # Include the container-specific autogenerated configuration.
./lxd.nix ./lxd.nix
./services/opengist.nix ./services/opengist.nix
./services/kener.nix
#./services/upsnap.nix
]; ];
networking = { networking = {
@ -22,6 +24,36 @@
vim vim
]; ];
# services.upsnap = {
# enable = true;
# };
services.kener = {
enable = true;
};
# Would like to use my PG DB for this but the service just doesn't
# support DBs that are hosted outside of the Nix box
services.writefreely = {
enable = true;
host = "write.amy.mov";
database = {
type = "sqlite3";
};
admin = {
name = "amy";
};
settings = {
server = {
bind = "0.0.0.0";
port = 8123;
};
};
};
services.opengist = { services.opengist = {
enable = true; enable = true;
config = ./opengist.yml; config = ./opengist.yml;

View file

@ -0,0 +1,45 @@
{ lib, buildNpmPackage, fetchFromGitHub }:
let
pname = "kener";
version = "3.2.12";
in
buildNpmPackage rec {
inherit pname version;
src = fetchFromGitHub {
owner = "rajnandan1";
repo = pname;
rev = "e6b5600a4726f719c2228d7d2da5a919e4bc15a3";
hash = "sha256-UBmt7SYZ2WukJvT1TOcwVr/L8RZVpVkLamCNV/xC8L4=";
};
npmDepsHash = "sha256-csB6qMJt3wBQyyWrK31F0FaRck3rt0JiH/lv77f+570=";
npmBuild = "npm run build";
# Copy src because the main runner (hosts the API etc) calls some stuff from it
installPhase = ''
mkdir $out
cp -R src/ $out
cp -R node_modules/ $out
cp -R build/ $out
cp -R migrations/ $out
cp -R seeds/ $out
cp -R static/ $out
cp -R knexfile.js $out
sed -i "s@./migrations@$out/migrations@g" $out/knexfile.js
sed -i "s@./seeds@$out/seeds@g" $out/knexfile.js
cp main.js $out
'';
meta = with lib; {
description = "Stunning status pages, batteries included!";
homepage = "https://kener.ing";
license = licenses.mit;
maintainers = with maintainers; [ nullishamy ];
mainProgram = "";
};
}

44
config/services/kener.nix Normal file
View file

@ -0,0 +1,44 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.kener;
src = builtins.fetchTarball {
url = "https://github.com/rajnandan1/kener/archive/refs/tags/3.2.12.tar.gz";
sha256 = "sha256:0a301jz8vqi2bd93k4lyabinshvadz084jfnkzrmxqrfr7w9gqbl";
};
in {
options = {
services.kener = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable Kener.
'';
};
};
};
config = mkIf cfg.enable {
systemd.services.kener =
let
kener = (pkgs.callPackage ./kener-pkg.nix {});
in {
path = [
pkgs.nodejs
pkgs.unixtools.ping
];
environment = {
"DATABASE_URL" = "sqlite:///opt/kener/kener.db";
"ORIGIN" = "https://amy.mov";
"KENER_SECRET_KEY" = "my-super-strong-key";
};
name = "kener.service";
enable = true;
script = "node ${kener}/main.js";
description = "Kener";
};
};
}

View file

@ -0,0 +1,141 @@
{
lib,
stdenv,
fetchFromGitLab,
bash,
makeWrapper,
jemalloc,
ffmpeg-headless,
python3,
pkg-config,
glib,
vips,
pnpm_9,
nodejs,
pixman,
pango,
cairo,
}:
stdenv.mkDerivation (finalAttrs: {
pname = "sharkey";
version = "2025.4.2";
src = fetchFromGitLab {
domain = "activitypub.software";
owner = "TransFem-org";
repo = "Sharkey";
rev = finalAttrs.version;
fetchSubmodules = true;
hash = "sha256-gCZY9d/YLNQRGVFqsK7//UDiS19Jtqa7adGliIdE+4c=";
};
pnpmDeps = pnpm_9.fetchDeps {
inherit (finalAttrs) src pname;
hash = "sha256-2bt/sHKGNIjKfOvZ6DCXvdJcKoOJX/ueWdLULlYK3YU=";
};
nativeBuildInputs = [
pnpm_9.configHook
nodejs
makeWrapper
python3
pkg-config
];
buildInputs = [
glib
vips
pixman
pango
cairo
];
# This environment variable is required for `node-gyp`, which is used by some native dependencies we build below.
# Without it, `node-gyp` won't know where the source code for node.js is, and will fail to download it instead.
npm_config_nodedir = nodejs;
# Sharkey depends on some packages with native code that needs to be built.
# These aren't built by default, so we need to run their build scripts manually.
#
# The tricky thing is that not all of them required for Sharkey to "successfully" build.
# They will trick you, make you think that Sharkey works, and successfully run your databse migrations.
# And then, when your instance tries to run, it will crash with an error like:
#
# Error [ERR_INTERNAL_ASSERTION]: This is caused by either a bug in Node.js or incorrect usage of Node.js internals.
# Please open an issue with this stack trace at https://github.com/nodejs/node/issues
#
# If you see that error, IT IS LYING TO YOU. It means Sharkey added a new dependency that required native code to be built.
# Figure out what is the new dependency. You can ask in their discord, and they'll probably tell you.
# And then, build it in the `buildPhase` below.
buildPhase = ''
runHook preBuild
(
cd node_modules/.pnpm/node_modules/v-code-diff
pnpm run postinstall
)
(
cd node_modules/.pnpm/node_modules/re2
pnpm run rebuild
)
(
cd node_modules/.pnpm/node_modules/sharp
pnpm run install
)
(
cd node_modules/.pnpm/node_modules/canvas
pnpm run install
)
pnpm build
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/Sharkey
ln -s /var/lib/sharkey $out/Sharkey/files
ln -s /run/sharkey $out/Sharkey/.config
cp -r * $out/Sharkey
makeWrapper ${lib.getExe pnpm_9} $out/bin/sharkey \
--chdir $out/Sharkey \
--prefix PATH : ${
lib.makeBinPath [
bash
pnpm_9
nodejs
]
} \
--prefix LD_LIBRARY_PATH : ${
lib.makeLibraryPath [
jemalloc
ffmpeg-headless
stdenv.cc.cc.lib
]
}
runHook postInstall
'';
passthru = {
inherit (finalAttrs) pnpmDeps;
};
meta = {
description = "🌎 A Sharkish microblogging platform 🚀";
homepage = "https://joinsharkey.org";
license = lib.licenses.gpl3Only;
maintainers = with lib.maintainers; [ sodiboo ];
platforms = [
"x86_64-linux"
"aarch64-linux"
];
mainProgram = "sharkey";
};
})

246
config/services/sharkey.nix Normal file
View file

@ -0,0 +1,246 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.sharkey;
createDB = cfg.database.host == "127.0.0.1" && cfg.database.createLocally;
createRedis = cfg.redis.host == "127.0.0.1" && cfg.redis.createLocally;
createMeili = cfg.meilisearch.host == "127.0.0.1" && cfg.meilisearch.createLocally;
createMeiliKey = cfg.meilisearch.key == lib.fakeSha256;
settingsFormat = pkgs.formats.yaml { };
configFile = settingsFormat.generate "sharkey-config.yml" cfg.settings;
in
{
options = {
services.sharkey = with lib; {
enable = mkEnableOption "sharkey";
domain = mkOption {
type = lib.types.str;
example = "fedi.amy.mov";
};
package = lib.mkOption {
type = lib.types.package;
defaultText = lib.literalExpression "pkgs.sharkey";
description = "Sharkey package to use.";
};
database = {
createLocally = mkOption {
type = lib.types.bool;
default = true;
};
host = mkOption {
type = lib.types.str;
default = "127.0.0.1";
};
port = mkOption {
type = lib.types.port;
default = 5432;
};
name = mkOption {
type = lib.types.str;
default = "sharkey";
};
passwordFile = mkOption {
description = ''
Path to a file containing the password for the database user.
This file must be readable by the `sharkey` user.
If creating a database locally, it must also be readable by the `postgres` user.
'';
type = lib.types.path;
example = "/run/secrets/sharkey-db-password";
};
};
redis = {
createLocally = mkOption {
type = lib.types.bool;
default = true;
};
host = mkOption {
type = lib.types.str;
default = "127.0.0.1";
};
port = mkOption {
type = lib.types.port;
default = 6379;
};
passwordFile = mkOption {
description = ''
Path to a file containing the password for the redis server.
This file must be readable by the `sharkey` user.
'';
type = lib.types.path;
example = "/run/secrets/sharkey-redis-password";
};
};
meilisearch = {
createLocally = mkOption {
type = lib.types.bool;
default = true;
};
host = mkOption {
type = lib.types.str;
default = "127.0.0.1";
};
port = mkOption {
type = lib.types.port;
default = 7700;
};
index = mkOption {
type = lib.types.str;
default = replaceStrings [ "." ] [ "_" ] cfg.domain;
};
key = mkOption {
type = lib.types.str;
default = "$MEILI_MASTER_KEY";
};
};
settings = mkOption {
type = settingsFormat.type;
default = { };
description = ''
Configuration for Sharkey, see
<link xlink:href="https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/.config/example.yml"/>
for supported settings.
'';
};
};
};
config = lib.mkIf cfg.enable {
documentation.enable = false;
assertions = [
{
assertion = createMeiliKey -> createMeili;
message = "services.sharkey.meilisearch.key is required to be set when connecting to a remote meilisearch instance";
}
];
services.sharkey.settings = {
url = "https://${cfg.domain}/";
db.host = cfg.database.host;
db.port = cfg.database.port;
db.db = cfg.database.name;
db.user = cfg.database.name;
db.pass = "$SHARKEY_DB_PASSWORD";
redis.host = cfg.redis.host;
redis.port = cfg.redis.port;
redis.pass = "$SHARKEY_REDIS_PASSWORD";
meilisearch.host = cfg.meilisearch.host;
meilisearch.port = cfg.meilisearch.port;
meilisearch.apiKey = cfg.meilisearch.key;
meilisearch.index = cfg.meilisearch.index;
meilisearch.ssl = !createMeili;
meilisearch.scope = "global";
};
environment.etc."sharkey.yml".source = configFile;
systemd.services.sharkey = {
after =
[ "network-online.target" ]
++ lib.optionals createDB [ "postgresql.service" ]
++ lib.optionals createRedis [ "redis-sharkey.service" ]
++ lib.optionals createMeili [ "meilisearch.service" ];
wantedBy = [ "multi-user.target" ];
preStart = ''
SHARKEY_DB_PASSWORD="$(cat ${lib.escapeShellArg cfg.database.passwordFile})" \
SHARKEY_REDIS_PASSWORD="$(cat ${lib.escapeShellArg cfg.redis.passwordFile})" \
${pkgs.envsubst}/bin/envsubst -i "${configFile}" > $MISSKEY_CONFIG_YML
'';
environment.MISSKEY_CONFIG_YML = "/run/sharkey/config.yml";
environment.NODE_ENV = "production";
serviceConfig = {
EnvironmentFile = lib.mkIf (
config.services.meilisearch.masterKeyEnvironmentFile != null
) config.services.meilisearch.masterKeyEnvironmentFile;
Type = "simple";
User = "sharkey";
StateDirectory = "sharkey";
StateDirectoryMode = "0700";
RuntimeDirectory = "sharkey";
RuntimeDirectoryMode = "0700";
ExecStart = "${cfg.package}/bin/sharkey migrateandstart";
TimeoutSec = 60;
Restart = "always";
StandardOutput = "journal";
StandardError = "journal";
SyslogIdentifier = "sharkey";
};
};
services.postgresql = lib.mkIf createDB {
enable = true;
settings.port = cfg.database.port;
ensureUsers = [
{
name = cfg.database.name;
ensureDBOwnership = true;
}
];
ensureDatabases = [ cfg.database.name ];
};
services.redis = lib.mkIf createRedis {
servers.sharkey = {
enable = true;
user = "sharkey";
bind = "127.0.0.1";
port = cfg.redis.port;
requirePassFile = cfg.redis.passwordFile;
};
};
systemd.services.postgresql.postStart = lib.mkIf createDB ''
$PSQL -tAc "ALTER ROLE ${cfg.database.name} WITH ENCRYPTED PASSWORD '$(printf "%s" $(cat ${cfg.database.passwordFile} | tr -d "\n"))';"
'';
services.meilisearch = lib.mkIf createMeili {
enable = true;
listenAddress = "127.0.0.1";
listenPort = cfg.meilisearch.port;
environment = "production";
};
users.users.sharkey = {
group = "sharkey";
isSystemUser = true;
home = "/run/sharkey";
packages = [ cfg.package ];
};
users.groups.sharkey = { };
};
meta.maintainers = with lib.maintainers; [ sodiboo ];
}

View file

@ -0,0 +1,43 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.upsnap;
src = pkgs.fetchzip {
url = "https://github.com/seriousm4x/UpSnap/releases/download/5.0.4/UpSnap_5.0.4_linux_amd64.zip";
sha256 = "sha256:1qlav9if6f2c50rzakyilxgzmq2c5bzcs6lx1w7sffxhl440nxhs";
};
in {
options = {
services.upsnap = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable UpSnap.
'';
};
bind = mkOption {
type = types.str;
default = "0.0.0.0:8090";
description = ''
The bind address/port
'';
};
};
};
config = mkIf cfg.enable {
systemd.services.upsnap = {
environment = {
"HOME" = "/opt/upsnap";
};
name = "upsnap.service";
enable = true;
script = "${src} serve --http ${cfg.bind}";
description = "UpSnap";
};
};
}

94
config/tf.nix Normal file
View file

@ -0,0 +1,94 @@
{ lib, ... }:
let
# Set to false to boostrap the containers
addNetworking = true;
pmHost = "https://192.168.1.100";
creds = {
ctPassword = "password";
};
sshKeys = ''
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDTbclOyOwIAPgVE/v5lIuf0P+Tq/Qkw3+GFa4YuRaCC amy@nixon
'';
templates = {
nixos = "nas-main:vztmpl/nixos-system-x86_64-linux.tar.xz";
};
in {
terraform = {
required_providers = {
proxmox = {
source = "telmate/proxmox";
version = "3.0.2-rc01";
};
};
};
provider.proxmox = {
pm_api_url = "${pmHost}:8006/api2/json";
pm_tls_insecure = true;
pm_user = "root@pam";
pm_password = "";
};
resource.proxmox_lxc = {
nix01 = {
target_node = "strawberry";
hostname = "nix01";
ostemplate = templates.nixos;
password = creds.ctPassword;
unprivileged = true;
swap = 1024;
ostype = "nixos";
cmode = "console";
rootfs = {
storage = "local-lvm";
size = "16G";
};
network = lib.mkIf addNetworking {
name = "eth0";
bridge = "vmbr0";
ip = "192.168.1.220/32";
gw = "192.168.1.1";
firewall = false;
};
features = {
nesting = true;
};
};
nix02 = {
target_node = "strawberry";
hostname = "nix02";
ostemplate = templates.nixos;
password = creds.ctPassword;
unprivileged = true;
swap = 1024;
ostype = "nixos";
cmode = "console";
rootfs = {
storage = "local-lvm";
size = "16G";
};
network = lib.mkIf addNetworking {
name = "eth0";
bridge = "vmbr0";
ip = "192.168.1.221/32";
gw = "192.168.1.1";
firewall = false;
};
features = {
nesting = true;
};
};
};
}

348
flake.lock generated
View file

@ -21,6 +21,50 @@
"type": "github" "type": "github"
} }
}, },
"authentik-nix": {
"inputs": {
"authentik-src": "authentik-src",
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"flake-utils": "flake-utils",
"napalm": "napalm",
"nixpkgs": "nixpkgs_2",
"pyproject-build-systems": "pyproject-build-systems",
"pyproject-nix": "pyproject-nix",
"systems": "systems_2",
"uv2nix": "uv2nix"
},
"locked": {
"lastModified": 1749129962,
"narHash": "sha256-gc1l5z5dWw9a9DWsrp0ZiD+SSMsNpEwMEiRi8K5sh5c=",
"owner": "nix-community",
"repo": "authentik-nix",
"rev": "271a38f7c4e2551f0674b894e2adf7cd1ddb8168",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "authentik-nix",
"type": "github"
}
},
"authentik-src": {
"flake": false,
"locked": {
"lastModified": 1749043670,
"narHash": "sha256-gwHngqb23U8By7jhxFWQZOXy+vPQApJSkvr4gHI5ifQ=",
"owner": "goauthentik",
"repo": "authentik",
"rev": "bda30c5ad5838fea36dc0a06f8580cca437f0fc0",
"type": "github"
},
"original": {
"owner": "goauthentik",
"ref": "version/2025.4.2",
"repo": "authentik",
"type": "github"
}
},
"darwin": { "darwin": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@ -45,8 +89,8 @@
}, },
"deploy-rs": { "deploy-rs": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat_2",
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_3",
"utils": "utils" "utils": "utils"
}, },
"locked": { "locked": {
@ -64,6 +108,22 @@
} }
}, },
"flake-compat": { "flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1696426674, "lastModified": 1696426674,
@ -83,6 +143,24 @@
"inputs": { "inputs": {
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": {
"lastModified": 1748821116,
"narHash": "sha256-F82+gS044J1APL0n4hH50GYdPRv/5JWm34oCJYmVKdE=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "49f0870db23e8c1ca0b5259734a02cd9e1e371a1",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib_2"
},
"locked": { "locked": {
"lastModified": 1743550720, "lastModified": 1743550720,
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
@ -97,6 +175,48 @@
"type": "github" "type": "github"
} }
}, },
"flake-parts_3": {
"inputs": {
"nixpkgs-lib": [
"terranix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1736143030,
"narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": [
"authentik-nix",
"systems"
]
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"home-manager": { "home-manager": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@ -118,6 +238,32 @@
"type": "github" "type": "github"
} }
}, },
"napalm": {
"inputs": {
"flake-utils": [
"authentik-nix",
"flake-utils"
],
"nixpkgs": [
"authentik-nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1725806412,
"narHash": "sha256-lGZjkjds0p924QEhm/r0BhAxbHBJE1xMOldB/HmQH04=",
"owner": "willibutz",
"repo": "napalm",
"rev": "b492440d9e64ae20736d3bec5c7715ffcbde83f5",
"type": "github"
},
"original": {
"owner": "willibutz",
"ref": "avoid-foldl-stack-overflow",
"repo": "napalm",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1703013332, "lastModified": 1703013332,
@ -135,6 +281,21 @@
} }
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": {
"lastModified": 1748740939,
"narHash": "sha256-rQaysilft1aVMwF14xIdGS3sj1yHlI6oKQNBRTF40cc=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "656a64127e9d791a334452c6b6606d17539476e2",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs-lib_2": {
"locked": { "locked": {
"lastModified": 1743296961, "lastModified": 1743296961,
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
@ -149,7 +310,39 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-unstable": {
"locked": {
"lastModified": 1749794982,
"narHash": "sha256-Kh9K4taXbVuaLC0IL+9HcfvxsSUx8dPB5s5weJcc9pc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ee930f9755f58096ac6e8ca94a1887e0534e2d81",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": { "nixpkgs_2": {
"locked": {
"lastModified": 1748929857,
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1702272962, "lastModified": 1702272962,
"narHash": "sha256-D+zHwkwPc6oYQ4G3A1HuadopqRwUY/JkMwHz1YF7j4Q=", "narHash": "sha256-D+zHwkwPc6oYQ4G3A1HuadopqRwUY/JkMwHz1YF7j4Q=",
@ -165,7 +358,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_3": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1743583204, "lastModified": 1743583204,
"narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=", "narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=",
@ -181,12 +374,80 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_5": {
"locked": {
"lastModified": 1728956102,
"narHash": "sha256-J8zo+UYNjHATsxn2/ROl8iaji2RgLm+sG7b3VcD36YM=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "3d85bae2431f20ab1ac5cf14d03d314dffe629af",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"pyproject-build-systems": {
"inputs": {
"nixpkgs": [
"authentik-nix",
"nixpkgs"
],
"pyproject-nix": [
"authentik-nix",
"pyproject-nix"
],
"uv2nix": [
"authentik-nix",
"uv2nix"
]
},
"locked": {
"lastModified": 1748562898,
"narHash": "sha256-STk4QklrGpM3gliPKNJdBLSQvIrqRuwHI/rnYb/5rh8=",
"owner": "pyproject-nix",
"repo": "build-system-pkgs",
"rev": "33bd58351957bb52dd1700ea7eeefe34de06a892",
"type": "github"
},
"original": {
"owner": "pyproject-nix",
"repo": "build-system-pkgs",
"type": "github"
}
},
"pyproject-nix": {
"inputs": {
"nixpkgs": [
"authentik-nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1746540146,
"narHash": "sha256-QxdHGNpbicIrw5t6U3x+ZxeY/7IEJ6lYbvsjXmcxFIM=",
"owner": "pyproject-nix",
"repo": "pyproject.nix",
"rev": "e09c10c24ebb955125fda449939bfba664c467fd",
"type": "github"
},
"original": {
"owner": "pyproject-nix",
"repo": "pyproject.nix",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"agenix": "agenix", "agenix": "agenix",
"authentik-nix": "authentik-nix",
"deploy-rs": "deploy-rs", "deploy-rs": "deploy-rs",
"flake-parts": "flake-parts", "flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs_3" "nixpkgs": "nixpkgs_4",
"nixpkgs-unstable": "nixpkgs-unstable",
"terranix": "terranix"
} }
}, },
"systems": { "systems": {
@ -205,6 +466,21 @@
} }
}, },
"systems_2": { "systems_2": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
},
"systems_3": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
@ -219,9 +495,44 @@
"type": "github" "type": "github"
} }
}, },
"systems_4": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"terranix": {
"inputs": {
"flake-parts": "flake-parts_3",
"nixpkgs": "nixpkgs_5",
"systems": "systems_4"
},
"locked": {
"lastModified": 1749381683,
"narHash": "sha256-16z7tXZch12SAd3d8tbAiEOamyq3zFbw1oUq/ipmTkM=",
"owner": "terranix",
"repo": "terranix",
"rev": "9d2370279d595be9e728b68d29ff0b546d88e619",
"type": "github"
},
"original": {
"owner": "terranix",
"repo": "terranix",
"type": "github"
}
},
"utils": { "utils": {
"inputs": { "inputs": {
"systems": "systems_2" "systems": "systems_3"
}, },
"locked": { "locked": {
"lastModified": 1701680307, "lastModified": 1701680307,
@ -236,6 +547,31 @@
"repo": "flake-utils", "repo": "flake-utils",
"type": "github" "type": "github"
} }
},
"uv2nix": {
"inputs": {
"nixpkgs": [
"authentik-nix",
"nixpkgs"
],
"pyproject-nix": [
"authentik-nix",
"pyproject-nix"
]
},
"locked": {
"lastModified": 1748916602,
"narHash": "sha256-GiwjjmPIISDFD0uQ1DqQ+/38hZ+2z1lTKVj/TkKaWwQ=",
"owner": "pyproject-nix",
"repo": "uv2nix",
"rev": "a4dd471de62b27928191908f57bfcd702ec2bfc9",
"type": "github"
},
"original": {
"owner": "pyproject-nix",
"repo": "uv2nix",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -4,38 +4,110 @@
inputs = { inputs = {
flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.url = "github:hercules-ci/flake-parts";
authentik-nix.url = "github:nix-community/authentik-nix";
terranix.url = "github:terranix/terranix";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# Later version of nixpkgs for forgejo
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
deploy-rs.url = "github:serokell/deploy-rs"; deploy-rs.url = "github:serokell/deploy-rs";
agenix.url = "github:ryantm/agenix"; agenix.url = "github:ryantm/agenix";
}; };
outputs = inputs@{ flake-parts, self, ... }: outputs = inputs@{ flake-parts, self, ... }:
let
sshUser = "root";
activateConfig = inputs.deploy-rs.lib.x86_64-linux.activate.nixos;
baseModules = [
./secrets
inputs.agenix.nixosModules.default
{
_module.args.unstable = import inputs.nixpkgs-unstable {
system = "x86_64-linux";
config.allowUnfree = true;
};
}
];
hosts = {
nix01 = {
location = "nix01.cluster";
};
nix02 = {
location = "nix02.cluster";
};
};
in
flake-parts.lib.mkFlake { inherit inputs; } { flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ]; systems = [ "x86_64-linux" ];
perSystem = { config, self', inputs', pkgs, system, ... }: { perSystem = { config, self', inputs', pkgs, system, ... }: {
_module.args.pkgs = import inputs.nixpkgs {
inherit system;
config.allowUnfree = true;
};
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
packages = with pkgs; [ packages = with pkgs; [
terraform
deploy-rs deploy-rs
just
inputs'.agenix.packages.default inputs'.agenix.packages.default
]; ];
}; };
packages.default = inputs.terranix.lib.terranixConfiguration {
inherit system;
modules = [ ./config/tf.nix ];
};
}; };
flake = { flake = {
nixosConfigurations.nixos = inputs.nixpkgs.lib.nixosSystem { nixosConfigurations.nixos = inputs.nixpkgs.lib.nixosSystem {
system = "x86_64-linux"; system = "x86_64-linux";
modules = [ modules = baseModules ++ [
./config/nixos.nix ./config/nixos.nix
./secrets
inputs.agenix.nixosModules.default
]; ];
}; };
deploy.nodes.nixos = { # deploy.nodes.nixos = {
hostname = "nixos.cluster"; # hostname = "nixos.cluster";
# profiles.system = {
# sshUser = "root";
# path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.nixos;
# };
# };
nixosConfigurations.nix01 = inputs.nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = baseModules ++ [
./config/nix01.nix
];
};
deploy.nodes.nix01 = {
hostname = hosts.nix01.location;
profiles.system = { profiles.system = {
sshUser = "root"; inherit sshUser;
path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.nixos; path = activateConfig self.nixosConfigurations.nix01;
};
};
nixosConfigurations.nix02 = inputs.nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = baseModules ++ [
./config/nix02.nix
inputs.authentik-nix.nixosModules.default
];
};
deploy.nodes.nix02 = {
hostname = hosts.nix02.location;
profiles.system = {
inherit sshUser;
path = activateConfig self.nixosConfigurations.nix02;
}; };
}; };

15
justfile Normal file
View file

@ -0,0 +1,15 @@
[private]
default:
@just --list
# Deploy the infrastructure through Terraform
infra:
nix build -o tf/homelab.tf.json && cd tf && terraform apply
# Deploy the systems through deploy-rs
deploy:
ssh-add host-key; deploy --auto-rollback false --magic-rollback false
# Rekey all of the secrets
rekey:
cd secrets && agenix -r -i ../host-key

Binary file not shown.

11
secrets/authentik.env.age Normal file
View file

@ -0,0 +1,11 @@
age-encryption.org/v1
-> ssh-ed25519 IYaO9g m8IFAIsugPKr+aH/NKMEuEaUKxgsOEkglfVU+LeCkHE
UmtFqaY5jLy0Vw/mrfGVADj1RFCCdLHE4g1t8SXjiR4
-> ssh-ed25519 Xqrm3g ZemtqeCHXBipTzFF8Wi6bYMQhOtUPa7cmFDea9RFB2Q
5/ZkLwrYpRyGMb/iQ3rOzZgCfod01lk5s+QgSajESq8
-> ssh-ed25519 FkAUOA BdMn4hxvWNOvSM5wRhsDtKEFrOOJoqHEF659cC6pO1c
9Txf5IOCDLIVR6aaR29EXfXF505GBzYJv79c2aSad6w
-> ssh-ed25519 G48T3w uV889WAiFjGtIrdqqf05C7Coy+0ZaaeGd0PMCCzfa1U
K32JwPfl2pTNhHZpWbbwD0ESdQhy4VuVG2R+2uy48Qs
--- L4+z0lU8ww3YDkHUpR7zCMLfQMLzpW8VLl22u2yHtTE
KC7ãD<EFBFBD>%£Æs<ˆht¦1„Ó¿2.æ@†ôŒ(Ø÷U.wûÈJ7.xs˜þvð>‡AžQhÀäDÞZ¦îצÆã©ùÏêˆ]p]±7ôóks]úFg@Z ¦a+?`€µfN6;øœ×âb”¹øyÂ'<27>ؽ0and¨[žÃØÄoÂ3W4£kPзÜ_Lu 2,#{Y;÷¿G7†éH×Í<C38D>4¢„8`He¤ägÅ;î!/ÙØÉNg` ÐIñîÙÃßßtYàï=¯²ÉpQvT~Ý "¨§ö<C2A7>Äý $ ¦Ÿ– j6݆e·”_}¢–=Û‚ @ØkjÕ­#|-l1q<31>B

12
secrets/blog.dbpass.age Normal file
View file

@ -0,0 +1,12 @@
age-encryption.org/v1
-> ssh-ed25519 IYaO9g ddDuW7b6yGdgv2TWdNWtn/9cA7Onw7NhnmAqk217jWk
kEZ6a9fr2ujIjFrUmpcrPkOSHiD76r8XoqQ+STYCZxg
-> ssh-ed25519 Xqrm3g 8IHpKy90zF1jGTJ8GpN5pzJvJ53sGWO94ze3sI5wDVw
wNlEOKy4z8f9Fj+/dyfe/gw4csMokoCIGmGGhvZTTXc
-> ssh-ed25519 FkAUOA QRc7iYIMYP/wFDOeswkIoVY9ybFO21GJTX5f0ddAZR0
W/ZCrz/Ce17zZRqKcych5fxJQDB+ShLCYGFAWBHgrJs
-> ssh-ed25519 G48T3w 1EUU7Vjhf/i8b9oxfg9IhQcu6Wolto74yK/6TvbLZ3g
LQsvfwD1Urxo/wdUkt0QktWEEh0X9E5htHLusqdZRUg
--- HEFxRiVTg2950mW0Gjf8wuzpMo7sa72gn3w92fhbBv4
qE;'Q、
G。ウAキVi"恥喘无蜥Vワモヘオキ闘ツナ

View file

@ -4,6 +4,8 @@
fileName: _: fileName: _:
lib.nameValuePair (lib.removeSuffix ".age" fileName) { lib.nameValuePair (lib.removeSuffix ".age" fileName) {
file = ./. + "/${fileName}"; file = ./. + "/${fileName}";
# FIXME: Don't do this bruh
mode = "0644";
} }
) (import ./secrets.nix); ) (import ./secrets.nix);
} }

View file

@ -0,0 +1,11 @@
age-encryption.org/v1
-> ssh-ed25519 IYaO9g VGA0gLwtQGiFgmgEf3tjwTgHLgGEi1RUDmDRLTnIFHY
p74Eblp5zp+6PQNxEPeAdfEYjIWCJptatCJjiqGzXTw
-> ssh-ed25519 Xqrm3g tCFqDPviklsnX5sM1k6aZTTEYXsMRCGE/fPR9Pvy0D0
EN3zvXgiR2I2gsoJHrf4Ws0e0APrIL4abJpTxmCU0QY
-> ssh-ed25519 FkAUOA v78yauukg/kqKxwyV7OSjrK6cTYsR/WMfrmqX2To50Q
JBBrbiE1OcrU1ccc2dcR075/smE4S34fmEMed8dxhRw
-> ssh-ed25519 G48T3w MJ/fDTqSKaiQayZMYxaIOaQimPMEzsjxHXEYUKB5VBE
x7/Tc8vC5s14t5AAsZBI74h9ylqZWgARDof8tBwkxfE
--- X2s9FwwDdkcRWFMNLiv1JX/BE8RcPZGP86vh+PdpdtE
ŽŽ¤S=Ý¡ÔÏxUÒ]®:Éþ¨FÃÃ<C383>hÄD!ÓÐ/7

View file

@ -0,0 +1,11 @@
age-encryption.org/v1
-> ssh-ed25519 IYaO9g MOUCzOR71o8NIie8OHb738/OQ63ztsQm+sJktwTRHUY
lFIByNCGmCE2PWOp2PZE/hxFw4xkn9yUM50gwc2ut68
-> ssh-ed25519 Xqrm3g IwwSBGM8ua3DqaQN+Wbnf3OmysOfLGJ7TOuNJZYNT3g
kxZmpD/qBlRvJocKxJdwmS5xDTcqDh4n8OuioR+hKtc
-> ssh-ed25519 FkAUOA Uu9awt3H4XnIKzJQZvgJDdqrY6KrCMWJ5QPc25N5gQw
bdXLLhlC6I3QtDcRPXY1gKUhHKePpeQaSWqO2I5CTzg
-> ssh-ed25519 G48T3w ScMxEKkuhSvubQpJCnhr3UdMBl+aF20Bejx3tiBB+lg
DZaY3phejHvYxGrZdE6VnLWrQG/h9Vxm9587SuoEZcE
--- 8B25N9XV5c5T0lLQhUYLs7VV0Zi9Jn1VE8cEGAYyKPs
p褐蛉皿ホロンャ咲蜻ケm烋ュ<E7838B><EFBDAD>Oケmル苴ロ<E88BB4>チカm朽5

View file

@ -1,10 +1,21 @@
# Used by the agenix cli and our module to generate all of the secret entries into the agenix module (see ./default.nix)
let let
# host-key.pub
amy = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDTbclOyOwIAPgVE/v5lIuf0P+Tq/Qkw3+GFa4YuRaCC amy@nixon"; amy = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDTbclOyOwIAPgVE/v5lIuf0P+Tq/Qkw3+GFa4YuRaCC amy@nixon";
users = [ amy ]; users = [ amy ];
# /etc/ssh/ssh_host_ed25519_key.pub on each host
nixos = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILMAy1iKOrL2yBCWljLnuwo29G5plDblI41jJ4Woy1el root@nixos"; nixos = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILMAy1iKOrL2yBCWljLnuwo29G5plDblI41jJ4Woy1el root@nixos";
systems = [ nixos ]; nix01 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBQfwok81BymeM9zW8D/LPZxRX6HGLkeTi1hS7GjPoZF root@nix01";
nix02 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGFJBDr16y8BAhtLfbc2WYJLwtgrxEyrpJx0zJpHPn/Z root@nix02";
systems = [ nixos nix01 nix02 ];
in in
{ {
"atticd.env.age".publicKeys = users ++ systems; "atticd.env.age".publicKeys = users ++ systems;
"blog.dbpass.age".publicKeys = users ++ systems;
"pgadmin.password.age".publicKeys = users ++ systems;
"sharkey.dbpass.age".publicKeys = users ++ systems;
"sharkey.redispass.age".publicKeys = users ++ systems;
"authentik.env.age".publicKeys = users ++ systems;
"forgejo.dbpass.age".publicKeys = users ++ systems;
} }

BIN
secrets/sharkey.dbpass.age Normal file

Binary file not shown.

View file

@ -0,0 +1,11 @@
age-encryption.org/v1
-> ssh-ed25519 IYaO9g DItojUGo0JgjIqrK08qOAHEPQJyi1O1nxrPlgy/AP1E
mCBsazT0fmMkZS0IPAwED+T9HKTe3tKyQ1Za/aJIgH8
-> ssh-ed25519 Xqrm3g SElTQ//ZPGb3WcAl8eAlJ15GBFWNdcsb3YQIb70OxlU
sZ2t9r5/D31qnAsrB/L5wktCpqioX2wXqbVxXfhSKWQ
-> ssh-ed25519 FkAUOA pt/3qcltuba+E+z82uhY7jvV28wmrKv49kiTIVYcn3o
B2PoSaa8WTGFNk6R0tq6JXXRQQa3MthhRZtWDfS1MYs
-> ssh-ed25519 G48T3w Zn7f2iF40UtqNyIp+mR/uzK3Gie0ei7EnYqlk83P/08
eefHjO7mEHG6XmX0iN+vVtMHUe1F25p4Revh6Ii8SUY
--- fU40EtRSgZ9IrSbs8CytvsbTaTWh30xoKsMHmMkUWsE
Kカ=スmィv畋鈑甫>+<2B>トe1n&@cq迂!#9ー・Tq;ミ<>