merge: merged leyla/main

This commit is contained in:
Eve 2025-11-27 14:57:56 -06:00
parent 3a58722815
commit 0a8b3e1496
120 changed files with 2396 additions and 4519 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@ result
.direnv
.vscode/*
!.vscode/settings.json
nixos.qcow2

View file

@ -3,4 +3,12 @@
echo "restoring stashed changes"
git stash pop -q
# Find the most recent pre-commit stash and restore it
recent_stash=$(git stash list | grep "pre-commit-stash-" | head -n 1 | cut -d: -f1)
if [ -n "$recent_stash" ]; then
echo "Found recent pre-commit stash: $recent_stash"
git stash pop -q "$recent_stash"
else
echo "No pre-commit stash found to restore"
fi

View file

@ -4,14 +4,28 @@
# Get current branch name
current_branch=$(git branch --show-current)
# Only restore stash if we're on main branch and a merge just completed
# Only perform actions if we're on main branch and a merge just completed
if [ "$current_branch" = "main" ]; then
# Check if there are any stashes to restore
if git stash list | grep -q "stash@"; then
echo "Post-merge: restoring stashed changes on main branch"
git stash pop -q
echo "Post-merge on main branch - running nix flake check"
# Run nix flake check after merge into main
nix flake check
if [ ! $? -eq 0 ]; then
echo "Warning: nix flake check failed after merge into main"
echo "Please fix the issues as soon as possible"
else
echo "Post-merge: no stash to restore on main branch"
echo "nix flake check passed after merge"
fi
# Check if there are any pre-commit stashes to restore
recent_stash=$(git stash list | grep "pre-commit-stash-" | head -n 1 | cut -d: -f1)
if [ -n "$recent_stash" ]; then
echo "Post-merge: restoring pre-commit stash on main branch"
git stash pop -q "$recent_stash"
else
echo "Post-merge: no pre-commit stash to restore on main branch"
fi
else
echo "Post-merge: no action needed on branch '$current_branch'"

View file

@ -1,15 +1,25 @@
#!/usr/bin/env nix-shell
#! nix-shell -i bash ../shell.nix
echo "stashing all uncommitted changes"
git stash -q --keep-index
# Get current branch name
current_branch=$(git branch --show-current)
echo "checking flakes all compile"
echo "stashing all uncommitted changes with named stash (excluding hooks)"
git stash push -q --keep-index -m "pre-commit-stash-$(date +%s)" -- ':!.hooks/'
# Only run nix flake check if we're on main branch
if [ "$current_branch" = "main" ]; then
echo "On main branch - checking flakes all compile"
nix flake check
if [ ! $? -eq 0 ]; then
echo "Error: nix flake check failed on main branch"
exit 1
fi
echo "nix flake check passed"
else
echo "Not on main branch - skipping nix flake check"
fi
echo "running linter"
alejandra -q .

View file

@ -17,8 +17,8 @@ fi
if [ "$target_branch" = "main" ]; then
echo "Merging into main branch - running nix flake check..."
echo "stashing all uncommitted changes"
git stash -q --keep-index
echo "stashing all uncommitted changes with named stash (excluding hooks)"
git stash push -q --keep-index -m "pre-merge-stash-$(date +%s)" -- ':!.hooks/'
echo "checking flakes all compile"
nix flake check

View file

@ -43,17 +43,34 @@ nix multi user, multi system, configuration with `sops` secret management, `home
- Look into this for auto rotating sops keys `https://technotim.live/posts/rotate-sops-encryption-keys/`
- Look into this for npins https://jade.fyi/blog/pinning-nixos-with-npins/
- https://nixos-and-flakes.thiscute.world/
- proton mail now has an smtp server we could use that for our zfs and SMART test emails
- VR https://lvra.gitlab.io/docs/distros/nixos/
# Tasks:
## Documentation
- [ ] project layout
- [ ] users file structure
- [ ] reverse proxy design
- public service compatibility
- vpn based services compatibility
- [ ] the choice of impermanence
- [ ] storage module design
- base impermanence compatibility and structure reason
- what does local vs persist mean in pool names (do we need a second layer? ephemeral, local, and persist? local exist only on this machine and is not backed up, persist is backed up to other machines (I think we need to redo the sops and torrent/media folders?))
- plans to possibly support btrfs in the future
- plans for home manager datasets
- plans for auto systemd service datasets
- [ ] plans to migrate to some kind of acl structure for user management
- [ ] plans to migrate from flakes to npins
## Chores:
- [ ] test out crab hole service
- [ ] learn how to use actual
## Tech Debt
- [ ] monitor configuration in `~/.config/monitors.xml` should be sym linked to `/run/gdm/.config/monitors.xml` (https://www.reddit.com/r/NixOS/comments/u09cz9/home_manager_create_my_own_symlinks_automatically/)
- [ ] migrate away from flakes and move to npins
- [ ] rework the reverse_proxy.nix file so that it is a normally named service. Then also change it so that we can hook into it with both a base domain and a subdomain to make migrating to vpn accessible services easier
- [ ] `host.users` should be redone so that we just extend the base `users.users` object. Right now we cant quite do this because we have weird circular dependencies with disko/impermanence (not sure which one) and home manger enabling/disabling users per devices
## Broken things
- [ ] figure out steam vr things?
@ -66,18 +83,27 @@ nix multi user, multi system, configuration with `sops` secret management, `home
- [ ] rotate sops encryption keys periodically (and somehow sync between devices?)
- [ ] Secure Boot - https://github.com/nix-community/lanzaboote
- [ ] auto turn off on power loss - nut
- [ ] every service needs to have its own data pool
- [ ] secondary server with data sync. Maybe a Pi with a usb hdd enclosure and use rtcwake to only turn on once a week to sync data over tailscale with connection initiated from pi's side. We could probably put this at LZ. Hoping for it to draw only like $1 of power a month. Initial sync should probably be done here before we move it over because that will take a while. Data should be encrypted so that devices doesn't have access to it. Project will prob cost like $1800
## Data Access
- [ ] nfs export should be backed by the same values for server and client
- [ ] samba mounts
- [ ] offline access for nfs mounts (overlay with rsync might be a good option here? https://www.spinics.net/lists/linux-unionfs/msg07105.html note about nfs4 and overlay fs)
- [ ] figure out why syncthing and jellyfins permissions don't propagate downwards
- [ ] make radarr, sonarr, and bazarr accessible over vpn
- [ ] move searx, home-assistant, actual, jellyfin, paperless, and immich to only be accessible via vpn
- [ ] make radarr, sonarr, and bazarr accessible over vpn with fully qualified names via reverse proxy
- [ ] move searx, home-assistant, actual, vikunja, jellyfin, paperless, and immich to only be accessible via vpn
- [ ] FreeIPA/SSSD/LDAP/Kerberos to manage uid and gid's
## Services
- [ ] ntfy service for unified push
- [ ] signal socket server
- [ ] vikunja service for project management
- [ ] Penpot services (need to make this custom)
- [ ] minecraft server with old world file
- [ ] storj server
- [ ] Create Tor guard/relay server
- [ ] screeps server
- [ ] mastodon instance
## DevOps
@ -99,3 +125,6 @@ nix multi user, multi system, configuration with `sops` secret management, `home
- [ ] nginx queries
- [ ] ntfy.sh for push notifications
- [ ] kuma for uptime visualization
## Packages
- [ ] Custom private fork of MultiMC

View file

@ -19,6 +19,7 @@ in {
with pkgs; [
gnomeExtensions.dash-to-panel
claude-code
friture
]
);
@ -32,9 +33,11 @@ in {
(lib.mkIf (config.user.isDesktopUser || config.user.isTerminalUser) {
git = {
enable = true;
userName = "Eve";
userEmail = "evesnrobins@gmail.com";
extraConfig.init.defaultBranch = "main";
settings = {
user.name = "Eve";
user.email = "evesnrobins@gmail.com";
init.defaultBranch = "main";
};
};
openssh = {
@ -70,6 +73,7 @@ in {
tor-browser.enable = true;
olympus.enable = true;
libreoffice.enable = true;
noita-entangled-worlds.enable = true;
claude-code.enable = osConfig.host.ai.enable;

View file

@ -1,4 +1,6 @@
{osConfig, ...}: {
impermanence.fallbackPersistence.enable = false;
home = {
username = osConfig.users.users.git.name;
homeDirectory = osConfig.users.users.git.home;

View file

@ -12,6 +12,8 @@
];
config = {
impermanence.enable = osConfig.host.impermanence.enable;
# Home Manager needs a bit of information about you and the paths it should
# manage.
home = {

View file

@ -9,7 +9,7 @@
in {
imports = [
./vscode
./firefox.nix
./firefox
./direnv.nix
./openssh.nix
./git.nix
@ -68,10 +68,9 @@ in {
qflipper.enable = true;
openvpn.enable = true;
noisetorch.enable = true;
noita-entangled-worlds.enable = true;
tor-browser.enable = true;
gdx-liftoff.enable = true;
# polycule package is now working with Flutter 3.29
polycule.enable = true;
})
];
}

View file

@ -1,344 +0,0 @@
{
lib,
pkgs,
inputs,
...
}: {
config = {
programs.firefox = {
profiles.leyla = {
settings = {
"browser.search.defaultenginename" = "Searx";
"browser.search.order.1" = "Searx";
};
search = {
force = true;
default = "Searx";
engines = {
"Nix Packages" = {
urls = [
{
template = "https://search.nixos.org/packages";
params = [
{
name = "type";
value = "packages";
}
{
name = "query";
value = "{searchTerms}";
}
];
}
];
icon = "''${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg";
definedAliases = ["@np"];
};
"NixOS Wiki" = {
urls = [{template = "https://nixos.wiki/index.php?search={searchTerms}";}];
icon = "https://nixos.wiki/favicon.png";
updateInterval = 24 * 60 * 60 * 1000; # every day
definedAliases = ["@nw"];
};
"Searx" = {
urls = [{template = "https://search.jan-leila.com/?q={searchTerms}";}];
icon = "https://nixos.wiki/favicon.png";
updateInterval = 24 * 60 * 60 * 1000; # every day
definedAliases = ["@searx"];
};
};
};
extensions.packages = with inputs.firefox-addons.packages.${pkgs.system}; [
bitwarden
terms-of-service-didnt-read
multi-account-containers
shinigami-eyes
ublock-origin
sponsorblock
dearrow
df-youtube
return-youtube-dislikes
privacy-badger
decentraleyes
clearurls
localcdn
snowflake
deutsch-de-language-pack
dictionary-german
tab-session-manager
# (
# buildFirefoxXpiAddon rec {
# pname = "italiano-it-language-pack";
# version = "132.0.20241110.231641";
# addonId = "langpack-it@firefox.mozilla.org";
# url = "https://addons.mozilla.org/firefox/downloads/file/4392453/italiano_it_language_pack-${version}.xpi";
# sha256 = "";
# meta = with lib;
# {
# description = "Firefox Language Pack for Italiano (it) Italian";
# license = licenses.mpl20;
# mozPermissions = [];
# platforms = platforms.all;
# };
# }
# )
# (
# buildFirefoxXpiAddon rec {
# pname = "dizionario-italiano";
# version = "5.1";
# addonId = "it-IT@dictionaries.addons.mozilla.org";
# url = "https://addons.mozilla.org/firefox/downloads/file/1163874/dizionario_italiano-${version}.xpi";
# sha256 = "";
# meta = with lib;
# {
# description = "Add support for Italian to spellchecking";
# license = licenses.gpl3;
# mozPermissions = [];
# platforms = platforms.all;
# };
# }
# )
];
settings = {
# Disable irritating first-run stuff
"browser.disableResetPrompt" = true;
"browser.download.panel.shown" = true;
"browser.feeds.showFirstRunUI" = false;
"browser.messaging-system.whatsNewPanel.enabled" = false;
"browser.rights.3.shown" = true;
"browser.shell.checkDefaultBrowser" = false;
"browser.shell.defaultBrowserCheckCount" = 1;
"browser.startup.homepage_override.mstone" = "ignore";
"browser.uitour.enabled" = false;
"startup.homepage_override_url" = "";
"trailhead.firstrun.didSeeAboutWelcome" = true;
"browser.bookmarks.restore_default_bookmarks" = false;
"browser.bookmarks.addedImportButton" = true;
"browser.newtabpage.activity-stream.feeds.section.topstories" = false;
# Usage Experience
"browser.startup.homepage" = "about:home";
"browser.download.useDownloadDir" = false;
"browser.uiCustomization.state" = builtins.toJSON {
"currentVersion" = 20;
"newElementCount" = 6;
"dirtyAreaCache" = [
"nav-bar"
"PersonalToolbar"
"toolbar-menubar"
"TabsToolbar"
"unified-extensions-area"
"vertical-tabs"
];
"placements" = {
"widget-overflow-fixed-list" = [];
"unified-extensions-area" = [
# bitwarden
"_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action"
"ublock0_raymondhill_net-browser-action"
"sponsorblocker_ajay_app-browser-action"
"dearrow_ajay_app-browser-action"
"jid1-mnnxcxisbpnsxq_jetpack-browser-action"
"_testpilot-containers-browser-action"
"addon_simplelogin-browser-action"
"_74145f27-f039-47ce-a470-a662b129930a_-browser-action"
"jid1-bofifl9vbdl2zq_jetpack-browser-action"
"dfyoutube_example_com-browser-action"
"_b86e4813-687a-43e6-ab65-0bde4ab75758_-browser-action"
"_762f9885-5a13-4abd-9c77-433dcd38b8fd_-browser-action"
"_b11bea1f-a888-4332-8d8a-cec2be7d24b9_-browse-action"
"jid0-3guet1r69sqnsrca5p8kx9ezc3u_jetpack-browser-action"
];
"nav-bar" = [
"back-button"
"forward-button"
"stop-reload-button"
"urlbar-container"
"downloads-button"
"unified-extensions-button"
"reset-pbm-toolbar-button"
];
"toolbar-menubar" = [
"menubar-items"
];
"TabsToolbar" = [
"firefox-view-button"
"tabbrowser-tabs"
"new-tab-button"
"alltabs-button"
];
"vertical-tabs" = [];
"PersonalToolbar" = [
"import-button"
"personal-bookmarks"
];
};
"seen" = [
"save-to-pocket-button"
"developer-button"
"privacy_privacy_com-browser-action"
"sponsorblocker_ajay_app-browser-action"
"ublock0_raymondhill_net-browser-action"
"addon_simplelogin-browser-action"
"dearrow_ajay_app-browser-action"
"_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action"
"_74145f27-f039-47ce-a470-a662b129930a_-browser-action"
"jid1-bofifl9vbdl2zq_jetpack-browser-action"
"dfyoutube_example_com-browser-action"
"_testpilot-containers-browser-action"
"_b86e4813-687a-43e6-ab65-0bde4ab75758_-browser-action"
"jid1-mnnxcxisbpnsxq_jetpack-browser-action"
"_762f9885-5a13-4abd-9c77-433dcd38b8fd_-browser-action"
"_b11bea1f-a888-4332-8d8a-cec2be7d24b9_-browser-action"
"jid0-3guet1r69sqnsrca5p8kx9ezc3u_jetpack-browser-action"
];
};
"browser.newtabpage.activity-stream.feeds.topsites" = false;
"browser.newtabpage.activity-stream.showSponsoredTopSites" = false;
"browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts" = false;
"browser.newtabpage.blocked" = lib.genAttrs [
# Facebook
"4gPpjkxgZzXPVtuEoAL9Ig=="
# Reddit
"gLv0ja2RYVgxKdp0I5qwvA=="
# Amazon
"K00ILysCaEq8+bEqV/3nuw=="
# Twitter
"T9nJot5PurhJSy8n038xGA=="
] (_: 1);
"identity.fxaccounts.enabled" = false;
# Security
"privacy.trackingprotection.enabled" = true;
"dom.security.https_only_mode" = true;
"extensions.formautofill.addresses.enabled" = false;
"extensions.formautofill.creditCards.enabled" = false;
"signon.rememberSignons" = false;
"privacy.sanitize.sanitizeOnShutdown" = true;
"privacy.clearOnShutdown_v2.cache" = true;
"privacy.clearOnShutdown_v2.cookiesAndStorage" = true;
"privacy.clearOnShutdown_v2.historyFormDataAndDownloads" = true;
"urlclassifier.trackingSkipURLs" = "";
"urlclassifier.features.socialtracking.skipURLs" = "";
"dom.security.https_only_mode_pbm" = true;
"dom.security.https_only_mode_error_page_user_suggestions" = true;
# Disable telemetry
"app.shield.optoutstudies.enabled" = false;
"browser.discovery.enabled" = false;
"browser.newtabpage.activity-stream.feeds.telemetry" = false;
"browser.newtabpage.activity-stream.telemetry" = false;
"browser.ping-centre.telemetry" = false;
"datareporting.healthreport.service.enabled" = false;
"datareporting.healthreport.uploadEnabled" = false;
"datareporting.policy.dataSubmissionEnabled" = false;
"datareporting.sessions.current.clean" = true;
"devtools.onboarding.telemetry.logged" = false;
"toolkit.telemetry.archive.enabled" = false;
"toolkit.telemetry.bhrPing.enabled" = false;
"toolkit.telemetry.enabled" = false;
"toolkit.telemetry.firstShutdownPing.enabled" = false;
"toolkit.telemetry.hybridContent.enabled" = false;
"toolkit.telemetry.newProfilePing.enabled" = false;
"toolkit.telemetry.prompted" = 2;
"toolkit.telemetry.rejected" = true;
"toolkit.telemetry.reportingpolicy.firstRun" = false;
"toolkit.telemetry.server" = "";
"toolkit.telemetry.shutdownPingSender.enabled" = false;
"toolkit.telemetry.unified" = false;
"toolkit.telemetry.unifiedIsOptIn" = false;
"toolkit.telemetry.updatePing.enabled" = false;
};
bookmarks = {
force = true;
settings = [
{
name = "Media";
url = "https://media.jan-leila.com/";
keyword = "";
tags = [""];
}
{
name = "Photos";
url = "https://photos.jan-leila.com";
keyword = "";
tags = [""];
}
{
name = "Git";
url = "https://git.jan-leila.com/";
keyword = "";
tags = [""];
}
{
name = "Home Automation";
url = "https://home.jan-leila.com/";
keyword = "";
tags = [""];
}
{
name = "Mail";
url = "https://mail.protonmail.com";
keyword = "";
tags = [""];
}
{
name = "Open Street Map";
url = "https://www.openstreetmap.org/";
keyword = "";
tags = [""];
}
{
name = "Password Manager";
url = "https://vault.bitwarden.com/";
keyword = "";
tags = [""];
}
{
name = "Mastodon";
url = "https://mspsocial.net";
keyword = "";
tags = [""];
}
{
name = "Linked In";
url = "https://www.linkedin.com/";
keyword = "";
tags = [""];
}
{
name = "Job Search";
url = "https://www.jobsinnetwork.com/?state=cleaned_history&language%5B%5D=en&query=react&locations.countryCode%5B%5D=IT&locations.countryCode%5B%5D=DE&locations.countryCode%5B%5D=NL&experience%5B%5D=medior&experience%5B%5D=junior&page=1";
keyword = "";
tags = [""];
}
{
name = "React Docs";
url = "https://react.dev/";
keyword = "";
tags = [""];
}
# Template
# {
# name = "";
# url = "";
# keyword = "";
# tags = [""];
# }
];
};
};
};
};
}

View file

@ -0,0 +1,155 @@
{...}: {
programs.firefox = {
profiles.leyla = {
bookmarks = {
force = true;
settings = [
# Personal Services
{
name = "Media";
url = "https://media.jan-leila.com/";
keyword = "";
tags = [""];
}
{
name = "Photos";
url = "https://photos.jan-leila.com";
keyword = "";
tags = [""];
}
{
name = "Git";
url = "https://git.jan-leila.com/";
keyword = "";
tags = [""];
}
{
name = "Home Automation";
url = "https://home.jan-leila.com/";
keyword = "";
tags = [""];
}
{
name = "Search";
url = "https://search.jan-leila.com/";
keyword = "";
tags = [""];
}
{
name = "Budget";
url = "https://budget.jan-leila.com/";
keyword = "";
tags = [""];
}
{
name = "Documents";
url = "https://documents.jan-leila.com/";
keyword = "";
tags = [""];
}
# Defiant Server Services
{
name = "QBittorrent";
url = "http://defiant:8084";
keyword = "";
tags = ["defiant"];
}
{
name = "Sonarr";
url = "http://defiant:8989";
keyword = "";
tags = ["defiant"];
}
{
name = "Radarr";
url = "http://defiant:7878";
keyword = "";
tags = ["defiant"];
}
{
name = "Bazarr";
url = "http://defiant:6767";
keyword = "";
tags = ["defiant"];
}
{
name = "Lidarr";
url = "http://defiant:8686";
keyword = "";
tags = ["defiant"];
}
{
name = "Jackett";
url = "http://defiant:9117";
keyword = "";
tags = ["defiant"];
}
{
name = "Crab-hole DNS";
url = "http://defiant:8085";
keyword = "";
tags = ["defiant"];
}
# External Services
{
name = "Mail";
url = "https://mail.protonmail.com";
keyword = "";
tags = [""];
}
{
name = "Open Street Map";
url = "https://www.openstreetmap.org/";
keyword = "";
tags = [""];
}
{
name = "Password Manager";
url = "https://vault.bitwarden.com/";
keyword = "";
tags = [""];
}
{
name = "Mastodon";
url = "https://mspsocial.net";
keyword = "";
tags = [""];
}
{
name = "Linked In";
url = "https://www.linkedin.com/";
keyword = "";
tags = [""];
}
{
name = "Job Search";
url = "https://www.jobsinnetwork.com/?state=cleaned_history&language%5B%5D=en&query=react&locations.countryCode%5B%5D=IT&locations.countryCode%5B%5D=DE&locations.countryCode%5B%5D=NL&experience%5B%5D=medior&experience%5B%5D=junior&page=1";
keyword = "";
tags = [""];
}
{
name = "React Docs";
url = "https://react.dev/";
keyword = "";
tags = [""];
}
{
name = "Cyberia Matrix";
url = "https://chat.cyberia.club";
keyword = "";
tags = [""];
}
# Template
# {
# name = "";
# url = "";
# keyword = "";
# tags = [""];
# }
];
};
};
};
}

View file

@ -0,0 +1,18 @@
{
lib,
pkgs,
inputs,
...
}: {
imports = [
./firefox.nix
./bookmarks.nix
./harden.nix
];
config = {
programs.firefox = {
enable = true;
};
};
}

View file

@ -0,0 +1,191 @@
{
lib,
pkgs,
inputs,
...
}: {
programs.firefox = {
profiles.leyla = {
settings = {
"browser.search.defaultenginename" = "Searx";
"browser.search.order.1" = "Searx";
};
search = {
force = true;
default = "Searx";
engines = {
"Nix Packages" = {
urls = [
{
template = "https://search.nixos.org/packages";
params = [
{
name = "type";
value = "packages";
}
{
name = "query";
value = "{searchTerms}";
}
];
}
];
icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg";
definedAliases = ["@np"];
};
"NixOS Wiki" = {
urls = [{template = "https://nixos.wiki/index.php?search={searchTerms}";}];
icon = "https://nixos.wiki/favicon.png";
updateInterval = 24 * 60 * 60 * 1000; # every day
definedAliases = ["@nw"];
};
"Searx" = {
urls = [{template = "https://search.jan-leila.com/?q={searchTerms}";}];
icon = "https://nixos.wiki/favicon.png";
updateInterval = 24 * 60 * 60 * 1000; # every day
definedAliases = ["@searx"];
};
};
};
extensions.packages = with inputs.firefox-addons.packages.${pkgs.stdenv.hostPlatform.system}; [
bitwarden
terms-of-service-didnt-read
multi-account-containers
shinigami-eyes
ublock-origin
sponsorblock
dearrow
df-youtube
return-youtube-dislikes
privacy-badger
decentraleyes
clearurls
localcdn
snowflake
pkgs.firefox-extensions.deutsch-de-language-pack
dictionary-german
tab-session-manager
pkgs.firefox-extensions.italiano-it-language-pack
pkgs.firefox-extensions.dizionario-italiano
];
settings = {
# Disable irritating first-run stuff
"browser.disableResetPrompt" = true;
"browser.download.panel.shown" = true;
"browser.feeds.showFirstRunUI" = false;
"browser.messaging-system.whatsNewPanel.enabled" = false;
"browser.rights.3.shown" = true;
"browser.shell.checkDefaultBrowser" = false;
"browser.shell.defaultBrowserCheckCount" = 1;
"browser.startup.homepage_override.mstone" = "ignore";
"browser.uitour.enabled" = false;
"startup.homepage_override_url" = "";
"trailhead.firstrun.didSeeAboutWelcome" = true;
"browser.bookmarks.restore_default_bookmarks" = false;
"browser.bookmarks.addedImportButton" = true;
"browser.newtabpage.activity-stream.feeds.section.topstories" = false;
# Usage Experience
"browser.startup.homepage" = "about:home";
"browser.download.useDownloadDir" = false;
"browser.uiCustomization.state" = builtins.toJSON {
"currentVersion" = 20;
"newElementCount" = 6;
"dirtyAreaCache" = [
"nav-bar"
"PersonalToolbar"
"toolbar-menubar"
"TabsToolbar"
"unified-extensions-area"
"vertical-tabs"
];
"placements" = {
"widget-overflow-fixed-list" = [];
"unified-extensions-area" = [
# bitwarden
"_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action"
"ublock0_raymondhill_net-browser-action"
"sponsorblocker_ajay_app-browser-action"
"dearrow_ajay_app-browser-action"
"jid1-mnnxcxisbpnsxq_jetpack-browser-action"
"_testpilot-containers-browser-action"
"addon_simplelogin-browser-action"
"_74145f27-f039-47ce-a470-a662b129930a_-browser-action"
"jid1-bofifl9vbdl2zq_jetpack-browser-action"
"dfyoutube_example_com-browser-action"
"_b86e4813-687a-43e6-ab65-0bde4ab75758_-browser-action"
"_762f9885-5a13-4abd-9c77-433dcd38b8fd_-browser-action"
"_b11bea1f-a888-4332-8d8a-cec2be7d24b9_-browse-action"
"jid0-3guet1r69sqnsrca5p8kx9ezc3u_jetpack-browser-action"
];
"nav-bar" = [
"back-button"
"forward-button"
"stop-reload-button"
"urlbar-container"
"downloads-button"
"unified-extensions-button"
"reset-pbm-toolbar-button"
];
"toolbar-menubar" = [
"menubar-items"
];
"TabsToolbar" = [
"firefox-view-button"
"tabbrowser-tabs"
"new-tab-button"
"alltabs-button"
];
"vertical-tabs" = [];
"PersonalToolbar" = [
"import-button"
"personal-bookmarks"
];
};
"seen" = [
"save-to-pocket-button"
"developer-button"
"privacy_privacy_com-browser-action"
"sponsorblocker_ajay_app-browser-action"
"ublock0_raymondhill_net-browser-action"
"addon_simplelogin-browser-action"
"dearrow_ajay_app-browser-action"
"_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action"
"_74145f27-f039-47ce-a470-a662b129930a_-browser-action"
"jid1-bofifl9vbdl2zq_jetpack-browser-action"
"dfyoutube_example_com-browser-action"
"_testpilot-containers-browser-action"
"_b86e4813-687a-43e6-ab65-0bde4ab75758_-browser-action"
"jid1-mnnxcxisbpnsxq_jetpack-browser-action"
"_762f9885-5a13-4abd-9c77-433dcd38b8fd_-browser-action"
"_b11bea1f-a888-4332-8d8a-cec2be7d24b9_-browser-action"
"jid0-3guet1r69sqnsrca5p8kx9ezc3u_jetpack-browser-action"
];
};
"browser.newtabpage.activity-stream.feeds.topsites" = false;
"browser.newtabpage.activity-stream.showSponsoredTopSites" = false;
"browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts" = false;
"browser.newtabpage.blocked" = lib.genAttrs [
# Facebook
"4gPpjkxgZzXPVtuEoAL9Ig=="
# Reddit
"gLv0ja2RYVgxKdp0I5qwvA=="
# Amazon
"K00ILysCaEq8+bEqV/3nuw=="
# Twitter
"T9nJot5PurhJSy8n038xGA=="
] (_: 1);
"identity.fxaccounts.enabled" = false;
};
};
};
}

View file

@ -0,0 +1,50 @@
{...}: {
programs.firefox = {
profiles.leyla = {
settings = {
# Security
"privacy.trackingprotection.enabled" = true;
"dom.security.https_only_mode" = true;
"dom.security.https_only_mode_pbm" = true;
"dom.security.https_only_mode_error_page_user_suggestions" = true;
# Privacy & Data Protection
"extensions.formautofill.addresses.enabled" = false;
"extensions.formautofill.creditCards.enabled" = false;
"signon.rememberSignons" = false;
"privacy.sanitize.sanitizeOnShutdown" = true;
"privacy.clearOnShutdown_v2.cache" = true;
"privacy.clearOnShutdown_v2.cookiesAndStorage" = true;
"privacy.clearOnShutdown_v2.historyFormDataAndDownloads" = true;
"urlclassifier.trackingSkipURLs" = "";
"urlclassifier.features.socialtracking.skipURLs" = "";
# Disable telemetry and data collection
"app.shield.optoutstudies.enabled" = false;
"browser.discovery.enabled" = false;
"browser.newtabpage.activity-stream.feeds.telemetry" = false;
"browser.newtabpage.activity-stream.telemetry" = false;
"browser.ping-centre.telemetry" = false;
"datareporting.healthreport.service.enabled" = false;
"datareporting.healthreport.uploadEnabled" = false;
"datareporting.policy.dataSubmissionEnabled" = false;
"datareporting.sessions.current.clean" = true;
"devtools.onboarding.telemetry.logged" = false;
"toolkit.telemetry.archive.enabled" = false;
"toolkit.telemetry.bhrPing.enabled" = false;
"toolkit.telemetry.enabled" = false;
"toolkit.telemetry.firstShutdownPing.enabled" = false;
"toolkit.telemetry.hybridContent.enabled" = false;
"toolkit.telemetry.newProfilePing.enabled" = false;
"toolkit.telemetry.prompted" = 2;
"toolkit.telemetry.rejected" = true;
"toolkit.telemetry.reportingpolicy.firstRun" = false;
"toolkit.telemetry.server" = "";
"toolkit.telemetry.shutdownPingSender.enabled" = false;
"toolkit.telemetry.unified" = false;
"toolkit.telemetry.unifiedIsOptIn" = false;
"toolkit.telemetry.updatePing.enabled" = false;
};
};
};
}

View file

@ -2,9 +2,11 @@
config = {
programs = {
git = {
userName = "Leyla Becker";
userEmail = "git@jan-leila.com";
extraConfig.init.defaultBranch = "main";
settings = {
user.name = "Leyla Becker";
user.email = "git@jan-leila.com";
init.defaultBranch = "main";
};
};
};
};

View file

@ -69,6 +69,9 @@ in {
# go development
go.enable = true;
# rust development
rustAnalyzer.enable = true;
# claude development
claudeDev = lib.mkIf ai-tooling-enabled {
enable = true;

View file

@ -102,18 +102,6 @@
directories = ["leyla_documents" "eve_documents" "users_documents" "media"];
};
};
reverse_proxy = {
enable = true;
enableACME = true;
hostname = "jan-leila.com";
};
postgres = {
extraUsers = {
leyla = {
isAdmin = true;
};
};
};
};
systemd.network = {
@ -225,6 +213,12 @@
};
services = {
# PostgreSQL database server
postgresql = {
enable = true;
adminUsers = ["leyla"];
};
# temp enable desktop environment for setup
# Enable the X11 windowing system.
xserver.enable = true;
@ -237,6 +231,16 @@
gnome.enable = true;
};
# Enable new reverse proxy system
reverseProxy = {
enable = true;
openFirewall = true;
acme = {
enable = true;
email = "jan-leila@protonmail.com";
};
};
ollama = {
enable = true;
exposePort = true;
@ -294,35 +298,35 @@
jellyfin = {
enable = true;
subdomain = "media";
extraSubdomains = ["jellyfin"];
domain = "media.jan-leila.com";
extraDomains = ["jellyfin.jan-leila.com"];
};
immich = {
enable = true;
subdomain = "photos";
domain = "photos.jan-leila.com";
};
forgejo = {
enable = true;
subdomain = "git";
reverseProxy.domain = "git.jan-leila.com";
};
searx = {
enable = true;
subdomain = "search";
domain = "search.jan-leila.com";
};
actual = {
enable = false;
subdomain = "budget";
enable = true;
domain = "budget.jan-leila.com";
};
home-assistant = {
enable = true;
subdomain = "home";
domain = "home.jan-leila.com";
openFirewall = true;
database = "postgres";
postgres.enable = true;
extensions = {
sonos.enable = true;
@ -333,7 +337,7 @@
paperless = {
enable = true;
subdomain = "documents";
domain = "documents.jan-leila.com";
passwordFile = config.sops.secrets."services/paperless_password".path;
};
@ -348,12 +352,13 @@
openFirewall = true;
show_doc = true;
downstreams = {
loopback = {
host = {
enable = true;
openFirewall = true;
};
};
upstreams.cloudFlare.enable = true;
blocklists.ad_malware.enable = true;
};
qbittorrent = {

151
flake.lock generated
View file

@ -25,11 +25,11 @@
]
},
"locked": {
"lastModified": 1760701190,
"narHash": "sha256-y7UhnWlER8r776JsySqsbTUh2Txf7K30smfHlqdaIQw=",
"lastModified": 1763651264,
"narHash": "sha256-8vvwZbw0s7YvBMJeyPVpWke6lg6ROgtts5N2/SMCcv4=",
"owner": "nix-community",
"repo": "disko",
"rev": "3a9450b26e69dcb6f8de6e2b07b3fc1c288d85f5",
"rev": "e86a89079587497174ccab6d0d142a65811a4fd9",
"type": "github"
},
"original": {
@ -46,11 +46,11 @@
},
"locked": {
"dir": "pkgs/firefox-addons",
"lastModified": 1760673822,
"narHash": "sha256-h+liPhhMw1yYvkDGLHzQJQShQs+yLjNgjfAyZX+sRrM=",
"lastModified": 1763697825,
"narHash": "sha256-AgCCcVPOi1tuzuW5/StlwqBjRWSX62oL97qWuxrq5UA=",
"owner": "rycee",
"repo": "nur-expressions",
"rev": "5cca27f1bb30a26140d0cf60ab34daa45b4fa11f",
"rev": "cefce78793603231be226fa77e7ad58e0e4899b8",
"type": "gitlab"
},
"original": {
@ -62,11 +62,11 @@
},
"flake-compat": {
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"lastModified": 1761588595,
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
@ -133,11 +133,11 @@
]
},
"locked": {
"lastModified": 1760662441,
"narHash": "sha256-mlDqR1Ntgs9uYYEAUR1IhamKBO0lxoNS4zGLzEZaY0A=",
"lastModified": 1763748372,
"narHash": "sha256-AUc78Qv3sWir0hvbmfXoZ7Jzq9VVL97l+sP9Jgms+JU=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "722792af097dff5790f1a66d271a47759f477755",
"rev": "d10a9b16b2a3ee28433f3d1c603f4e9f1fecb8e1",
"type": "github"
},
"original": {
@ -164,11 +164,11 @@
"lix": {
"flake": false,
"locked": {
"lastModified": 1755787066,
"narHash": "sha256-X2UwkUEban08GRSPXRr+kz8fckHqebr3P77qSvjoeOw=",
"rev": "ac9721a92e8138d29707824dbedb484c76948493",
"lastModified": 1761937274,
"narHash": "sha256-KlELhsSq3XbemrGyQhmGurFu7m8wOEBw+8M04L7hn7A=",
"rev": "91867941fa73afea7869b7c71ede82e5ef8927da",
"type": "tarball",
"url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/ac9721a92e8138d29707824dbedb484c76948493.tar.gz?rev=ac9721a92e8138d29707824dbedb484c76948493"
"url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/91867941fa73afea7869b7c71ede82e5ef8927da.tar.gz?rev=91867941fa73afea7869b7c71ede82e5ef8927da"
},
"original": {
"type": "tarball",
@ -185,11 +185,11 @@
]
},
"locked": {
"lastModified": 1759851320,
"narHash": "sha256-n5dRAIC3/78drQtFxmQRrBLd6TKfotUnX7GWu0mAcSg=",
"lastModified": 1763435414,
"narHash": "sha256-i2467FddWfd19q5Qoj+1/BAeg6LZmM5m4mYGRSQn/as=",
"ref": "refs/heads/main",
"rev": "7c31a18259b8358ac196cf803a26967c0fa1d3e4",
"revCount": 163,
"rev": "192c92b603731fbc1bade6c1b18c8d8a0086f703",
"revCount": 169,
"type": "git",
"url": "https://git.lix.systems/lix-project/nixos-module.git"
},
@ -207,11 +207,11 @@
]
},
"locked": {
"lastModified": 1759342933,
"narHash": "sha256-mdlUFcrOfvT0Pm+Hko/6aR3xf1ao5JA2iem4KsEVjP4=",
"lastModified": 1760821194,
"narHash": "sha256-UCsJ8eDuHL14u2GFIYEY/drtZ6jht5zN/G/6QNlEy2g=",
"owner": "utensils",
"repo": "mcp-nixos",
"rev": "50b02bcba32b941d2ec48fedef68641702ca5b0f",
"rev": "0ae453f38d0f088c31d4678da3a12b183165986f",
"type": "github"
},
"original": {
@ -227,11 +227,11 @@
]
},
"locked": {
"lastModified": 1760721282,
"narHash": "sha256-aAHphQbU9t/b2RRy2Eb8oMv+I08isXv2KUGFAFn7nCo=",
"lastModified": 1763505477,
"narHash": "sha256-nJRd4LY2kT3OELfHqdgWjvToNZ4w+zKCMzS2R6z4sXE=",
"owner": "LnL7",
"repo": "nix-darwin",
"rev": "c3211fcd0c56c11ff110d346d4487b18f7365168",
"rev": "3bda9f6b14161becbd07b3c56411f1670e19b9b5",
"type": "github"
},
"original": {
@ -268,11 +268,11 @@
]
},
"locked": {
"lastModified": 1760720017,
"narHash": "sha256-ALb+L8zaP6IJ3BigQJ+ih7NqmaptzL/CbkNkLbhmsGE=",
"lastModified": 1763690163,
"narHash": "sha256-MMl9P8f17unCvlk2IAinnMq/P72f51UUHVRIYnojT7w=",
"owner": "nix-community",
"repo": "nix-vscode-extensions",
"rev": "b0897a5d1d5829eb67ca7168680873ee7a0d52b8",
"rev": "590349d9faeb398a037205c2927ffbaede980539",
"type": "github"
},
"original": {
@ -283,11 +283,11 @@
},
"nixos-hardware": {
"locked": {
"lastModified": 1760106635,
"narHash": "sha256-2GoxVaKWTHBxRoeUYSjv0AfSOx4qw5CWSFz2b+VolKU=",
"lastModified": 1762847253,
"narHash": "sha256-BWWnUUT01lPwCWUvS0p6Px5UOBFeXJ8jR+ZdLX8IbrU=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "9ed85f8afebf2b7478f25db0a98d0e782c0ed903",
"rev": "899dc449bc6428b9ee6b3b8f771ca2b0ef945ab9",
"type": "github"
},
"original": {
@ -315,11 +315,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1760524057,
"narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=",
"lastModified": 1763421233,
"narHash": "sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5",
"rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648",
"type": "github"
},
"original": {
@ -329,6 +329,43 @@
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1759070547,
"narHash": "sha256-JVZl8NaVRYb0+381nl7LvPE+A774/dRpif01FKLrYFQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "647e5c14cbd5067f44ac86b74f014962df460840",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"noita-entangled-worlds": {
"inputs": {
"nixpkgs": "nixpkgs_3",
"rust-overlay": "rust-overlay",
"systems": "systems_3"
},
"locked": {
"lastModified": 1764204484,
"narHash": "sha256-S45ghD/YjcKDy8Mz3DYklLMaA/z6f6mTbx0i7pAktYk=",
"owner": "IntQuant",
"repo": "noita_entangled_worlds",
"rev": "ab2c2162157140ab519fa19f6737c044e1ed0e3b",
"type": "github"
},
"original": {
"owner": "IntQuant",
"ref": "master",
"repo": "noita_entangled_worlds",
"type": "github"
}
},
"root": {
"inputs": {
"disko": "disko",
@ -343,10 +380,32 @@
"nix-vscode-extensions": "nix-vscode-extensions",
"nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_2",
"noita-entangled-worlds": "noita-entangled-worlds",
"secrets": "secrets",
"sops-nix": "sops-nix"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"noita-entangled-worlds",
"nixpkgs"
]
},
"locked": {
"lastModified": 1759199574,
"narHash": "sha256-w24RYly3VSVKp98rVfCI1nFYfQ0VoWmShtKPCbXgK6A=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "381776b12d0d125edd7c1930c2041a1471e586c0",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"secrets": {
"flake": false,
"locked": {
@ -370,11 +429,11 @@
]
},
"locked": {
"lastModified": 1760393368,
"narHash": "sha256-8mN3kqyqa2PKY0wwZ2UmMEYMcxvNTwLaOrrDsw6Qi4E=",
"lastModified": 1763607916,
"narHash": "sha256-VefBA1JWRXM929mBAFohFUtQJLUnEwZ2vmYUNkFnSjE=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "ab8d56e85b8be14cff9d93735951e30c3e86a437",
"rev": "877bb495a6f8faf0d89fc10bd142c4b7ed2bcc0b",
"type": "github"
},
"original": {
@ -412,6 +471,22 @@
"repo": "default",
"type": "github"
}
},
"systems_3": {
"flake": false,
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",

View file

@ -77,6 +77,12 @@
url = "github:utensils/mcp-nixos";
inputs.nixpkgs.follows = "nixpkgs";
};
# Noita Entangled Worlds package
# Not following our nixpkgs so it can use its own rust-overlay configuration
noita-entangled-worlds = {
url = "github:IntQuant/noita_entangled_worlds/master";
};
};
outputs = {
@ -91,15 +97,9 @@
util = import ./util {inherit inputs;};
forEachPkgs = util.forEachPkgs;
mkNixosInstaller = util.mkNixosInstaller;
mkNixosSystem = util.mkNixosSystem;
mkDarwinSystem = util.mkDarwinSystem;
mkHome = util.mkHome;
syncthingConfiguration = util.syncthingConfiguration;
installerSystems = {
basic = mkNixosInstaller "basic" [];
};
nixosSystems = {
horizon = mkNixosSystem "horizon";
@ -170,14 +170,10 @@
};
});
installerConfigurations = installerSystems;
nixosConfigurations = nixosSystems;
darwinConfigurations = darwinSystems;
homeConfigurations = homeConfigurations;
syncthingConfiguration = syncthingConfiguration;
};
}

View file

@ -2,5 +2,9 @@
{inputs, ...}: {
nixpkgs.overlays = [
inputs.nix-vscode-extensions.overlays.default
# Add noita_entangled_worlds from upstream flake to pkgs
(final: prev: {
noita_entangled_worlds = inputs.noita-entangled-worlds.packages.${prev.stdenv.hostPlatform.system}.noita-proxy;
})
];
}

View file

@ -1,4 +1,8 @@
{pkgs, ...}: {
{
pkgs,
inputs,
...
}: {
imports = [
./python
];
@ -16,15 +20,17 @@
./prostudiomasters.nix
{};
})
(final: prev: {
noita_entangled_worlds = pkgs.callPackage ./noita-entangled-worlds.nix {};
})
(final: prev: {
gdx-liftoff = pkgs.callPackage ./gdx-liftoff.nix {};
})
(final: prev: {
codium-extensions = pkgs.callPackage ./codium-extensions {};
})
(final: prev: {
firefox-extensions = pkgs.callPackage ./firefox-extensions {
inherit inputs;
};
})
(final: prev: {
mapillary-uploader = pkgs.callPackage ./mapillary-uploader.nix {};
})
@ -38,8 +44,5 @@
# Override h3 C library to version 4.3.0
h3 = pkgs.callPackage ./h3-c-lib.nix {};
})
(final: prev: {
polycule = pkgs.callPackage ./polycule {};
})
];
}

View file

@ -0,0 +1,17 @@
{
pkgs,
inputs,
...
}: let
inherit (inputs.firefox-addons.lib.${pkgs.stdenv.hostPlatform.system}) buildFirefoxXpiAddon;
in {
italiano-it-language-pack = pkgs.callPackage ./italiano-it-language-pack.nix {
inherit buildFirefoxXpiAddon;
};
dizionario-italiano = pkgs.callPackage ./dizionario-italiano.nix {
inherit buildFirefoxXpiAddon;
};
deutsch-de-language-pack = pkgs.callPackage ./deutsch-de-language-pack.nix {
inherit buildFirefoxXpiAddon;
};
}

View file

@ -0,0 +1,18 @@
{
lib,
buildFirefoxXpiAddon,
...
}:
buildFirefoxXpiAddon rec {
pname = "deutsch-de-language-pack";
version = "145.0.20251106.194447";
addonId = "langpack-de@firefox.mozilla.org";
url = "https://addons.mozilla.org/firefox/downloads/file/4614311/deutsch_de_language_pack-${version}.xpi";
sha256 = "aaaa95c29984fb3802a5e7edb6b7e5020c391d81f389b8a8133c163959ea4299";
meta = with lib; {
description = "Firefox Language Pack for Deutsch (de) German";
license = licenses.mpl20;
mozPermissions = [];
platforms = platforms.all;
};
}

View file

@ -0,0 +1,18 @@
{
lib,
buildFirefoxXpiAddon,
...
}:
buildFirefoxXpiAddon rec {
pname = "dizionario-italiano";
version = "5.1";
addonId = "it-IT@dictionaries.addons.mozilla.org";
url = "https://addons.mozilla.org/firefox/downloads/file/3693497/dizionario_italiano-${version}.xpi";
sha256 = "90b173ffdde34a77108152a5ff51879767b1dd84e0aa0dfb7b2bab94cd2e7f53";
meta = with lib; {
description = "Add support for Italian to spellchecking";
license = licenses.gpl3;
mozPermissions = [];
platforms = platforms.all;
};
}

View file

@ -0,0 +1,18 @@
{
lib,
buildFirefoxXpiAddon,
...
}:
buildFirefoxXpiAddon rec {
pname = "italiano-it-language-pack";
version = "145.0.20251106.194447";
addonId = "langpack-it@firefox.mozilla.org";
url = "https://addons.mozilla.org/firefox/downloads/file/4614309/italiano_it_language_pack-${version}.xpi";
sha256 = "1eb271cedbf326543e222ba1b9a1da62fceef9d3c523ac02a098df296f155038";
meta = with lib; {
description = "Firefox Language Pack for Italiano (it) Italian";
license = licenses.mpl20;
mozPermissions = [];
platforms = platforms.all;
};
}

View file

@ -9,7 +9,7 @@
src = fetchurl {
url = "http://tools.mapillary.com/uploader/download/linux/${version}";
name = "mapillary-uploader.AppImage";
sha256 = "sha256-OY3SiMHUyjwPDrPWfa+mFg2BHZrz6GG/9/D5sCP2Da8=";
sha256 = "sha256-hpWdfeuhYylO+SFD3BsKI0s/xtObCDd5OcuJ6i/aEuI=";
};
appimageContents = appimageTools.extractType2 {

View file

@ -1,46 +0,0 @@
# not working yet
{
pkgs,
rustPlatform,
fetchFromGitHub,
...
}: let
version = "1.5.3";
repo = fetchFromGitHub {
owner = "IntQuant";
repo = "noita_entangled_worlds";
rev = "v${version}";
hash = "sha256-frrpD0aWTeDbZYtp15R+quUUAZf7OvHlbSLtGJJtAqk=";
};
in
rustPlatform.buildRustPackage {
name = "noita-proxy-${version}";
src = repo + "/noita-proxy";
prePatch = ''
substituteInPlace Cargo.toml \
--replace "path = \"../shared\"" "path = \"${repo + "/shared"}\""
'';
nativeBuildInputs = with pkgs; [
pkg-config
python3
cmake
];
buildInputs = with pkgs; [
openssl
openssl.dev
libpulseaudio
libjack2
alsa-lib
xorg.libxcb
xorg.libxcb.dev
libopus
];
propagatedBuildInputs = with pkgs; [
steamworks-sdk-redist
];
runtimeDependencies = with pkgs; [
steamworks-sdk-redist
];
doCheck = false;
cargoHash = "sha256-TzUS6d6PopgGf2i1yVaXaXdzNrvfSz+Gv67BAtxYmb4=";
}

View file

@ -1,149 +0,0 @@
{
lib,
flutter332,
fetchFromGitLab,
pkg-config,
wrapGAppsHook,
gtk3,
glib,
glib-networking,
webkitgtk_4_1,
libsecret,
libnotify,
dbus,
sqlcipher,
openssl,
mpv,
alsa-lib,
libass,
ffmpeg-full,
libplacebo,
libunwind,
shaderc,
vulkan-headers,
vulkan-loader,
lcms2,
libdovi,
libdvdnav,
libdvdread,
mujs,
libbluray,
lua,
rubberband,
libuchardet,
zimg,
openal,
pipewire,
libpulseaudio,
libcaca,
libdrm,
libdisplay-info,
libgbm,
xorg,
nv-codec-headers-11,
libva,
libvdpau,
}:
flutter332.buildFlutterApplication rec {
pname = "polycule";
version = "0.3.4";
src = fetchFromGitLab {
owner = "polycule_client";
repo = "polycule";
rev = "v${version}";
hash = "sha256-RUu8DKuX2NUU5Ce5WLHtDaORkn7CSrgTj3KhM/z+yHc=";
};
pubspecLock = lib.importJSON ./polycule-pubspec.lock.json;
gitHashes = {
matrix = "sha256-w/QB5nYJ9Lh77TcYKEN/DnNQjWfp+9NX0dwQ9GOzWE8=";
media_kit = "sha256-1sVX+aHFLFJBtrNZrR6tWkb80vFELW2N9EejyQKlBPg=";
media_kit_libs_android_video = "sha256-N6QoktM8u9NYF8MAXLsxM9RlV8nICM4NbnmABHTRkZg=";
};
nativeBuildInputs = [
pkg-config
wrapGAppsHook
];
buildInputs = [
gtk3
glib
glib-networking
webkitgtk_4_1
libsecret
libnotify
dbus
sqlcipher
openssl
mpv
alsa-lib
libass
ffmpeg-full
libplacebo
libunwind
shaderc
vulkan-headers
vulkan-loader
lcms2
libdovi
libdvdnav
libdvdread
mujs
libbluray
lua
rubberband
libuchardet
zimg
openal
pipewire
libpulseaudio
libcaca
libdrm
libdisplay-info
libgbm
xorg.libXScrnSaver
xorg.libXpresent
nv-codec-headers-11
libva
libvdpau
];
flutterBuildFlags = [
"--release"
"--target"
"lib/main.dart"
"--dart-define=POLYCULE_VERSION=v${version}"
"--dart-define=POLYCULE_IS_STABLE=true"
"--no-tree-shake-icons"
];
postInstall = ''
# Install desktop files and icons from the source
install -Dm644 linux/business.braid.polycule.desktop $out/share/applications/polycule.desktop
install -Dm644 assets/logo/logo-circle.png $out/share/pixmaps/polycule.png
# Update desktop file to use correct executable name
substituteInPlace $out/share/applications/polycule.desktop \
--replace 'Exec=business.braid.polycule' 'Exec=polycule'
# Create a symlink with the expected name
ln -sf $out/bin/polycule $out/bin/business.braid.polycule
'';
meta = with lib; {
description = "A geeky and efficient [matrix] client for power users";
longDescription = ''
Polycule is a modern Matrix client built with Flutter, designed for power users
who want a fast, efficient, and feature-rich Matrix experience.
'';
homepage = "https://polycule.im/";
license = licenses.eupl12;
maintainers = [];
platforms = ["x86_64-linux" "aarch64-linux"];
sourceProvenance = with sourceTypes; [fromSource];
mainProgram = "polycule";
};
}

File diff suppressed because it is too large Load diff

View file

@ -8,21 +8,25 @@
in {
options.impermanence = {
enable = lib.mkEnableOption "impermanence for home directory";
fallbackPersistence.enable = lib.mkOption {
type = lib.types.bool;
default = true;
};
};
config = lib.mkMerge [
(lib.mkIf config.impermanence.enable {
assertions = [
{
assertion = osConfig.impermanence.enable;
message = "impermanence can not be enabled for a user when it is not enabled for a configuration";
assertion = osConfig.host.impermanence.enable;
message = "impermanence can not be enabled for a user when it is not enabled for the system";
}
];
})
(lib.mkIf osConfig.host.impermanence.enable {
# If impermanence is not enabled for this user but system impermanence is enabled,
# persist the entire home directory as fallback
home.persistence."/persist/home/${config.home.username}" = lib.mkIf (!cfg.enable) {
(lib.mkIf (osConfig.host.impermanence.enable && !cfg.enable && cfg.fallbackPersistence.enable) {
home.persistence."/persist/home/${config.home.username}" = {
directories = ["."];
allowOther = true;
};

View file

@ -11,7 +11,7 @@
config = lib.mkIf config.programs.bitwarden.enable (lib.mkMerge [
{
home.packages = with pkgs; [
bitwarden
bitwarden-desktop
];
}
(

View file

@ -31,6 +31,7 @@
./freecad.nix
./onionshare.nix
./mfoc.nix
./noita-entangled-worlds.nix
./pdfarranger.nix
./picard.nix
./qflipper.nix
@ -43,7 +44,6 @@
./davinci-resolve.nix
./gdx-liftoff.nix
./tor-browser.nix
./polycule.nix
./vmware-workstation.nix
];
}

View file

@ -1,19 +1,9 @@
{
lib,
pkgs,
config,
...
}: {
options.programs.discord = {
enable = lib.mkEnableOption "enable discord";
};
config = lib.mkIf config.programs.discord.enable (lib.mkMerge [
{
home.packages = with pkgs; [
discord
];
}
(
lib.mkIf config.impermanence.enable {
home.persistence."/persist${config.home.homeDirectory}" = {

View file

@ -0,0 +1,18 @@
{
lib,
pkgs,
config,
...
}: {
options = {
programs.noita-entangled-worlds = {
enable = lib.mkEnableOption "Noita Entangled Worlds multiplayer mod";
};
};
config = lib.mkIf config.programs.noita-entangled-worlds.enable {
home.packages = with pkgs; [
noita_entangled_worlds
];
};
}

View file

@ -1,31 +0,0 @@
{
lib,
pkgs,
config,
...
}: {
options.programs.polycule = {
enable = lib.mkEnableOption "enable polycule matrix client";
package = lib.mkPackageOption pkgs "polycule" {};
};
config = lib.mkIf config.programs.polycule.enable (lib.mkMerge [
{
home.packages = [
config.programs.polycule.package
];
}
(
lib.mkIf config.impermanence.enable {
home.persistence."/persist${config.home.homeDirectory}" = {
# TODO: check that these are actually the correct folders
# directories = [
# "${config.xdg.configHome}/polycule"
# "${config.xdg.dataHome}/polycule"
# "${config.xdg.cacheHome}/polycule"
# ];
};
}
)
]);
}

View file

@ -19,6 +19,10 @@ in {
gitmoji = lib.mkEnableOption "should emoji be prompted for as a part of the commit message./";
promptScopes = lib.mkEnableOption "prompting for scopes in conventional commits";
promptFooter = lib.mkEnableOption "prompting for footer in conventional commits";
showNewVersionNotes = lib.mkEnableOption "showing new version notes for conventional commits";
};
};
config = lib.mkIf config.extraExtensions.conventionalCommits.enable {
@ -27,6 +31,8 @@ in {
userSettings = {
"conventionalCommits.gitmoji" = config.extraExtensions.conventionalCommits.gitmoji;
"conventionalCommits.promptScopes" = config.extraExtensions.conventionalCommits.promptScopes;
"conventionalCommits.promptFooter" = config.extraExtensions.conventionalCommits.promptFooter;
"conventionalCommits.showNewVersionNotes" = config.extraExtensions.conventionalCommits.showNewVersionNotes;
};
};
}));

View file

@ -16,6 +16,7 @@
./go.nix
./evenBetterToml.nix
./openRemoteSsh.nix
./rustAnalyzer.nix
./astroVscode.nix
./vscodeMdx.nix
./claudeDev.nix

View file

@ -0,0 +1,27 @@
{
lib,
pkgs,
config,
...
}: let
pkgsRepositories = pkgs.nix-vscode-extensions.forVSCodeVersion config.programs.vscode.package.version;
pkgsRepository = pkgsRepositories.open-vsx;
in {
options.programs.vscode.profiles = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({config, ...}: {
options = {
extraExtensions.rustAnalyzer = {
enable = lib.mkEnableOption "should the rust-analyzer extension for vscode be enabled";
extension = lib.mkPackageOption pkgsRepository "rust-analyzer" {
default = ["rust-lang" "rust-analyzer"];
};
};
};
config = lib.mkIf config.extraExtensions.rustAnalyzer.enable {
extensions = [
config.extraExtensions.rustAnalyzer.extension
];
};
}));
};
}

View file

@ -3,6 +3,39 @@
lib,
...
}: {
# options.storage = {
# zfs = {
# # TODO: enable option
# # when this option is enabled we need to configure and enable disko things
# # TODO: we need some way of managing notifications
# # TODO: we need options to configure zfs pools
# # we should have warnings when the configured pool is missing drives
# # TODO: dataset option that is a submodule that adds datasets to the system
# # warnings for when a dataset was created in the past on a system but it is now missing some of the options defined for it
# # TODO: pools and datasets need to be passed to disko
# };
# impermanence = {
# # TODO: enable option
# # TODO: datasets option that is a submodule that will be used to define what datasets to add to the storage system
# # We should by default create the `local`, `local/system/nix`, `local/system/root`, `persist` `persist/system/root`, and `persist/system/var/log` datasets
# # Then we should make a dataset for user folders local and persist
# # We should also create datasets for systemd modules that have have impermanence enabled for them
# # we need to figure out what options a dataset can have in zfs
# };
# # TODO: we should have an impermanence module for home manager that proxies its values namespaced to the user down here that matches the same interface
# # TODO: we should have a way of enabling impermanence for a systemd config
# # these should have an option to put their folder into their own dataset (this needs to support private vs non private)
# # options for features that can be added to the dataset
# };
options.host.impermanence.enable = lib.mkEnableOption "are we going to use impermanence on this device";
config = lib.mkMerge [
@ -93,6 +126,7 @@
];
};
# TODO: this should live in leylas home manager configuration
security.sudo.extraConfig = "Defaults lecture=never";
}
)

View file

@ -0,0 +1,24 @@
{
lib,
config,
...
}: let
const = import ./const.nix;
dataDirectory = const.dataDirectory;
in {
options.services.actual = {
port = lib.mkOption {
type = lib.types.port;
description = "The port to listen on";
default = 5006;
};
};
config = lib.mkIf config.services.actual.enable {
services.actual = {
settings = {
port = config.services.actual.port;
dataDir = dataDirectory;
};
};
};
}

View file

@ -1,3 +1,3 @@
{
dataDirectory = "/var/lib/actual/";
dataDirectory = "/var/lib/private/actual";
}

View file

@ -1,26 +1,8 @@
{
lib,
config,
...
}: let
const = import ./const.nix;
dataDirectory = const.dataDirectory;
in {
imports = [
./actual.nix
./proxy.nix
./fail2ban.nix
./impermanence.nix
];
config = lib.mkIf config.services.actual.enable {
systemd.tmpfiles.rules = [
"d ${dataDirectory} 2770 actual actual"
];
services.actual = {
settings = {
ACTUAL_DATA_DIR = dataDirectory;
};
};
};
}

View file

@ -6,11 +6,22 @@
const = import ./const.nix;
dataDirectory = const.dataDirectory;
in {
config = lib.mkIf (config.services.actual.enable && config.host.impermanence.enable) {
options.services.actual = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.actual.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.actual.impermanence.enable {
assertions = [
{
assertion = config.services.actual.settings.ACTUAL_DATA_DIR == dataDirectory;
message = "actual data location does not match persistence";
assertion = config.services.actual.settings.dataDir == dataDirectory;
message = "actual data location does not match persistence\nconfig directory: ${config.services.actual.settings.dataDir}\npersistence directory: ${dataDirectory}";
}
{
assertion = config.systemd.services.actual.serviceConfig.DynamicUser or false;
message = "actual systemd service must have DynamicUser enabled to use private directory";
}
];
environment.persistence."/persist/system/root" = {

View file

@ -4,17 +4,30 @@
...
}: {
options.services.actual = {
subdomain = lib.mkOption {
domain = lib.mkOption {
type = lib.types.str;
default = "actual";
description = "subdomain of base domain that actual will be hosted at";
description = "domain that actual will be hosted at";
default = "actual.arpa";
};
extraDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "extra domains that should be configured for actual";
default = [];
};
reverseProxy.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.actual.enable && config.services.reverseProxy.enable;
};
};
config = lib.mkIf (config.services.actual.enable && config.host.reverse_proxy.enable) {
host = {
reverse_proxy.subdomains.${config.services.actual.subdomain} = {
config = lib.mkIf config.services.actual.reverseProxy.enable {
services.reverseProxy.services.actual = {
target = "http://localhost:${toString config.services.actual.settings.port}";
domain = config.services.actual.domain;
extraDomains = config.services.actual.extraDomains;
settings = {
forwardHeaders.enable = true;
};
};
};

View file

@ -1,6 +1,5 @@
{...}: {
imports = [
./proxy.nix
./impermanence.nix
];
}

View file

@ -5,7 +5,14 @@
}: let
bazarr_data_directory = "/var/lib/bazarr";
in {
config = lib.mkIf (config.services.bazarr.enable && config.host.impermanence.enable) {
options.services.bazarr = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.bazarr.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.bazarr.impermanence.enable {
assertions = [
{
assertion = config.services.bazarr.dataDir == bazarr_data_directory;

View file

@ -1,28 +0,0 @@
{
lib,
config,
...
}: {
options.services.bazarr = {
subdomain = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Subdomain for reverse proxy. If null, service will be local only.";
};
extraSubdomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "Extra subdomains for reverse proxy.";
};
};
config = lib.mkIf (config.services.bazarr.enable && config.services.bazarr.subdomain != null) {
host.reverse_proxy.subdomains.bazarr = {
subdomain = config.services.bazarr.subdomain;
extraSubdomains = config.services.bazarr.extraSubdomains;
target = "http://127.0.0.1:6767";
websockets.enable = true;
forwardHeaders.enable = true;
};
};
}

View file

@ -27,9 +27,19 @@ in {
show_doc = lib.mkEnableOption "OpenAPI documentation (loads content from third party websites)";
downstreams = {
loopback = {
enable = lib.mkEnableOption "loopback downstream DNS server on localhost:53";
openFirewall = lib.mkEnableOption "automatic port forwarding for the loopback downstream";
host = {
enable = lib.mkEnableOption "host downstream DNS server accessible from network on all interfaces";
port = lib.mkOption {
type = lib.types.port;
default = 53;
description = "Port for the host downstream DNS server to listen on.";
};
openFirewall = lib.mkEnableOption "automatic port forwarding for the host downstream";
disableSystemdResolved = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to automatically disable systemd-resolved when using port 53. Set to false if you want to handle the conflict manually.";
};
};
};
@ -79,9 +89,44 @@ in {
default = [];
description = "List of additional upstream DNS server configurations.";
};
blocklists = {
ad_malware = {
enable = lib.mkEnableOption "Host file for blocking ads and malware";
url = lib.mkOption {
type = lib.types.str;
default = "http://sbc.io/hosts/hosts";
description = "URL of the ad and malware blocklist host file";
};
};
};
extraBlocklists = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "Additional blocklist URLs to be added to the configuration";
};
};
config = lib.mkIf cfg.enable {
# Assertions for proper configuration
assertions = [
{
assertion = !(cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && config.services.resolved.enable && cfg.downstreams.host.disableSystemdResolved);
message = "crab-hole host downstream cannot use port 53 while systemd-resolved is enabled. Either disable systemd-resolved or use a different port.";
}
{
assertion = !(cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && !cfg.downstreams.host.disableSystemdResolved && config.services.resolved.enable);
message = "crab-hole host downstream is configured to use port 53 but systemd-resolved is still enabled and disableSystemdResolved is false. Set disableSystemdResolved = true or manually disable systemd-resolved.";
}
];
# Automatically disable systemd-resolved if using port 53
services.resolved.enable = lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && cfg.downstreams.host.disableSystemdResolved) (lib.mkForce false);
# Configure DNS nameservers when disabling systemd-resolved
networking.nameservers = lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.port == 53 && cfg.downstreams.host.disableSystemdResolved) (lib.mkDefault ["127.0.0.1" "1.1.1.1" "8.8.8.8"]);
services.crab-hole.settings = lib.mkMerge [
{
api = {
@ -91,13 +136,17 @@ in {
};
downstream = cfg.extraDownstreams;
upstream.name_servers = cfg.extraUpstreams;
blocklist.lists = cfg.extraBlocklists;
}
(lib.mkIf cfg.downstreams.loopback.enable {
(lib.mkIf cfg.blocklists.ad_malware.enable {
blocklist.lists = [cfg.blocklists.ad_malware.url];
})
(lib.mkIf cfg.downstreams.host.enable {
downstream = [
{
protocol = "udp";
listen = "localhost";
port = 53;
listen = "0.0.0.0";
port = cfg.downstreams.host.port;
}
];
})
@ -136,8 +185,8 @@ in {
(lib.mkIf cfg.openFirewall {
allowedTCPPorts = [cfg.port];
})
(lib.mkIf (cfg.downstreams.loopback.enable && cfg.downstreams.loopback.openFirewall) {
allowedUDPPorts = [53];
(lib.mkIf (cfg.downstreams.host.enable && cfg.downstreams.host.openFirewall) {
allowedUDPPorts = [cfg.downstreams.host.port];
})
];
};

View file

@ -5,7 +5,14 @@
}: let
workingDirectory = "/var/lib/private/crab-hole";
in {
config = lib.mkIf (config.services.immich.enable && config.host.impermanence.enable) {
options.services.crab-hole = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.crab-hole.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.crab-hole.impermanence.enable {
assertions = [
{
assertion =

View file

@ -1,10 +1,9 @@
{...}: {
imports = [
./reverse_proxy.nix
./fail2ban.nix
./postgres.nix
./reverseProxy
./fail2ban
./postgres
./network_storage
./podman.nix
./actual
./bazarr
@ -18,7 +17,7 @@
./lidarr
./panoramax
./paperless
./qbittorent.nix
./qbittorent
./radarr
./searx
./sonarr

View file

@ -1,74 +0,0 @@
{
lib,
pkgs,
config,
...
}: let
dataFolder = "/var/lib/fail2ban";
dataFile = "fail2ban.sqlite3";
in {
config = lib.mkIf config.services.fail2ban.enable (lib.mkMerge [
{
environment.etc = {
"fail2ban/filter.d/nginx.local".text = lib.mkIf config.services.nginx.enable (
pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
[Definition]
failregex = "limiting requests, excess:.* by zone.*client: <HOST>"
'')
);
};
services.fail2ban = {
maxretry = 5;
ignoreIP = [
# Whitelist local networks
"10.0.0.0/8"
"172.16.0.0/12"
"192.168.0.0/16"
# tail scale tailnet
"100.64.0.0/10"
"fd7a:115c:a1e0::/48"
];
bantime = "24h"; # Ban IPs for one day on the first ban
bantime-increment = {
enable = true; # Enable increment of bantime after each violation
formula = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
maxtime = "168h"; # Do not ban for more than 1 week
overalljails = true; # Calculate the ban time based on all the violations
};
jails = {
nginx-iptables.settings = lib.mkIf config.services.nginx.enable {
enabled = true;
filter = "nginx";
action = ''iptables-multiport[name=HTTP, port="http,https"]'';
backend = "auto";
findtime = 600;
bantime = 600;
maxretry = 5;
};
# TODO; figure out if there is any fail2ban things we can do on searx
# searx-iptables.settings = lib.mkIf config.services.searx.enable {};
};
};
}
(lib.mkIf config.host.impermanence.enable {
assertions = [
{
assertion = config.services.fail2ban.daemonSettings.Definition.dbfile == "${dataFolder}/${dataFile}";
message = "fail2ban data file does not match persistence";
}
];
environment.persistence."/persist/system/root" = {
directories = [
{
directory = dataFolder;
user = "fail2ban";
group = "fail2ban";
}
];
};
})
]);
}

View file

@ -0,0 +1,6 @@
{...}: {
imports = [
./fail2ban.nix
./impermanence.nix
];
}

View file

@ -0,0 +1,51 @@
{
lib,
pkgs,
config,
...
}: {
config = lib.mkIf config.services.fail2ban.enable {
environment.etc = {
"fail2ban/filter.d/nginx.local".text = lib.mkIf config.services.nginx.enable (
pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
[Definition]
failregex = "limiting requests, excess:.* by zone.*client: <HOST>"
'')
);
};
services.fail2ban = {
maxretry = 5;
ignoreIP = [
# Whitelist local networks
"10.0.0.0/8"
"172.16.0.0/12"
"192.168.0.0/16"
# tail scale tailnet
"100.64.0.0/10"
"fd7a:115c:a1e0::/48"
];
bantime = "24h"; # Ban IPs for one day on the first ban
bantime-increment = {
enable = true; # Enable increment of bantime after each violation
formula = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
maxtime = "168h"; # Do not ban for more than 1 week
overalljails = true; # Calculate the ban time based on all the violations
};
jails = {
nginx-iptables.settings = lib.mkIf config.services.nginx.enable {
enabled = true;
filter = "nginx";
action = ''iptables-multiport[name=HTTP, port="http,https"]'';
backend = "auto";
findtime = 600;
bantime = 600;
maxretry = 5;
};
# TODO; figure out if there is any fail2ban things we can do on searx
# searx-iptables.settings = lib.mkIf config.services.searx.enable {};
};
};
};
}

View file

@ -0,0 +1,34 @@
{
lib,
config,
...
}: let
dataFolder = "/var/lib/fail2ban";
dataFile = "fail2ban.sqlite3";
in {
options.services.fail2ban = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.fail2ban.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.fail2ban.impermanence.enable {
assertions = [
{
assertion = config.services.fail2ban.daemonSettings.Definition.dbfile == "${dataFolder}/${dataFile}";
message = "fail2ban data file does not match persistence";
}
];
environment.persistence."/persist/system/root" = {
directories = [
{
directory = dataFolder;
user = "fail2ban";
group = "fail2ban";
}
];
};
};
}

View file

@ -1,6 +1,5 @@
{...}: {
imports = [
./proxy.nix
./impermanence.nix
];
}

View file

@ -3,7 +3,14 @@
config,
...
}: {
config = lib.mkIf (config.services.flaresolverr.enable && config.host.impermanence.enable) {
options.services.flaresolverr = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.flaresolverr.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.flaresolverr.impermanence.enable {
# FlareSolverr typically doesn't need persistent storage as it's a proxy service
# but we'll add basic structure in case it's needed for logs or configuration
environment.persistence."/persist/system/root" = {

View file

@ -1,28 +0,0 @@
{
lib,
config,
...
}: {
options.services.flaresolverr = {
subdomain = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Subdomain for reverse proxy. If null, service will be local only.";
};
extraSubdomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "Extra subdomains for reverse proxy.";
};
};
config = lib.mkIf (config.services.flaresolverr.enable && config.services.flaresolverr.subdomain != null) {
host.reverse_proxy.subdomains.flaresolverr = {
subdomain = config.services.flaresolverr.subdomain;
extraSubdomains = config.services.flaresolverr.extraSubdomains;
target = "http://127.0.0.1:${toString config.services.flaresolverr.port}";
websockets.enable = true;
forwardHeaders.enable = true;
};
};
}

View file

@ -2,40 +2,31 @@
lib,
config,
...
}: {
config = lib.mkIf config.services.forgejo.enable (
lib.mkMerge [
{
host = {
postgres = {
enable = true;
};
};
}: let
usingPostgres = config.services.forgejo.database.type == "postgres";
in {
config = lib.mkIf config.services.forgejo.enable {
assertions = [
{
assertion = config.services.forgejo.settings.database.DB_TYPE == "postgres";
message = "Forgejo database type must be postgres";
assertion = !usingPostgres || config.services.postgresql.enable;
message = "PostgreSQL must be enabled when Forgejo database type is postgres";
}
{
assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (db: db == "forgejo") config.services.postgresql.ensureDatabases);
message = "Forgejo built-in database creation failed - expected 'forgejo' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}";
}
{
assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (user: user.name == "forgejo") config.services.postgresql.ensureUsers);
message = "Forgejo built-in user creation failed - expected user 'forgejo' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}";
}
];
}
(lib.mkIf config.host.postgres.enable {
host = {
postgres = {
extraUsers = {
forgejo = {
isClient = true;
createUser = true;
};
};
extraDatabases = {
forgejo = {
name = "forgejo";
};
};
};
};
})
]
);
services.forgejo.database.createDatabase = lib.mkDefault usingPostgres;
systemd.services.forgejo = lib.mkIf usingPostgres {
requires = [
config.systemd.services.postgresql.name
];
};
};
}

View file

@ -1,53 +1,9 @@
{
lib,
config,
...
}: let
const = import ./const.nix;
httpPort = const.httpPort;
sshPort = const.sshPort;
db_user = "forgejo";
in {
imports = [
./forgejo.nix
./proxy.nix
./database.nix
./fail2ban.nix
./impermanence.nix
];
config = lib.mkIf config.services.forgejo.enable {
assertions = [
{
assertion = config.services.forgejo.settings.server.BUILTIN_SSH_SERVER_USER == config.users.users.git.name;
message = "Forgejo BUILTIN_SSH_SERVER_USER hardcoded value does not match expected git user name";
}
];
services.forgejo = {
database = {
type = "postgres";
socket = "/run/postgresql";
};
lfs.enable = true;
settings = {
server = {
DOMAIN = "${config.services.forgejo.subdomain}.${config.host.reverse_proxy.hostname}";
HTTP_PORT = httpPort;
START_SSH_SERVER = true;
SSH_LISTEN_PORT = sshPort;
SSH_PORT = 22;
BUILTIN_SSH_SERVER_USER = "git";
ROOT_URL = "https://git.jan-leila.com";
};
service = {
DISABLE_REGISTRATION = true;
};
database = {
DB_TYPE = "postgres";
NAME = db_user;
USER = db_user;
};
};
};
};
}

View file

@ -4,7 +4,16 @@
pkgs,
...
}: {
config = lib.mkIf (config.services.forgejo.enable && config.services.fail2ban.enable) {
options.services.forgejo = {
fail2ban = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.forgejo.enable && config.services.fail2ban.enable;
};
};
};
config = lib.mkIf config.services.forgejo.fail2ban.enable {
environment.etc = {
"fail2ban/filter.d/forgejo.local".text = lib.mkIf config.services.forgejo.enable (
pkgs.lib.mkDefault (pkgs.lib.mkAfter ''

View file

@ -0,0 +1,46 @@
{
lib,
config,
...
}: let
const = import ./const.nix;
httpPort = const.httpPort;
sshPort = const.sshPort;
db_user = "forgejo";
in {
config = lib.mkIf config.services.forgejo.enable {
assertions = [
{
assertion = config.services.forgejo.settings.server.BUILTIN_SSH_SERVER_USER == config.users.users.git.name;
message = "Forgejo BUILTIN_SSH_SERVER_USER hardcoded value does not match expected git user name";
}
];
services.forgejo = {
database = {
type = "postgres";
socket = "/run/postgresql";
};
lfs.enable = true;
settings = {
server = {
DOMAIN = config.services.forgejo.reverseProxy.domain;
HTTP_PORT = httpPort;
START_SSH_SERVER = true;
SSH_LISTEN_PORT = sshPort;
SSH_PORT = 22;
BUILTIN_SSH_SERVER_USER = "git";
ROOT_URL = "https://git.jan-leila.com";
};
service = {
DISABLE_REGISTRATION = true;
};
database = {
DB_TYPE = "postgres";
NAME = db_user;
USER = db_user;
};
};
};
};
}

View file

@ -5,7 +5,14 @@
}: let
stateDir = "/var/lib/forgejo";
in {
config = lib.mkIf (config.services.forgejo.enable && config.host.impermanence.enable) {
options.services.forgejo = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.forgejo.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.forgejo.impermanence.enable {
assertions = [
{
assertion = config.services.forgejo.stateDir == stateDir;

View file

@ -7,16 +7,33 @@
httpPort = const.httpPort;
in {
options.services.forgejo = {
subdomain = lib.mkOption {
reverseProxy = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.forgejo.enable && config.services.reverseProxy.enable;
};
domain = lib.mkOption {
type = lib.types.str;
description = "subdomain of base domain that forgejo will be hosted at";
default = "forgejo";
description = "domain that forgejo will be hosted at";
default = "git.jan-leila.com";
};
extraDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "extra domains that should be configured for forgejo";
default = [];
};
};
};
config = lib.mkIf (config.services.forgejo.enable && config.host.reverse_proxy.enable) {
host.reverse_proxy.subdomains.${config.services.forgejo.subdomain} = {
config = lib.mkIf config.services.forgejo.reverseProxy.enable {
services.reverseProxy.services.forgejo = {
target = "http://localhost:${toString httpPort}";
domain = config.services.forgejo.reverseProxy.domain;
extraDomains = config.services.forgejo.reverseProxy.extraDomains;
settings = {
forwardHeaders.enable = true;
};
};
networking.firewall.allowedTCPPorts = [

View file

@ -2,55 +2,52 @@
lib,
config,
...
}: let
dbUser = "hass";
in {
config = lib.mkIf config.services.home-assistant.enable (
lib.mkMerge [
{
host = {
}: {
options.services.home-assistant = {
postgres = {
enable = true;
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Use PostgreSQL instead of SQLite";
};
user = lib.mkOption {
type = lib.types.str;
default = "hass";
description = "Database user name";
};
database = lib.mkOption {
type = lib.types.str;
default = "hass";
description = "Database name";
};
};
};
config = lib.mkIf config.services.home-assistant.enable {
assertions = [
{
assertion = config.services.home-assistant.database == "postgres";
message = "Home Assistant database type must be postgres";
assertion = !config.services.home-assistant.postgres.enable || config.services.postgresql.enable;
message = "PostgreSQL must be enabled when using postgres database for Home Assistant";
}
];
}
(lib.mkIf config.host.postgres.enable {
host = {
postgres = {
extraUsers = {
${dbUser} = {
isClient = true;
createUser = true;
};
};
extraDatabases = {
${dbUser} = {
name = dbUser;
};
};
};
services.postgresql.databases.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable {
enable = true;
user = config.services.home-assistant.postgres.user;
database = config.services.home-assistant.postgres.database;
};
services.home-assistant = {
services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable {
extraPackages = python3Packages:
with python3Packages; [
psycopg2
];
};
systemd.services.home-assistant = {
systemd.services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable {
requires = [
config.systemd.services.postgresql.name
];
};
})
]
);
};
}

View file

@ -1,112 +1,10 @@
{
lib,
config,
...
}: {
imports = [
./home-assistant.nix
./proxy.nix
./database.nix
./fail2ban.nix
./impermanence.nix
./extensions
];
options.services.home-assistant = {
database = lib.mkOption {
type = lib.types.enum [
"builtin"
"postgres"
];
description = "what database do we want to use";
default = "builtin";
};
extensions = {
sonos = {
enable = lib.mkEnableOption "enable the sonos plugin";
port = lib.mkOption {
type = lib.types.int;
default = 1400;
description = "what port to use for sonos discovery";
};
};
jellyfin = {
enable = lib.mkEnableOption "enable the jellyfin plugin";
};
wyoming = {
enable = lib.mkEnableOption "enable wyoming";
};
};
};
config = lib.mkIf config.services.home-assistant.enable (lib.mkMerge [
{
services.home-assistant = {
configDir = "/var/lib/hass";
extraComponents = [
"default_config"
"esphome"
"met"
"radio_browser"
"isal"
"zha"
"webostv"
"tailscale"
"syncthing"
"analytics_insights"
"unifi"
"openweathermap"
"ollama"
"mobile_app"
"logbook"
"ssdp"
"usb"
"webhook"
"bluetooth"
"dhcp"
"energy"
"history"
"backup"
"assist_pipeline"
"conversation"
"sun"
"zeroconf"
"cpuspeed"
];
config = {
http = {
server_port = 8123;
use_x_forwarded_for = true;
trusted_proxies = ["127.0.0.1" "::1"];
ip_ban_enabled = true;
login_attempts_threshold = 10;
};
homeassistant = {
external_url = "https://${config.services.home-assistant.subdomain}.${config.host.reverse_proxy.hostname}";
# internal_url = "http://192.168.1.2:8123";
};
recorder.db_url = "postgresql://@/${config.services.home-assistant.configDir}";
"automation manual" = [];
"automation ui" = "!include automations.yaml";
mobile_app = {};
};
extraPackages = python3Packages:
with python3Packages; [
hassil
numpy
gtts
];
};
# TODO: configure /var/lib/hass/secrets.yaml via sops
networking.firewall.allowedUDPPorts = [
1900
];
systemd.tmpfiles.rules = [
"f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass"
];
}
]);
}

View file

@ -3,8 +3,17 @@
pkgs,
config,
...
}:
lib.mkIf (config.services.fail2ban.enable && config.services.home-assistant.enable) {
}: {
options.services.home-assistant = {
fail2ban = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.fail2ban.enable && config.services.home-assistant.enable;
};
};
};
config = lib.mkIf config.services.home-assistant.fail2ban.enable {
environment.etc = {
"fail2ban/filter.d/hass.local".text = (
pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
@ -36,4 +45,5 @@ lib.mkIf (config.services.fail2ban.enable && config.services.home-assistant.enab
};
};
};
};
}

View file

@ -0,0 +1,104 @@
{
lib,
config,
...
}: {
options.services.home-assistant = {
database = lib.mkOption {
type = lib.types.enum [
"builtin"
"postgres"
];
description = "what database do we want to use";
default = "builtin";
};
extensions = {
sonos = {
enable = lib.mkEnableOption "enable the sonos plugin";
port = lib.mkOption {
type = lib.types.int;
default = 1400;
description = "what port to use for sonos discovery";
};
};
jellyfin = {
enable = lib.mkEnableOption "enable the jellyfin plugin";
};
wyoming = {
enable = lib.mkEnableOption "enable wyoming";
};
};
};
config = lib.mkIf config.services.home-assistant.enable (lib.mkMerge [
{
services.home-assistant = {
configDir = "/var/lib/hass";
extraComponents = [
"default_config"
"esphome"
"met"
"radio_browser"
"isal"
"zha"
"webostv"
"tailscale"
"syncthing"
"analytics_insights"
"unifi"
"openweathermap"
"ollama"
"mobile_app"
"logbook"
"ssdp"
"usb"
"webhook"
"bluetooth"
"dhcp"
"energy"
"history"
"backup"
"assist_pipeline"
"conversation"
"sun"
"zeroconf"
"cpuspeed"
];
config = {
http = {
server_port = 8123;
use_x_forwarded_for = true;
trusted_proxies = ["127.0.0.1" "::1"];
ip_ban_enabled = true;
login_attempts_threshold = 10;
};
homeassistant = {
external_url = "https://${config.services.home-assistant.domain}";
# internal_url = "http://192.168.1.2:8123";
};
recorder.db_url = "postgresql://@/${config.services.home-assistant.configDir}";
"automation manual" = [];
"automation ui" = "!include automations.yaml";
mobile_app = {};
};
extraPackages = python3Packages:
with python3Packages; [
hassil
numpy
gtts
];
};
# TODO: configure /var/lib/hass/secrets.yaml via sops
networking.firewall.allowedUDPPorts = [
1900
];
systemd.tmpfiles.rules = [
"f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass"
];
}
]);
}

View file

@ -4,29 +4,39 @@
...
}: {
options.services.home-assistant = {
subdomain = lib.mkOption {
domain = lib.mkOption {
type = lib.types.str;
description = "subdomain of base domain that home-assistant will be hosted at";
default = "home-assistant";
description = "domain that home-assistant will be hosted at";
default = "home-assistant.arpa";
};
extraDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "extra domains that should be configured for home-assistant";
default = [];
};
reverseProxy = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.reverseProxy.enable && config.services.home-assistant.enable;
};
};
};
config = lib.mkIf (config.host.reverse_proxy.enable && config.services.home-assistant.enable) {
host = {
reverse_proxy.subdomains.${config.services.home-assistant.subdomain} = {
config = lib.mkIf config.services.home-assistant.reverseProxy.enable {
services.reverseProxy.services.home-assistant = {
target = "http://localhost:${toString config.services.home-assistant.config.http.server_port}";
domain = config.services.home-assistant.domain;
extraDomains = config.services.home-assistant.extraDomains;
websockets.enable = true;
settings = {
proxyWebsockets.enable = true;
forwardHeaders.enable = true;
extraConfig = ''
add_header Upgrade $http_upgrade;
add_header Connection \"upgrade\";
proxy_buffering off;
proxy_read_timeout 90;
'';
# Custom timeout settings
proxyHeaders = {
enable = true;
timeout = 90;
};
};
};
};

View file

@ -3,24 +3,28 @@
config,
...
}: {
config = lib.mkIf config.services.immich.enable (lib.mkMerge [
config = lib.mkIf config.services.immich.enable {
assertions = [
{
host = {
postgres = {
enable = true;
assertion = !config.services.immich.database.enable || config.services.postgresql.enable;
message = "PostgreSQL must be enabled when using postgres database for Immich";
}
{
assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (db: db == "immich") config.services.postgresql.ensureDatabases);
message = "Immich built-in database creation failed - expected 'immich' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}";
}
{
assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (user: user.name == "immich") config.services.postgresql.ensureUsers);
message = "Immich built-in user creation failed - expected user 'immich' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}";
}
];
# Note: Immich has built-in database creation via services.immich.database.createDB we only add the systemd dependency
systemd.services.immich-server = lib.mkIf config.services.immich.database.enable {
requires = [
config.systemd.services.postgresql.name
];
};
};
}
(lib.mkIf config.host.postgres.enable {
host = {
postgres = {
extraUsers = {
${config.services.immich.database.user} = {
isClient = true;
};
};
};
};
})
]);
}

View file

@ -4,7 +4,16 @@
pkgs,
...
}: {
config = lib.mkIf (config.services.fail2ban.enable && config.services.immich.enable) {
options.services.immich = {
fail2ban = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.fail2ban.enable && config.services.immich.enable;
};
};
};
config = lib.mkIf config.services.immich.fail2ban.enable {
environment.etc = {
"fail2ban/filter.d/immich.local".text = pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
[Definition]

View file

@ -5,7 +5,14 @@
}: let
mediaLocation = "/var/lib/immich";
in {
config = lib.mkIf (config.services.immich.enable && config.host.impermanence.enable) {
options.services.immich = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.immich.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.immich.impermanence.enable {
assertions = [
{
assertion = config.services.immich.mediaLocation == mediaLocation;

View file

@ -4,31 +4,40 @@
...
}: {
options.services.immich = {
subdomain = lib.mkOption {
domain = lib.mkOption {
type = lib.types.str;
description = "subdomain of base domain that immich will be hosted at";
default = "immich";
description = "domain that immich will be hosted at";
default = "immich.arpa";
};
extraDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "extra domains that should be configured for immich";
default = [];
};
reverseProxy = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.immich.enable && config.services.reverseProxy.enable;
};
};
};
config = lib.mkIf (config.services.immich.enable && config.host.reverse_proxy.enable) {
host = {
reverse_proxy.subdomains.${config.services.immich.subdomain} = {
config = lib.mkIf config.services.immich.reverseProxy.enable {
services.reverseProxy.services.immich = {
target = "http://localhost:${toString config.services.immich.port}";
domain = config.services.immich.domain;
extraDomains = config.services.immich.extraDomains;
websockets.enable = true;
settings = {
proxyWebsockets.enable = true;
forwardHeaders.enable = true;
maxBodySize = 50000;
extraConfig = ''
# allow large file uploads
client_max_body_size 50000M;
# set timeout
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
proxy_redirect off;
'';
# Custom timeout settings
proxyHeaders = {
enable = true;
timeout = 600;
};
};
};
};

View file

@ -1,6 +1,5 @@
{...}: {
imports = [
./proxy.nix
./impermanence.nix
];
}

View file

@ -5,7 +5,14 @@
}: let
jackett_data_directory = "/var/lib/jackett/.config/Jackett";
in {
config = lib.mkIf (config.services.jackett.enable && config.host.impermanence.enable) {
options.services.jackett = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.jackett.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.jackett.impermanence.enable {
assertions = [
{
assertion = config.services.jackett.dataDir == jackett_data_directory;

View file

@ -1,28 +0,0 @@
{
lib,
config,
...
}: {
options.services.jackett = {
subdomain = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Subdomain for reverse proxy. If null, service will be local only.";
};
extraSubdomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "Extra subdomains for reverse proxy.";
};
};
config = lib.mkIf (config.services.jackett.enable && config.services.jackett.subdomain != null) {
host.reverse_proxy.subdomains.jackett = {
subdomain = config.services.jackett.subdomain;
extraSubdomains = config.services.jackett.extraSubdomains;
target = "http://127.0.0.1:9117";
websockets.enable = true;
forwardHeaders.enable = true;
};
};
}

View file

@ -1,38 +1,8 @@
{
lib,
pkgs,
config,
...
}: let
jellyfinPort = 8096;
dlanPort = 1900;
in {
imports = [
./jellyfin.nix
./proxy.nix
./fail2ban.nix
./impermanence.nix
];
options.services.jellyfin = {
media_directory = lib.mkOption {
type = lib.types.str;
description = "directory jellyfin media will be hosted at";
default = "/srv/jellyfin/media";
};
};
config = lib.mkIf config.services.jellyfin.enable {
environment.systemPackages = [
pkgs.jellyfin
pkgs.jellyfin-web
pkgs.jellyfin-ffmpeg
];
networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort];
systemd.tmpfiles.rules = [
"d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media"
"A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-"
];
};
}

View file

@ -6,7 +6,14 @@
jellyfin_data_directory = "/var/lib/jellyfin";
jellyfin_cache_directory = "/var/cache/jellyfin";
in {
config = lib.mkIf (config.services.jellyfin.enable && config.host.impermanence.enable) {
options.services.jellyfin = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.jellyfin.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.jellyfin.impermanence.enable {
fileSystems."/persist/system/jellyfin".neededForBoot = true;
host.storage.pool.extraDatasets = {

View file

@ -0,0 +1,32 @@
{
lib,
pkgs,
config,
...
}: let
jellyfinPort = 8096;
dlanPort = 1900;
in {
options.services.jellyfin = {
media_directory = lib.mkOption {
type = lib.types.str;
description = "directory jellyfin media will be hosted at";
default = "/srv/jellyfin/media";
};
};
config = lib.mkIf config.services.jellyfin.enable {
environment.systemPackages = [
pkgs.jellyfin
pkgs.jellyfin-web
pkgs.jellyfin-ffmpeg
];
networking.firewall.allowedTCPPorts = [jellyfinPort dlanPort];
systemd.tmpfiles.rules = [
"d ${config.services.jellyfin.media_directory} 2770 jellyfin jellyfin_media"
"A ${config.services.jellyfin.media_directory} - - - - u:jellyfin:rwX,g:jellyfin_media:rwX,o::-"
];
};
}

View file

@ -6,33 +6,36 @@
jellyfinPort = 8096;
in {
options.services.jellyfin = {
subdomain = lib.mkOption {
domain = lib.mkOption {
type = lib.types.str;
description = "subdomain of base domain that jellyfin will be hosted at";
default = "jellyfin";
description = "domain that jellyfin will be hosted at";
default = "jellyfin.arpa";
};
extraSubdomains = lib.mkOption {
extraDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "ex subdomain of base domain that jellyfin will be hosted at";
description = "extra domains that should be configured for jellyfin";
default = [];
};
reverseProxy = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.jellyfin.enable && config.services.reverseProxy.enable;
};
};
};
config = lib.mkIf (config.services.jellyfin.enable && config.host.reverse_proxy.enable) {
host.reverse_proxy.subdomains.jellyfin = {
config = lib.mkIf config.services.jellyfin.reverseProxy.enable {
services.reverseProxy.services.jellyfin = {
target = "http://localhost:${toString jellyfinPort}";
domain = config.services.jellyfin.domain;
extraDomains = config.services.jellyfin.extraDomains;
subdomain = config.services.jellyfin.subdomain;
extraSubdomains = config.services.jellyfin.extraSubdomains;
settings = {
forwardHeaders.enable = true;
extraConfig = ''
client_max_body_size 20M;
add_header X-Content-Type-Options "nosniff";
proxy_buffering off;
'';
maxBodySize = 20;
noSniff.enable = true;
proxyBuffering.enable = false;
};
};
};
}

View file

@ -1,6 +1,5 @@
{...}: {
imports = [
./proxy.nix
./impermanence.nix
];
}

View file

@ -5,7 +5,14 @@
}: let
lidarr_data_directory = "/var/lib/lidarr/.config/Lidarr";
in {
config = lib.mkIf (config.services.lidarr.enable && config.host.impermanence.enable) {
options.services.lidarr = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.lidarr.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.lidarr.impermanence.enable {
assertions = [
{
assertion = config.services.lidarr.dataDir == lidarr_data_directory;

View file

@ -1,28 +0,0 @@
{
lib,
config,
...
}: {
options.services.lidarr = {
subdomain = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Subdomain for reverse proxy. If null, service will be local only.";
};
extraSubdomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "Extra subdomains for reverse proxy.";
};
};
config = lib.mkIf (config.services.lidarr.enable && config.services.lidarr.subdomain != null) {
host.reverse_proxy.subdomains.lidarr = {
subdomain = config.services.lidarr.subdomain;
extraSubdomains = config.services.lidarr.extraSubdomains;
target = "http://127.0.0.1:8686";
websockets.enable = true;
forwardHeaders.enable = true;
};
};
}

View file

@ -1,90 +1,6 @@
{
config,
lib,
...
}: let
export_directory = config.host.network_storage.export_directory;
in {
imports = [
./network_storage.nix
./nfs.nix
];
options = {
host.network_storage = {
enable = lib.mkEnableOption "is this machine going to export network storage";
export_directory = lib.mkOption {
type = lib.types.path;
description = "what are exports going to be stored in";
default = "/exports";
};
directories = lib.mkOption {
type = lib.types.listOf (lib.types.submodule ({config, ...}: {
options = {
folder = lib.mkOption {
type = lib.types.str;
description = "what is the name of this export directory";
};
bind = lib.mkOption {
type = lib.types.nullOr lib.types.path;
description = "is this directory bound to anywhere";
default = null;
};
user = lib.mkOption {
type = lib.types.str;
description = "what user owns this directory";
default = "nouser";
};
group = lib.mkOption {
type = lib.types.str;
description = "what group owns this directory";
default = "nogroup";
};
_directory = lib.mkOption {
internal = true;
readOnly = true;
type = lib.types.path;
default = "${export_directory}/${config.folder}";
};
};
}));
description = "list of directory names to export";
};
};
};
config = lib.mkIf config.host.network_storage.enable (lib.mkMerge [
{
# create any folders that we need to have for our exports
systemd.tmpfiles.rules =
[
"d ${config.host.network_storage.export_directory} 2775 nobody nogroup -"
]
++ (
builtins.map (
directory: "d ${directory._directory} 2770 ${directory.user} ${directory.group}"
)
config.host.network_storage.directories
);
# set up any bind mounts that we need for our exports
fileSystems = builtins.listToAttrs (
builtins.map (directory:
lib.attrsets.nameValuePair directory._directory {
device = directory.bind;
options = ["bind"];
}) (
builtins.filter (directory: directory.bind != null) config.host.network_storage.directories
)
);
}
# (lib.mkIf config.host.impermanence.enable {
# environment.persistence."/persist/system/root" = {
# enable = true;
# hideMounts = true;
# directories = [
# config.host.network_storage.export_directory
# ];
# };
# })
]);
}

View file

@ -0,0 +1,86 @@
{
config,
lib,
...
}: let
export_directory = config.host.network_storage.export_directory;
in {
options = {
host.network_storage = {
enable = lib.mkEnableOption "is this machine going to export network storage";
export_directory = lib.mkOption {
type = lib.types.path;
description = "what are exports going to be stored in";
default = "/exports";
};
directories = lib.mkOption {
type = lib.types.listOf (lib.types.submodule ({config, ...}: {
options = {
folder = lib.mkOption {
type = lib.types.str;
description = "what is the name of this export directory";
};
bind = lib.mkOption {
type = lib.types.nullOr lib.types.path;
description = "is this directory bound to anywhere";
default = null;
};
user = lib.mkOption {
type = lib.types.str;
description = "what user owns this directory";
default = "nouser";
};
group = lib.mkOption {
type = lib.types.str;
description = "what group owns this directory";
default = "nogroup";
};
_directory = lib.mkOption {
internal = true;
readOnly = true;
type = lib.types.path;
default = "${export_directory}/${config.folder}";
};
};
}));
description = "list of directory names to export";
};
};
};
config = lib.mkIf config.host.network_storage.enable (lib.mkMerge [
{
# create any folders that we need to have for our exports
systemd.tmpfiles.rules =
[
"d ${config.host.network_storage.export_directory} 2775 nobody nogroup -"
]
++ (
builtins.map (
directory: "d ${directory._directory} 2770 ${directory.user} ${directory.group}"
)
config.host.network_storage.directories
);
# set up any bind mounts that we need for our exports
fileSystems = builtins.listToAttrs (
builtins.map (directory:
lib.attrsets.nameValuePair directory._directory {
device = directory.bind;
options = ["bind"];
}) (
builtins.filter (directory: directory.bind != null) config.host.network_storage.directories
)
);
}
# (lib.mkIf config.host.impermanence.enable {
# environment.persistence."/persist/system/root" = {
# enable = true;
# hideMounts = true;
# directories = [
# config.host.network_storage.export_directory
# ];
# };
# })
]);
}

View file

@ -3,32 +3,46 @@
config,
...
}: {
config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [
options.services.panoramax = {
database = {
postgres = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Use PostgreSQL instead of SQLite";
};
user = lib.mkOption {
type = lib.types.str;
default = "panoramax";
description = "Database user name";
};
database = lib.mkOption {
type = lib.types.str;
default = "panoramax";
description = "Database name";
};
};
};
};
config = lib.mkIf config.services.panoramax.enable {
assertions = [
{
host = {
postgres = {
assertion = !config.services.panoramax.database.postgres.enable || config.services.postgresql.enable;
message = "PostgreSQL must be enabled when using postgres database for Panoramax";
}
];
services.postgresql.databases.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable {
enable = true;
user = config.services.panoramax.database.postgres.user;
database = config.services.panoramax.database.postgres.database;
};
systemd.services.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable {
requires = [
config.systemd.services.postgresql.name
];
};
};
}
(
lib.mkIf config.host.postgres.enable {
host = {
postgres = {
extraUsers = {
${config.services.panoramax.database.user} = {
isClient = true;
createUser = true;
};
};
extraDatabases = {
${config.services.panoramax.database.name} = {
name = config.services.panoramax.database.user;
};
};
};
};
}
)
]);
}

View file

@ -3,7 +3,14 @@
config,
...
}: {
config = lib.mkIf (config.services.panoramax.enable && config.host.impermanence.enable) {
options.services.panoramax = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.panoramax.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.panoramax.impermanence.enable {
# TODO: configure impermanence for panoramax data
# This would typically include directories like:
# - /var/lib/panoramax

View file

@ -4,31 +4,35 @@
...
}: {
options.services.panoramax = {
subdomain = lib.mkOption {
domain = lib.mkOption {
type = lib.types.str;
description = "subdomain of base domain that panoramax will be hosted at";
default = "panoramax";
description = "domain that panoramax will be hosted at";
default = "panoramax.arpa";
};
extraDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "extra domains that should be configured for panoramax";
default = [];
};
reverseProxy = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.panoramax.enable && config.services.reverseProxy.enable;
};
};
};
config = lib.mkIf (config.services.panoramax.enable && config.host.reverse_proxy.enable) {
host = {
reverse_proxy.subdomains.${config.services.panoramax.subdomain} = {
config = lib.mkIf config.services.panoramax.reverseProxy.enable {
services.reverseProxy.services.panoramax = {
target = "http://localhost:${toString config.services.panoramax.port}";
domain = config.services.panoramax.domain;
extraDomains = config.services.panoramax.extraDomains;
websockets.enable = true;
settings = {
proxyWebsockets.enable = true;
forwardHeaders.enable = true;
extraConfig = ''
# allow large file uploads for panoramic images
client_max_body_size 100M;
# set timeout for image processing
proxy_read_timeout 300s;
proxy_send_timeout 300s;
send_timeout 300s;
proxy_redirect off;
'';
maxBodySize = 100000;
timeout = 300;
};
};
};

View file

@ -3,32 +3,28 @@
lib,
...
}: {
config = lib.mkIf config.services.paperless.enable (lib.mkMerge [
config = lib.mkIf config.services.paperless.enable {
assertions = [
{
host = {
postgres = {
enable = true;
assertion = !config.services.paperless.database.createLocally || config.services.postgresql.enable;
message = "PostgreSQL must be enabled when using local postgres database for Paperless";
}
{
assertion = !config.services.paperless.database.createLocally || (builtins.any (db: db == "paperless") config.services.postgresql.ensureDatabases);
message = "Paperless built-in database creation failed - expected 'paperless' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}";
}
{
assertion = !config.services.paperless.database.createLocally || (builtins.any (user: user.name == "paperless") config.services.postgresql.ensureUsers);
message = "Paperless built-in user creation failed - expected user 'paperless' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}";
}
];
services.paperless.database.createLocally = lib.mkDefault true;
systemd.services.paperless-scheduler = lib.mkIf config.services.paperless.database.createLocally {
requires = [
config.systemd.services.postgresql.name
];
};
};
}
(
lib.mkIf config.host.postgres.enable {
host = {
postgres = {
extraUsers = {
${config.services.paperless.database.user} = {
isClient = true;
createUser = true;
};
};
extraDatabases = {
${config.services.paperless.database.user} = {
name = config.services.paperless.database.user;
};
};
};
};
}
)
]);
}

View file

@ -1,35 +1,9 @@
{
config,
lib,
...
}: {
imports = [
./paperless.nix
./proxy.nix
./database.nix
./fail2ban.nix
./impermanence.nix
];
options.services.paperless = {
database = {
user = lib.mkOption {
type = lib.types.str;
description = "what is the user and database that we are going to use for paperless";
default = "paperless";
};
};
};
config = lib.mkIf config.services.paperless.enable {
services.paperless = {
domain = "${config.services.paperless.subdomain}.${config.host.reverse_proxy.hostname}";
configureTika = true;
settings = {
PAPERLESS_DBENGINE = "postgresql";
PAPERLESS_DBHOST = "/run/postgresql";
PAPERLESS_DBNAME = config.services.paperless.database.user;
PAPERLESS_DBUSER = config.services.paperless.database.user;
};
};
};
}

View file

@ -5,7 +5,14 @@
}: let
dataDir = "/var/lib/paperless";
in {
config = lib.mkIf (config.services.paperless.enable && config.host.impermanence.enable) {
options.services.paperless = {
impermanence.enable = lib.mkOption {
type = lib.types.bool;
default = config.services.paperless.enable && config.host.impermanence.enable;
};
};
config = lib.mkIf config.services.paperless.impermanence.enable {
assertions = [
{
assertion = config.services.paperless.dataDir == dataDir;

View file

@ -0,0 +1,27 @@
{
config,
lib,
...
}: {
options.services.paperless = {
database = {
user = lib.mkOption {
type = lib.types.str;
description = "what is the user and database that we are going to use for paperless";
default = "paperless";
};
};
};
config = lib.mkIf config.services.paperless.enable {
services.paperless = {
configureTika = true;
settings = {
PAPERLESS_DBENGINE = "postgresql";
PAPERLESS_DBHOST = "/run/postgresql";
PAPERLESS_DBNAME = config.services.paperless.database.user;
PAPERLESS_DBUSER = config.services.paperless.database.user;
};
};
};
}

View file

@ -4,25 +4,29 @@
...
}: {
options.services.paperless = {
subdomain = lib.mkOption {
type = lib.types.str;
description = "subdomain of base domain that paperless will be hosted at";
default = "paperless";
extraDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "extra domains that should be configured for paperless";
default = [];
};
reverseProxy = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.services.paperless.enable && config.services.reverseProxy.enable;
};
};
};
config = lib.mkIf (config.services.paperless.enable && config.host.reverse_proxy.enable) {
host = {
reverse_proxy.subdomains.${config.services.paperless.subdomain} = {
config = lib.mkIf config.services.paperless.reverseProxy.enable {
services.reverseProxy.services.paperless = {
target = "http://${config.services.paperless.address}:${toString config.services.paperless.port}";
domain = config.services.paperless.domain;
extraDomains = config.services.paperless.extraDomains;
websockets.enable = true;
settings = {
proxyWebsockets.enable = true;
forwardHeaders.enable = true;
extraConfig = ''
# allow large file uploads
client_max_body_size 50000M;
'';
maxBodySize = 50000;
};
};
};

View file

@ -1,73 +0,0 @@
{
lib,
config,
...
}: {
options.host.podman = {
enable = lib.mkEnableOption "should podman be enabled on this computer";
macvlan = {
subnet = lib.mkOption {
type = lib.types.str;
description = "Subnet for macvlan address range";
};
gateway = lib.mkOption {
type = lib.types.str;
description = "Gateway for macvlan";
# TODO: see if we can default this to systemd network gateway
};
networkInterface = lib.mkOption {
type = lib.types.str;
description = "Parent network interface for macvlan";
# TODO: see if we can default this some interface?
};
};
};
config = lib.mkIf config.host.podman.enable {
systemd = {
services = {
# "podman-network-macvlan" = {
# path = [pkgs.podman];
# serviceConfig = {
# Type = "oneshot";
# RemainAfterExit = true;
# ExecStop = "podman network rm -f macvlan";
# };
# script = ''
# podman network inspect macvlan || podman network create --driver macvlan --subnet ${config.host.podman.macvlan.subnet} --gateway ${config.host.podman.macvlan.gateway} --opt parent=${config.host.podman.macvlan.networkInterface} macvlan
# '';
# partOf = ["podman-compose-root.target"];
# wantedBy = ["podman-compose-root.target"];
# };
};
# disable computer sleeping
targets = {
# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
"podman-compose-root" = {
unitConfig = {
Description = "Root target for podman targets.";
};
wantedBy = ["multi-user.target"];
};
};
};
virtualisation = {
# Runtime
podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
# defaultNetwork.settings = {
# # Required for container networking to be able to use names.
# dns_enabled = true;
# };
};
oci-containers = {
backend = "podman";
};
};
};
}

View file

@ -1,121 +0,0 @@
{
config,
lib,
pkgs,
...
}: let
dataDir = "/var/lib/postgresql/16";
adminUsers = lib.lists.filter (user: user.isAdmin) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers);
clientUsers = lib.lists.filter (user: user.isClient) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers);
createUsers = lib.lists.filter (user: user.createUser) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers);
createDatabases = lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraDatabases;
in {
options = {
host.postgres = {
enable = lib.mkEnableOption "enable postgres";
extraUsers = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
};
isAdmin = lib.mkOption {
type = lib.types.bool;
default = false;
};
isClient = lib.mkOption {
type = lib.types.bool;
default = false;
};
createUser = lib.mkOption {
type = lib.types.bool;
default = false;
};
};
}));
default = {};
};
extraDatabases = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
};
};
}));
default = {};
};
};
};
config = lib.mkIf config.host.postgres.enable (lib.mkMerge [
{
services = {
postgresql = {
enable = true;
package = pkgs.postgresql_16;
ensureUsers =
[
{
name = "postgres";
}
]
++ (
builtins.map (user: {
name = user.name;
ensureDBOwnership = true;
})
createUsers
);
ensureDatabases = builtins.map (database: database.name) createDatabases;
identMap =
''
# ArbitraryMapName systemUser DBUser
# Administration Users
superuser_map root postgres
superuser_map postgres postgres
''
+ (
lib.strings.concatLines (builtins.map (user: "superuser_map ${user.name} postgres") adminUsers)
)
+ ''
# Client Users
''
+ (
lib.strings.concatLines (builtins.map (user: "user_map ${user.name} ${user.name}") clientUsers)
);
# configuration here lets users access the db that matches their name and lets user postgres access everything
authentication = pkgs.lib.mkOverride 10 ''
# type database DBuser origin-address auth-method optional_ident_map
local all postgres peer map=superuser_map
local sameuser all peer map=user_map
'';
};
};
}
(lib.mkIf config.host.impermanence.enable {
assertions = [
{
assertion = config.services.postgresql.dataDir == dataDir;
message = "postgres data directory does not match persistence";
}
];
environment.persistence."/persist/system/root" = {
enable = true;
hideMounts = true;
directories = [
{
directory = dataDir;
user = "postgres";
group = "postgres";
}
];
};
})
]);
}

View file

@ -0,0 +1,6 @@
{...}: {
imports = [
./postgres.nix
./impermanence.nix
];
}

View file

@ -0,0 +1,27 @@
{
config,
lib,
...
}: let
dataDir = "/var/lib/postgresql/16";
in {
config = lib.mkIf (config.services.postgresql.enable && config.host.impermanence.enable) {
assertions = [
{
assertion = config.services.postgresql.dataDir == dataDir;
message = "postgres data directory does not match persistence";
}
];
environment.persistence."/persist/system/root" = {
enable = true;
hideMounts = true;
directories = [
{
directory = dataDir;
user = "postgres";
group = "postgres";
}
];
};
};
}

View file

@ -0,0 +1,122 @@
{
config,
lib,
pkgs,
...
}: let
enabledDatabases = lib.filterAttrs (_: db: db.enable) config.services.postgresql.databases;
extraDatabasesList = config.services.postgresql.extraDatabases;
serviceDatabaseUsers = lib.mapAttrsToList (_: db: {
name = db.user;
ensureDBOwnership = true;
}) (lib.filterAttrs (_: db: db.ensureUser) enabledDatabases);
extraDatabaseUsers =
builtins.map (dbName: {
name = dbName;
ensureDBOwnership = true;
})
extraDatabasesList;
serviceDatabases = lib.mapAttrsToList (_: db: db.database) enabledDatabases;
extraDatabaseNames = extraDatabasesList;
serviceUserMappings = lib.mapAttrsToList (_: db: "user_map ${db.user} ${db.user}") enabledDatabases;
extraUserMappings = builtins.map (dbName: "user_map ${dbName} ${dbName}") extraDatabasesList;
builtinServiceMappings = let
forgejoMapping = lib.optional (config.services.forgejo.enable && config.services.forgejo.database.type == "postgres") "user_map forgejo forgejo";
immichMapping = lib.optional (config.services.immich.enable && config.services.immich.database.enable) "user_map immich immich";
paperlessMapping = lib.optional (config.services.paperless.enable && config.services.paperless.database.createLocally) "user_map paperless paperless";
in
forgejoMapping ++ immichMapping ++ paperlessMapping;
in {
options = {
services.postgresql = {
databases = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
options = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to create this database and user";
};
user = lib.mkOption {
type = lib.types.str;
default = name;
description = "Database user name";
};
database = lib.mkOption {
type = lib.types.str;
default = name;
description = "Database name";
};
ensureUser = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to ensure the user exists";
};
};
}));
default = {};
description = "Databases to create for services";
};
extraDatabases = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "Additional databases to create (user name will match database name)";
example = ["custom_db" "test_db"];
};
adminUsers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "System users who should have PostgreSQL superuser access";
example = ["leyla" "admin"];
};
};
};
config = lib.mkIf config.services.postgresql.enable {
services = {
postgresql = {
package = pkgs.postgresql_16;
ensureUsers =
[
{name = "postgres";}
]
++ serviceDatabaseUsers ++ extraDatabaseUsers;
ensureDatabases = serviceDatabases ++ extraDatabaseNames;
identMap =
''
# ArbitraryMapName systemUser DBUser
# Administration Users
superuser_map root postgres
superuser_map postgres postgres
''
+ (
lib.strings.concatLines (builtins.map (user: "superuser_map ${user} postgres") config.services.postgresql.adminUsers)
)
+ ''
# Client Users
''
+ (
lib.strings.concatLines (serviceUserMappings ++ extraUserMappings ++ builtinServiceMappings)
);
authentication = pkgs.lib.mkOverride 10 ''
# type database DBuser origin-address auth-method optional_ident_map
local all postgres peer map=superuser_map
local sameuser all peer map=user_map
'';
};
};
};
}

View file

@ -1,65 +0,0 @@
{
lib,
config,
...
}: let
qbittorent_profile_directory = "/var/lib/qBittorrent/";
in {
options.services.qbittorrent = {
mediaDir = lib.mkOption {
type = lib.types.path;
description = lib.mdDoc ''
The directory to create to store qbittorrent media.
'';
};
};
config = lib.mkIf config.services.qbittorrent.enable (lib.mkMerge [
(lib.mkIf config.host.impermanence.enable {
fileSystems."/persist/system/qbittorrent".neededForBoot = true;
host.storage.pool.extraDatasets = {
# sops age key needs to be available to pre persist for user generation
"persist/system/qbittorrent" = {
type = "zfs_fs";
mountpoint = "/persist/system/qbittorrent";
options = {
canmount = "on";
};
};
};
assertions = [
{
assertion = config.services.qbittorrent.profileDir == qbittorent_profile_directory;
message = "qbittorrent data directory does not match persistence";
}
];
environment.persistence = {
"/persist/system/root" = {
directories = [
{
directory = qbittorent_profile_directory;
user = "qbittorrent";
group = "qbittorrent";
}
];
};
"/persist/system/qbittorrent" = {
enable = true;
hideMounts = true;
directories = [
{
directory = config.services.qbittorrent.mediaDir;
user = "qbittorrent";
group = "qbittorrent";
mode = "1775";
}
];
};
};
})
]);
}

View file

@ -0,0 +1,6 @@
{...}: {
imports = [
./qbittorent.nix
./impermanence.nix
];
}

Some files were not shown because too many files have changed in this diff Show more