❄️ Dotfiles for our NixOS system configuration.

Compare changes

Choose any two refs to compare.

+16
flake.lock
···
"type": "github"
}
},
+
"darwin-login-items": {
+
"locked": {
+
"lastModified": 1763528199,
+
"narHash": "sha256-8LQ5Wp3AJUp71Elax1R9lNkuEbO2Mrnpq+o8qpbhQyc=",
+
"owner": "uncenter",
+
"repo": "nix-darwin-login-items",
+
"rev": "ab75c315893ca206ddf9529e6e3aac6cb01b2f1a",
+
"type": "github"
+
},
+
"original": {
+
"owner": "uncenter",
+
"repo": "nix-darwin-login-items",
+
"type": "github"
+
}
+
},
"easy-hosts": {
"locked": {
"lastModified": 1755470564,
···
"root": {
"inputs": {
"catppuccin": "catppuccin",
+
"darwin-login-items": "darwin-login-items",
"easy-hosts": "easy-hosts",
"flake-parts": "flake-parts",
"home-manager": "home-manager",
+3 -5
flake.nix
···
url = "git+https://tangled.org/@tangled.org/core";
inputs.nixpkgs.follows = "nixpkgs";
};
+
+
darwin-login-items.url = "github:uncenter/nix-darwin-login-items";
};
outputs =
-
inputs@{
-
flake-parts,
-
nixos-wsl,
-
...
-
}:
+
inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
inputs.easy-hosts.flakeModule
+20
home/chloe/activation.nix
···
+
# macOS Tahoe (26.x) no longer supports symlinks in the Launchpad.
+
# So, we're forced to copy applications instead of linking them.
+
# This is computationally slower, but we're left with no choice.
+
+
{
+
pkgs,
+
lib,
+
...
+
}:
+
+
{
+
config = lib.mkIf pkgs.stdenv.hostPlatform.isDarwin {
+
targets.darwin.copyApps = {
+
enable = true;
+
enableChecks = true;
+
};
+
+
targets.darwin.linkApps.enable = false;
+
};
+
}
+28
home/chloe/autostart.nix
···
+
{
+
lib,
+
osConfig,
+
pkgs,
+
...
+
}:
+
+
{
+
config = lib.mkIf osConfig.settings.profiles.graphical.enable {
+
xdg.configFile = {
+
# .desktop files for autostart only work on Linux with XDG
+
"autostart/1password.desktop" = lib.mkIf pkgs.stdenv.hostPlatform.isLinux {
+
text = ''
+
[Desktop Entry]
+
Name=1Password
+
Exec=1password --silent %U
+
Terminal=false
+
Type=Application
+
Icon=1password
+
StartupWMClass=1Password
+
Comment=Password manager and secure wallet
+
MimeType=x-scheme-handler/onepassword;
+
Categories=Office;
+
'';
+
};
+
};
+
};
+
}
+3
home/chloe/default.nix
···
{
imports = [
+
./activation.nix
+
./autostart.nix
./catppuccin.nix
./docs.nix
./files.nix
./packages
./programs
+
./scripts.nix
];
xdg.enable = true;
-23
home/chloe/packages/autostart.nix
···
-
{ lib, osConfig, pkgs, ... }:
-
-
{
-
config = lib.mkIf osConfig.settings.profiles.graphical.enable {
-
xdg.configFile = {
-
# .desktop files for autostart only work on Linux with XDG
-
"autostart/1password.desktop" = lib.mkIf pkgs.stdenv.isLinux {
-
text = ''
-
[Desktop Entry]
-
Name=1Password
-
Exec=1password --silent %U
-
Terminal=false
-
Type=Application
-
Icon=1password
-
StartupWMClass=1Password
-
Comment=Password manager and secure wallet
-
MimeType=x-scheme-handler/onepassword;
-
Categories=Office;
-
'';
-
};
-
};
-
};
-
}
+14
home/chloe/packages/base.nix
···
+
{ pkgs }:
+
+
with pkgs;
+
[
+
# dev tools
+
nodejs
+
deno
+
cloudflared
+
corepack_latest
+
bun
+
+
# other
+
_1password-cli
+
]
+15
home/chloe/packages/darwin.nix
···
+
{
+
pkgs,
+
lib,
+
osConfig,
+
}:
+
+
let
+
packages = with pkgs; [
+
# tools
+
shottr
+
];
+
in
+
lib.optionals (
+
osConfig.settings.profiles.graphical.enable && pkgs.stdenv.hostPlatform.isDarwin
+
) packages
+7 -10
home/chloe/packages/default.nix
···
-
{
+
{
pkgs,
lib,
-
osConfig,
+
osConfig,
...
}:
let
-
defaultPackages = import ./list/default.nix { inherit pkgs; };
-
guiPackages = import ./list/gui.nix { inherit pkgs lib osConfig; };
+
basePackages = import ./base.nix { inherit pkgs; };
+
darwinPackages = import ./darwin.nix { inherit pkgs lib osConfig; };
+
linuxPackages = import ./linux.nix { inherit pkgs lib osConfig; };
+
universalPackages = import ./universal.nix { inherit pkgs lib osConfig; };
in
{
-
imports = [
-
./autostart.nix
-
./scripts.nix
-
];
-
config = {
-
home.packages = defaultPackages ++ guiPackages;
+
home.packages = basePackages ++ darwinPackages ++ linuxPackages ++ universalPackages;
};
}
+32
home/chloe/packages/linux.nix
···
+
{
+
pkgs,
+
lib,
+
osConfig,
+
}:
+
+
let
+
packages = with pkgs; [
+
# messengers
+
telegram-desktop
+
vesktop
+
+
# dev tools
+
httpie-desktop
+
+
# mail
+
thunderbird
+
+
# games
+
xivlauncher
+
+
# messengers
+
discord
+
+
# other GUI apps
+
obs-studio
+
_1password-gui
+
];
+
in
+
lib.optionals (
+
osConfig.settings.profiles.graphical.enable && pkgs.stdenv.hostPlatform.isLinux
+
) packages
-19
home/chloe/packages/list/default.nix
···
-
{ pkgs }:
-
-
with pkgs; [
-
# dev tools
-
nodejs
-
deno
-
cloudflared
-
corepack_latest
-
bun
-
-
# fonts
-
iosevka
-
inter
-
atkinson-hyperlegible
-
nerd-fonts.jetbrains-mono
-
-
# other
-
_1password-cli
-
]
-31
home/chloe/packages/list/gui.nix
···
-
{ pkgs, lib, osConfig }:
-
-
let
-
# Common GUI packages available on all platforms
-
commonPackages = with pkgs; [
-
# cloud
-
owncloud-client
-
-
# messengers
-
telegram-desktop
-
vesktop
-
-
# notes
-
obsidian
-
-
# dev tools
-
zed-editor
-
httpie-desktop
-
-
# mail
-
thunderbird
-
-
# games
-
prismlauncher
-
xivlauncher
-
-
# other GUI apps
-
obs-studio
-
_1password-gui
-
];
-
in lib.optionals osConfig.settings.profiles.graphical.enable commonPackages
-15
home/chloe/packages/scripts.nix
···
-
{ pkgs, ... }:
-
-
{
-
home.packages = with pkgs; [
-
# Convert nix hash to SRI format and fetch from URL
-
(writeShellScriptBin "shash" ''
-
nix hash to-sri --type sha256 $(nix-prefetch-url ''$1)
-
'')
-
-
# Create a Python virtual environment with --copies flag
-
(writeShellScriptBin "create-venv" ''
-
nix run nixpkgs#python3 -- -m venv .venv --copies
-
'')
-
];
-
}
+25
home/chloe/packages/universal.nix
···
+
{
+
pkgs,
+
lib,
+
osConfig,
+
}:
+
+
let
+
packages = with pkgs; [
+
# dev tools
+
zed-editor
+
+
# fonts
+
iosevka
+
inter
+
atkinson-hyperlegible
+
nerd-fonts.jetbrains-mono
+
+
# games
+
prismlauncher
+
+
# notes
+
obsidian
+
];
+
in
+
lib.optionals osConfig.settings.profiles.graphical.enable packages
+12 -8
home/chloe/programs/cli/zsh.nix
···
-
{ lib, pkgs, osConfig, config, ... }:
+
{
+
lib,
+
pkgs,
+
osConfig,
+
config,
+
...
+
}:
{
programs.zsh = {
···
'';
envExtra = ''
-
${lib.optionalString pkgs.stdenv.isLinux ''
-
export PRISMA_SCHEMA_ENGINE_BINARY="${pkgs.prisma-engines}/bin/schema-engine"
-
export PRISMA_QUERY_ENGINE_BINARY="${pkgs.prisma-engines}/bin/query-engine"
-
export PRISMA_QUERY_ENGINE_LIBRARY="${pkgs.prisma-engines}/lib/libquery_engine.node"
-
export PRISMA_INTROSPECTION_ENGINE_BINARY="${pkgs.prisma-engines}/bin/introspection-engine"
-
export PRISMA_FMT_BINARY="${pkgs.prisma-engines}/bin/prisma-fmt"
-
''}
+
export PRISMA_SCHEMA_ENGINE_BINARY="${pkgs.prisma-engines}/bin/schema-engine"
+
export PRISMA_QUERY_ENGINE_BINARY="${pkgs.prisma-engines}/bin/query-engine"
+
export PRISMA_QUERY_ENGINE_LIBRARY="${pkgs.prisma-engines}/lib/libquery_engine.node"
+
export PRISMA_INTROSPECTION_ENGINE_BINARY="${pkgs.prisma-engines}/bin/introspection-engine"
+
export PRISMA_FMT_BINARY="${pkgs.prisma-engines}/bin/prisma-fmt"
'';
shellAliases = lib.mkMerge [
+15
home/chloe/scripts.nix
···
+
{ pkgs, ... }:
+
+
{
+
home.packages = with pkgs; [
+
# Convert nix hash to SRI format and fetch from URL
+
(writeShellScriptBin "shash" ''
+
nix hash to-sri --type sha256 $(nix-prefetch-url ''$1)
+
'')
+
+
# Create a Python virtual environment with --copies flag
+
(writeShellScriptBin "create-venv" ''
+
nix run nixpkgs#python3 -- -m venv .venv --copies
+
'')
+
];
+
}
+6
hosts/juniper/default.nix
···
{
+
settings = {
+
profiles = {
+
graphical.enable = true;
+
laptop.enable = true;
+
};
+
};
system.stateVersion = 6; # Initial nix-darwin version
}
+1
modules/darwin/default.nix
···
./packages.nix
./preferences
./security
+
./startup.nix
./users.nix
];
}
+1
modules/darwin/extras.nix
···
imports = [
inputs.home-manager.darwinModules.home-manager
inputs.ragenix.darwinModules.default
+
inputs.darwin-login-items.darwinModules.default
];
}
+8 -1
modules/darwin/homebrew.nix
···
# Casks (GUI applications)
casks = [
"1password"
+
"bruno"
+
"crossover"
+
"discord"
"maccy"
+
"microsoft-edge"
"microsoft-teams"
+
"mos"
"music-presence"
-
"prismlauncher"
+
"osu"
+
"signal"
+
"steam"
];
# Mac App Store apps (requires mas-cli)
+19
modules/darwin/startup.nix
···
+
{
+
config,
+
pkgs,
+
lib,
+
...
+
}:
+
+
{
+
environment.loginItems = {
+
enable = true;
+
items = [
+
"/Applications/1Password.app"
+
"/Applications/Mos.app"
+
"/Applications/Maccy.app"
+
"/Users/chloe/Applications/Home Manager Apps/Shottr.app"
+
"/Applications/Tailscale.app"
+
];
+
};
+
}
+14 -13
secrets/caddy.age
···
-----BEGIN AGE ENCRYPTED FILE-----
-
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGN2U2dKQSA3OTRw
-
N0pLbkIxZ2lvVEkyb0o4RGdZVVEzQ0VtSlp5MVJPMWNPd0xoc0JrCmRRMWR5N2hs
-
dFRwUVQ3RCtIb2hKT2ZvVlU4UUFBM0dkWmZ0U0lCUERZMUEKLT4gc3NoLWVkMjU1
-
MTkgMUNUOTd3IERweUM3ekJqTXBNTVYxU3p1OVhwZGxGdDl5bGNLRHBzbW12OHdN
-
NlpMRmsKendPWjFTZFFmMnpSWWNsa0NDM2kvTTNKYk42M01uS1BpazJzSGErUnE5
-
MAotPiBzc2gtZWQyNTUxOSBlUDNUdFEgb3prY01iWjBlS1pLSnZnTFJsOXM1NkVL
-
S3hJZTJ5V21lNFBDN21RTFN5VQpCa1lVNStyVWl5TS9Lcm9ZZHArV3ExWmViVzNv
-
cm9zNUdzN2ZjcVFkTjFrCi0+IDF3VVg2LWdyZWFzZSAvLUNTTzp0NSAyRUFrdSBp
-
UH0xIEgKWDJxU2tkQ20wZ29aQzlEWlh4RTQzaHBFV0tUb1hNUVg3aENOT0NxY0Z4
-
eEtpd3Nwb3BBYmU0a0JKZFBFOXFJYgpKMVNPcTIxNnhUelRmNmFqVGNsQVV6NTRO
-
d2cKLS0tIDYrQ2tWRWhHcTFGUnVNRU9kaWJCRkRSTXB5YTJvVnpwZ2ttLzhubGg1
-
N2sKd+UAtI0xQxss7MdlaHSRhAK6sOhBJ84PmnjDYDBhf4Xg2ON/EmD5AmUfcmcD
-
yEfwQSp1/vvUCtW1CXzEUswCRUzzhufms8GSLC7ehRAbhImhIK0lCKU=
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGN2U2dKQSA0THNq
+
UjBzRllITWxyM0tWckVhU0p3SE4zNWhYdmVuUUVyREtLOGxwbFRJCkh3QXFOUW9p
+
ZWxSUS9LTzJsMFdyK05jOXVBVzI2VUptTFFManZTQnRMUmsKLT4gc3NoLWVkMjU1
+
MTkgMUNUOTd3IEdKUHhlMG1hakFJcThMdjVWMk5WZ3RzYUZnbE5RdXJycmk3TW9H
+
TDF5bEUKTTIrbVh6UTZHYnhmV21sUFpSOGYwT3FCT1RacjRscUVabFpYWmh4U3Ft
+
cwotPiBzc2gtZWQyNTUxOSBlUDNUdFEgTXhVUGFrcExTV05qb2ZuWDkzd0hYSCtl
+
NjZLaWxFdi9ibmtUcDVzWE5DSQpSVTVvUlpBN2REZ2ZSMkRkWWlNWkFaekxROVVt
+
bEc4NXBkdVpWTkF0OENVCi0+IEw5e0xlLWdyZWFzZSBUIHdHZHk5IgpwVERQMzZG
+
ODgwUExoa082aFp2K0NvYmhtT1lvd2MzMlEycGc4YjlwVGNPcy9nS0FQV3lZTkpr
+
SkkzdXlWdllBCnBGODRBRU9vQnB4THdBQzZycGdMS05mN3R4VWZvaUpnbWZ5M3Zu
+
blN1cys1VGc4R09TdHdHaDhEQVF3Ci0tLSBYWmNaYkZiSzVQUXp0OU14WmY4cGNR
+
Mk1VZ1NxOWV3RUxVb0taRWRxZTFrCvVGqFlzVEsdUx95OQNCI1lmXdAXY5SxwJ6q
+
PI7Cj+zq+5BjUYqwPnu2KcMMKEtmBReSyL791bRn16PSzIS3yn2DzmIM72oea9hd
+
WJCud87qCxejIF3LgczKUqpTILzXtM7jzR2CNhoBSc9VDDA3U+ku4jOnxfj6
-----END AGE ENCRYPTED FILE-----
+45 -2
services/bluesky-pds/default.nix
···
serverAliases = [ "*.pds.sappho.systems" ];
extraConfig = ''
import common
-
import tls_cloudflare
-
reverse_proxy http://127.0.0.1:3333
+
import tls_bunny
+
+
handle / {
+
respond <<EOF
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⡀⢀⠀⢀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⢧⢳⣾⣂⣾⡁⣴⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠓⣬⠾⠋⠉⢸⣷⣋⣤⡦
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢯⡞⠃⠀⠀⠀⡼⠊⣠⡏⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣶⣄⣀⣀⡴⣡⡴⠃⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⣽⠧⢴⡀⢸⡛⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡿⠻⠛⠋⠉⠁⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠾⡇⢸⠉⠀⠀⠹⣼⣽⡚⠉⠹⡄⠀⠀⠀⠀⠀⠀⠀⠀⡴⠛⠙⢳⡀⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡘⢿⣆⠀⠀⠀⣿⠉⠀⠀⢀⣧⠀⢀⣤⠤⢤⣄⠀⢸⠉⠀⠀⠈⣧⢸⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠾⠉⠙⠻⢗⠀⣦⣏⣤⠀⣠⠞⠀⠀⣞⠋⠀⠀⠈⠻⡼⡀⣄⡀⢀⡯⠟⠧⢤⡀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢯⣀⠀⠀⠲⣷⡿⣿⠋⠉⠁⠀⠀⠀⠹⣄⠀⠀⣠⣤⠘⠧⣽⣨⣞⡁⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠁⠀⢸⣷⠀⠀⠀⠀⠀⠀⠈⣓⣦⣜⠉⣿⣶⠧⣟⣙⣀⣀⣠⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⡞⠉⠀⠾⠞⡵⢜⣇⡀⠙⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⠤⠤⣄⠀⣼⣿⡠⠴⠶⠦⣄⠀⣷⣀⠀⢀⣠⡷⡀⠉⠁⠀⢸⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⡋⠀⠀⠀⠈⢷⣿⠋⠀⠀⠀⠀⢸⡆⠀⠈⠉⣩⣿⠣⠙⠲⠤⠶⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⢻⠀⠀⠀⠀⠀⠀⡇⣠⣴⣾⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣄⠀⠀⠀⢠⣦⣌⡄⢠⣦⠀⠀⣾⠹⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠤⠙⠢⢄⡀⠀⢳⡄⢃⡼⠉⣀⠜⠋⠉⠒⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⢠⡯⠀⠀⠀⠀⠀⣠⡉⠙⣹⣿⠶⣮⣁⡀⠀⠀⠀⠀⢻⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠳⣄⠀⠀⠀⠘⠛⠓⣛⠇⢸⡷⡀⠻⠟⠀⠀⠀⠀⢸⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣹⠒⠶⢶⠒⠉⠀⠀⠈⠉⢫⠳⢄⣀⣀⣀⡤⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⡾⠋⠀⠀⢰⠀⠀⠀⠀⠀⠀⢸⣇⠀⠈⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⣠⣴⣾⠿⠛⠁⠀⠀⠀⠀⠈⠳⣄⣀⣀⣀⡤⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⣴⣿⡿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⣾⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⢸⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠈⢧⡃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
+
Welcome to Sapphic Angels' AT Protocol Personal Data Server (PDS).
+
+
Website: https://sapphic.moe
+
PDS: https://github.com/bluesky-social/pds
+
ATProto: https://atproto.com
+
+
🌸 🐇
+
EOF 200
+
}
+
+
handle {
+
reverse_proxy http://127.0.0.1:3333
+
}
'';
};
}
+9 -6
services/caddy/default.nix
···
services.caddy = {
enable = true;
package = pkgs.caddy.withPlugins {
-
plugins = [ "github.com/caddy-dns/cloudflare@v0.2.1" ];
-
hash = "sha256-iRzpN9awuEFsc7hqKzOMNiCFFEv833xhd4LM+VFQedI=";
+
plugins = [
+
"github.com/caddy-dns/bunny@v1.2.0"
+
];
+
hash = "sha256-bwffi5sWq07DVoPQGgEIN1jnvQKL6c4tFfR9AT9ThD4=";
};
environmentFile = config.age.secrets.caddy.path;
globalConfig = ''
+
debug
email chloe@sapphic.moe
'';
extraConfig = ''
-
(tls_cloudflare) {
+
(tls_bunny) {
tls {
-
dns cloudflare {env.CF_API_TOKEN}
-
resolvers 8.8.8.8 1.1.1.1
+
dns bunny {env.BUNNY_API_KEY}
+
resolvers 9.9.9.9 149.112.112.112
}
}
(common) {
···
}
'';
logFormat = ''
-
level info
+
level debug
format json
'';
};
+1 -1
services/destiny-labeler/default.nix
···
services.caddy.virtualHosts."labeler.sappho.systems" = {
extraConfig = ''
import common
-
import tls_cloudflare
+
import tls_bunny
reverse_proxy http://127.0.0.1:4002
'';
};
+35 -44
services/fail2ban/default.nix
···
-
{ config, pkgs, ... }:
+
{ pkgs, ... }:
{
age.secrets.abuseipdb = {
···
maxretry = 5;
findtime = "3600";
bantime = "86400";
-
action = "iptables-multiport[name=SSH, port='ssh']\nabuseipdb[abuseipdb_apikey=${config.age.secrets.abuseipdb.path}, abuseipdb_category='18,22', abuseipdb_comment='Fail2Ban SSH Brute Force']";
+
action = "iptables-multiport[name=SSH, port='ssh'] abuseipdb-agenix[abuseipdb_category='18,22']";
};
# Caddy HTTP/HTTPS protection - monitor for repeated 4xx/5xx errors
···
maxretry = 10;
findtime = "600";
bantime = "3600";
-
action = "iptables-multiport[name=Caddy, port='http,https']\nabuseipdb[abuseipdb_apikey=${config.age.secrets.abuseipdb.path}, abuseipdb_category='21', abuseipdb_comment='Fail2Ban Caddy Abuse']";
+
action = "iptables-multiport[name=Caddy, port='http,https'] abuseipdb-agenix[abuseipdb_category='21']";
};
# Rate-based protection - ban on excessive requests
···
maxretry = 50;
findtime = "60";
bantime = "1800";
-
action = "iptables-multiport[name=Caddy-RateLimit, port='http,https']\nabuseipdb[abuseipdb_apikey=${config.age.secrets.abuseipdb.path}, abuseipdb_category='21', abuseipdb_comment='Fail2Ban Rate Limiting']";
+
action = "iptables-multiport[name=Caddy-RateLimit, port='http,https'] abuseipdb-agenix[abuseipdb_category='21']";
};
};
};
-
# Custom filters and actions for Fail2Ban
-
environment.etc =
-
let
-
abuseipdbAction = pkgs.writeText "abuseipdb.conf" ''
-
[Definition]
-
actionstart =
-
actionstop =
-
actioncheck =
+
# Custom filters for Fail2Ban
+
environment.etc = {
+
# Caddy HTTP error monitoring filter
+
"fail2ban/filter.d/caddy-http.conf".text = ''
+
[Definition]
+
failregex = ^<HOST> -.*" (?:400|401|403|404|405|429|500|502|503|504) .*$
+
ignoreregex =
+
'';
-
# Report IP to AbuseIPDB using the API key from the secret file
-
actionban = /bin/sh -c 'curl -s -X POST https://api.abuseipdb.com/api/v2/report \
-
-H "Key: $(cat <abuseipdb_apikey>)" \
-
-H "Accept: application/json" \
-
-d "ip=<ip>&category=<abuseipdb_category>&comment=<abuseipdb_comment>&timestamp=$(date +%%s)" \
-
>> /var/log/fail2ban-abuseipdb.log 2>&1'
+
# Caddy rate limiting filter - detects repeated requests within short timeframe
+
"fail2ban/filter.d/caddy-ratelimit.conf".text = ''
+
[Definition]
+
failregex = ^<HOST> -.*" \d{3} .*$
+
ignoreregex =
+
'';
-
# No action to unban - AbuseIPDB reports are permanent
-
actionunban =
+
# Custom abuseipdb action that reads API key from file
+
"fail2ban/action.d/abuseipdb-agenix.conf".text = ''
+
[Definition]
+
# Report IP to AbuseIPDB, reading API key from Agenix secret file
+
# Uses double quotes to allow shell expansion of $(cat /run/agenix/abuseipdb)
+
# Sleep 12 seconds to respect AbuseIPDB rate limit (~5 requests per minute)
+
# Note: Don't use <matches> - fail2ban's wrapper causes issues with multiline content
+
# Don't use --fail so 429 rate limit errors don't mark action as failed
+
actionban = sleep 12; curl 'https://api.abuseipdb.com/api/v2/report' -H 'Accept: application/json' -H "Key: $(cat /run/agenix/abuseipdb)" --data-urlencode 'ip=<ip>' --data 'categories=<abuseipdb_category>' > /dev/null 2>&1
-
[Init]
-
# Default path - will be overridden by jail configuration
-
abuseipdb_apikey = /run/agenix/abuseipdb
-
abuseipdb_category = 18
-
abuseipdb_comment = Fail2Ban Report
-
'';
-
in
-
{
-
# Caddy HTTP error monitoring filter
-
"fail2ban/filter.d/caddy-http.conf".text = ''
-
[Definition]
-
failregex = ^<HOST> -.*" (?:400|401|403|404|405|429|500|502|503|504) .*$
-
ignoreregex =
-
'';
+
actionstart =
+
actionstop =
+
actioncheck =
+
actionunban =
-
# Caddy rate limiting filter - detects repeated requests within short timeframe
-
"fail2ban/filter.d/caddy-ratelimit.conf".text = ''
-
[Definition]
-
failregex = ^<HOST> -.*" \d{3} .*$
-
ignoreregex =
-
'';
-
-
# AbuseIPDB action - must be copied into action.d directory
-
"fail2ban/action.d/abuseipdb.conf".source = abuseipdbAction;
-
};
+
[Init]
+
abuseipdb_category = 18
+
'';
+
};
# Ensure the log directory exists
systemd.tmpfiles.rules = [
+1 -2
services/glance/default.nix
···
services.glance = {
enable = true;
-
openFirewall = true;
+
openFirewall = false;
environmentFile = config.age.secrets.glance.path;
settings = import ./settings.nix;
};
···
services.caddy.virtualHosts."home.sappho.systems" = {
extraConfig = ''
import common
-
import tls_cloudflare
reverse_proxy http://localhost:4040
'';
};
+29 -3
services/knot/default.nix
···
services.tangled-knot = {
enable = true;
motd = ''
-
🌸 welcome to the tangled knot server 🌸
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⡀⢀⠀⢀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⢧⢳⣾⣂⣾⡁⣴⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠓⣬⠾⠋⠉⢸⣷⣋⣤⡦
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢯⡞⠃⠀⠀⠀⡼⠊⣠⡏⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣶⣄⣀⣀⡴⣡⡴⠃⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⣽⠧⢴⡀⢸⡛⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡿⠻⠛⠋⠉⠁⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠾⡇⢸⠉⠀⠀⠹⣼⣽⡚⠉⠹⡄⠀⠀⠀⠀⠀⠀⠀⠀⡴⠛⠙⢳⡀⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡘⢿⣆⠀⠀⠀⣿⠉⠀⠀⢀⣧⠀⢀⣤⠤⢤⣄⠀⢸⠉⠀⠀⠈⣧⢸⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠾⠉⠙⠻⢗⠀⣦⣏⣤⠀⣠⠞⠀⠀⣞⠋⠀⠀⠈⠻⡼⡀⣄⡀⢀⡯⠟⠧⢤⡀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢯⣀⠀⠀⠲⣷⡿⣿⠋⠉⠁⠀⠀⠀⠹⣄⠀⠀⣠⣤⠘⠧⣽⣨⣞⡁⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠁⠀⢸⣷⠀⠀⠀⠀⠀⠀⠈⣓⣦⣜⠉⣿⣶⠧⣟⣙⣀⣀⣠⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⡞⠉⠀⠾⠞⡵⢜⣇⡀⠙⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⠤⠤⣄⠀⣼⣿⡠⠴⠶⠦⣄⠀⣷⣀⠀⢀⣠⡷⡀⠉⠁⠀⢸⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⡋⠀⠀⠀⠈⢷⣿⠋⠀⠀⠀⠀⢸⡆⠀⠈⠉⣩⣿⠣⠙⠲⠤⠶⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⢻⠀⠀⠀⠀⠀⠀⡇⣠⣴⣾⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣄⠀⠀⠀⢠⣦⣌⡄⢠⣦⠀⠀⣾⠹⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠤⠙⠢⢄⡀⠀⢳⡄⢃⡼⠉⣀⠜⠋⠉⠒⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⢠⡯⠀⠀⠀⠀⠀⣠⡉⠙⣹⣿⠶⣮⣁⡀⠀⠀⠀⠀⢻⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠳⣄⠀⠀⠀⠘⠛⠓⣛⠇⢸⡷⡀⠻⠟⠀⠀⠀⠀⢸⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣹⠒⠶⢶⠒⠉⠀⠀⠈⠉⢫⠳⢄⣀⣀⣀⡤⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⡾⠋⠀⠀⢰⠀⠀⠀⠀⠀⠀⢸⣇⠀⠈⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⠀⠀⣠⣴⣾⠿⠛⠁⠀⠀⠀⠀⠈⠳⣄⣀⣀⣀⡤⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠀⣴⣿⡿⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⣾⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⢸⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠈⢧⡃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
+
⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
-
hosted by sapphic angels
+
Welcome to Sapphic Angels' Knot server for tangled.org.
'';
server = {
hostname = "knot.sappho.systems";
···
services.caddy.virtualHosts."knot.sappho.systems" = {
extraConfig = ''
import common
-
import tls_cloudflare
+
import tls_bunny
reverse_proxy http://127.0.0.1:5555
'';
};
+1 -1
services/lanyard/default.nix
···
services.caddy.virtualHosts."lanyard.sappho.systems" = {
extraConfig = ''
import common
-
import tls_cloudflare
+
import tls_bunny
reverse_proxy http://127.0.0.1:4001
'';
};
+2 -4
services/ntfy/default.nix
···
services.caddy.virtualHosts."notify.sappho.systems" = {
extraConfig = ''
import common
-
import tls_cloudflare
+
import tls_bunny
+
reverse_proxy http://127.0.0.1:7070
'';
};
-
-
# Firewall
-
settings.firewall.allowedTCPPorts = [ 7070 ];
}
+2 -2
services/outline/default.nix
···
services.caddy.virtualHosts."wiki.sappho.systems" = {
extraConfig = ''
import common
-
import tls_cloudflare
+
import tls_bunny
reverse_proxy http://localhost:3300
'';
};
···
services.caddy.virtualHosts."minio.sappho.systems" = {
extraConfig = ''
import common
-
import tls_cloudflare
+
import tls_bunny
reverse_proxy http://localhost:9000
'';
};