diff --git a/flake.lock b/flake.lock index 3e8da75..6141529 100644 --- a/flake.lock +++ b/flake.lock @@ -27,27 +27,6 @@ "type": "github" } }, - "crowdsec": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1752497357, - "narHash": "sha256-9epXn1+T6U4Kfyw8B9zMzbERxDB3VfaPXhVebtai6CE=", - "ref": "refs/heads/main", - "rev": "84db7dcea77f7f477d79e69e35fb0bb560232667", - "revCount": 42, - "type": "git", - "url": "https://codeberg.org/kampka/nix-flake-crowdsec.git" - }, - "original": { - "type": "git", - "url": "https://codeberg.org/kampka/nix-flake-crowdsec.git" - } - }, "flake-parts": { "inputs": { "nixpkgs-lib": [ @@ -69,23 +48,6 @@ "type": "github" } }, - "flake-utils": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "id": "flake-utils", - "type": "indirect" - } - }, "home-manager": { "inputs": { "nixpkgs": [ @@ -128,6 +90,64 @@ "type": "github" } }, + "nix-rosetta-builder": { + "inputs": { + "nixos-generators": "nixos-generators", + "nixpkgs": [ + "nixpkgs-darwin" + ] + }, + "locked": { + "lastModified": 1756177999, + "narHash": "sha256-aSbB7/jrt7ujiJ55f2uGhOo+usGxVSkqbAMVgg2jDls=", + "owner": "cpick", + "repo": "nix-rosetta-builder", + "rev": "ebb7162a975074fb570a2c3ac02bc543ff2e9df4", + "type": "github" + }, + "original": { + "owner": "cpick", + "repo": "nix-rosetta-builder", + "type": "github" + } + }, + "nixlib": { + "locked": { + "lastModified": 1736643958, + "narHash": "sha256-tmpqTSWVRJVhpvfSN9KXBvKEXplrwKnSZNAoNPf/S/s=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "1418bc28a52126761c02dd3d89b2d8ca0f521181", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixos-generators": { + "inputs": { + "nixlib": "nixlib", + "nixpkgs": [ + "nix-rosetta-builder", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1737057290, + "narHash": "sha256-3Pe0yKlCc7EOeq1X/aJVDH0CtNL+tIBm49vpepwL1MQ=", + "owner": "nix-community", + "repo": "nixos-generators", + "rev": "d002ce9b6e7eb467cd1c6bb9aef9c35d191b5453", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixos-generators", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1764522689, @@ -238,9 +258,9 @@ "root": { "inputs": { "agenix": "agenix", - "crowdsec": "crowdsec", "home-manager": "home-manager", "nix-darwin": "nix-darwin", + "nix-rosetta-builder": "nix-rosetta-builder", "nixpkgs": "nixpkgs", "nixpkgs-darwin": "nixpkgs-darwin", "nixpkgs-unstable": "nixpkgs-unstable", @@ -286,21 +306,6 @@ "repo": "default", "type": "github" } - }, - "systems_2": { - "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", diff --git a/flake.nix b/flake.nix index e01f3d1..59c9ce0 100644 --- a/flake.nix +++ b/flake.nix @@ -30,10 +30,6 @@ }; # Linux-specific inputs - crowdsec = { - url = "git+https://codeberg.org/kampka/nix-flake-crowdsec.git"; - inputs.nixpkgs.follows = "nixpkgs"; - }; plasma-manager = { url = "github:nix-community/plasma-manager"; inputs = { @@ -48,11 +44,15 @@ url = "github:LnL7/nix-darwin/nix-darwin-25.11"; inputs.nixpkgs.follows = "nixpkgs-darwin"; }; + nix-rosetta-builder = { + url = "github:cpick/nix-rosetta-builder"; + inputs.nixpkgs.follows = "nixpkgs-darwin"; + }; }; outputs = { self, nix-darwin, nixpkgs, nixpkgs-darwin, nixpkgs-unstable - , home-manager, agenix, rycee-nurpkgs, nur, crowdsec, plasma-manager - , quadlet-nix, ... }: + , home-manager, agenix, rycee-nurpkgs, nur, plasma-manager, quadlet-nix + , nix-rosetta-builder, ... }: let mkHomeConfiguration = { hostname, arch ? "x86_64", os ? "linux" , desktop ? false, extraModules ? [ ] }: @@ -123,8 +123,11 @@ }; agenix = agenix; }; - modules = - [ ./systems/darwin/corianne.nix agenix.darwinModules.default ]; + modules = [ + ./systems/darwin/corianne.nix + agenix.darwinModules.default + nix-rosetta-builder.darwinModules.default + ]; }; nixosConfigurations = { @@ -147,9 +150,6 @@ agenix.nixosModules.default home-manager.nixosModules.home-manager quadlet-nix.nixosModules.quadlet - crowdsec.nixosModules.crowdsec - crowdsec.nixosModules.crowdsec-firewall-bouncer - { nixpkgs.overlays = [ crowdsec.overlays.default ]; } ]; }; }; diff --git a/homes/desktop.nix b/homes/desktop.nix index 7b1b2ad..255db78 100644 --- a/homes/desktop.nix +++ b/homes/desktop.nix @@ -22,6 +22,7 @@ nil nixd nixfmt-classic + nixos-rebuild quarto roboto-slab shellcheck diff --git a/programs/zed.nix b/programs/zed.nix index 19beb45..b3f7fd6 100644 --- a/programs/zed.nix +++ b/programs/zed.nix @@ -1,4 +1,4 @@ -{ ... }: { +{ pkgs, ... }: { programs.zed-editor = { enable = true; extensions = [ @@ -58,6 +58,22 @@ initialization_options.formatting.command = [ "nixfmt" ]; settings.nix.flake.autoArchive = true; }; + texlab = { + settings = { + build = { + onSave = false; + forwardSearchAfter = true; + }; + forwardSearch = if pkgs.stdenv.hostPlatform.isDarwin then { + executable = + "/Applications/Skim.app/Contents/SharedSupport/displayline"; + args = [ "-r" "%l" "%p" "%f" "-g" ]; + } else { + executable = "/usr/bin/okular"; + args = [ "--unique" "file:%p#src:%l%f" ]; + }; + }; + }; tinymist = { settings = { exportPdf = "onSave"; @@ -81,5 +97,12 @@ ui_font_size = 16; wrap_guides = [ 80 92 120 ]; }; + userTasks = [{ + label = "latexmk (project)"; + command = "latexmk"; + args = [ "-synctex=1" "-pdf" "-recorder" ]; + cwd = "$ZED_DIRNAME"; + tags = [ "latex-build" ]; + }]; }; } diff --git a/secrets.nix b/secrets.nix index 8042bf6..45783ee 100644 --- a/secrets.nix +++ b/secrets.nix @@ -34,7 +34,8 @@ in { ++ [ mcentire-host ]; "secrets/darwin-policies-json.age".publicKeys = system-administrators ++ [ corianne-host ]; + "secrets/freshrss.toml.age".publicKeys = system-administrators + ++ [ mcentire-host ]; "secrets/network-information.age".publicKeys = system-administrators ++ [ bosephus-host ]; - "secrets/pihole.age".publicKeys = system-administrators ++ [ bosephus-host ]; } diff --git a/secrets/freshrss.toml.age b/secrets/freshrss.toml.age new file mode 100644 index 0000000..e253b88 Binary files /dev/null and b/secrets/freshrss.toml.age differ diff --git a/secrets/pihole.age b/secrets/pihole.age deleted file mode 100644 index 1b2bc25..0000000 Binary files a/secrets/pihole.age and /dev/null differ diff --git a/services/crowdsec.nix b/services/crowdsec.nix index 6a54584..5c3e279 100644 --- a/services/crowdsec.nix +++ b/services/crowdsec.nix @@ -1,101 +1,49 @@ -{ pkgs, config, ... }: -let - crowdsec-port = "2763"; - firewall-bouncer-name = "fw-bouncer"; - # Although this key can be reproduced by anyone who actually cares to, the - # Crowdsec API will not be exposed to the outside world, so keeping this key - # super secret really isn't that important to me. Still make it look random - # so that hungry botnets can't just slurp up the password in plaintext. - firewall-bouncer-key = builtins.hashString "sha256" - "${config.networking.hostName}-crowdsec-bouncer-salt"; - toMultiYAML = items: - pkgs.lib.concatMapStrings (item: - '' - - --- - '' + (pkgs.lib.generators.toYAML { } item) + "\n") items; -in { +{ pkgs, config, ... }: { services = { crowdsec = { enable = true; - allowLocalJournalAccess = true; - settings = { - api.server = { listen_uri = "127.0.0.1:${crowdsec-port}"; }; - crowdsec_service.acquisition_path = pkgs.writeText "acquisitions.yaml" - (toMultiYAML [ - { - source = "journalctl"; - journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ]; - labels.type = "syslog"; - } - { - filenames = [ "/var/log/auth.log" ]; - labels.type = "syslog"; - } - { - filenames = [ "/var/log/syslog" "/var/log/kern.log" ]; - labels.type = "syslog"; - } - ]); + localConfig = { + acquisitions = [ + { + source = "journalctl"; + journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ]; + labels.type = "syslog"; + } + { + filenames = [ "/var/log/auth.log" ]; + labels.type = "syslog"; + } + { + filenames = [ "/var/log/syslog" "/var/log/kern.log" ]; + labels.type = "syslog"; + } + ]; }; + hub = { + collections = [ + "crowdsecurity/base-http-scenarios" + "crowdsecurity/http-cve" + "crowdsecurity/http-dos" + "crowdsecurity/iptables" + "crowdsecurity/linux" + "crowdsecurity/sshd" + "crowdsecurity/whitelist-good-actors" + ]; + }; + settings = { + general = { api.server.enable = true; }; + # See https://github.com/NixOS/nixpkgs/issues/445342 + lapi.credentialsFile = "/var/lib/crowdsec/lapi-credentials.yaml"; + }; + autoUpdateService = true; }; + crowdsec-firewall-bouncer = { enable = true; - settings = { - api_url = "http://localhost:${crowdsec-port}"; - api_key = firewall-bouncer-key; - }; + registerBouncer.enable = true; }; }; - systemd.services.crowdsec.serviceConfig = { - ExecStartPre = let - bouncer-script = pkgs.writeScriptBin "register-bouncer" '' - #!${pkgs.runtimeShell} - set -eu - set -o pipefail - - if ! cscli bouncers list | grep -q "${firewall-bouncer-name}"; then - cscli bouncers add "${firewall-bouncer-name}" --key "${firewall-bouncer-key}" - fi - ''; - collection-check = collection: '' - - if ! cscli collections list | grep -q "${collection}"; then - cscli collections --trace install "${collection}" - sleep 1 - fi - - ''; - collections = [ - "crowdsecurity/base-http-scenarios" - "crowdsecurity/http-cve" - "crowdsecurity/http-dos" - "crowdsecurity/iptables" - "crowdsecurity/linux" - "crowdsecurity/sshd" - "crowdsecurity/whitelist-good-actors" - ]; - collection-script = pkgs.writeScriptBin "install-collections" '' - #!${pkgs.runtimeShell} - set -eu - set -o pipefail - - # I had to run these commands in order to manually install collections - # using cscli. - # Not sure how often they should actually be run, but I would rather - # include this here. - # https://discourse.crowdsec.net/t/solved-cant-find-collections-appsec/1830 - cscli capi register - sleep 1 - cscli hub update - sleep 1 - - ${pkgs.lib.concatMapStrings collection-check collections} - ''; - in [ - "${bouncer-script}/bin/register-bouncer" - "${collection-script}/bin/install-collections" - ]; - }; + systemd.tmpfiles.rules = let cfg = config.services.crowdsec; + in [ "d /var/lib/crowdsec 0755 ${cfg.user} ${cfg.group}" ]; } diff --git a/services/freshrss.nix b/services/freshrss.nix new file mode 100644 index 0000000..2f142ff --- /dev/null +++ b/services/freshrss.nix @@ -0,0 +1,146 @@ +{ config, pkgs, home-manager-quadlet-nix, ... }: + +let + user = "freshrss"; + port = "37374"; + stateDirectory = "/var/lib/freshrss"; + serviceContainer = "freshrss"; + stateSubDir = subDir: "${stateDirectory}/${subDir}"; + createTmpfilesRule = subDir: "d ${stateSubDir subDir} 1755 ${user} ${user}"; + + dbDirectories = [ "database" ]; + serviceDirectories = [ "data" "extensions" ]; +in { + age.secrets = { + "freshrss.toml" = { + file = ./../secrets/freshrss.toml.age; + owner = "${user}"; + }; + }; + + millironx.podman-secrets.freshrss = { + user = "${user}"; + secrets-files = [ config.age.secrets."freshrss.toml".path ]; + }; + + services.caddy.virtualHosts."feeds.millironx.com".extraConfig = '' + reverse_proxy http://127.0.0.1:${port} { + header_up X-Forwarded-Port 443 + } + ''; + + systemd.tmpfiles.rules = builtins.map createTmpfilesRule + ([ stateDirectory ] ++ dbDirectories ++ serviceDirectories); + + services.borgmatic.configurations."${config.networking.hostName}" = { + source_directories = builtins.map stateSubDir dbDirectories; + + postgresql_databases = [{ + name = serviceContainer; + psql_command = + "/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${serviceContainer}-db psql --username=${user}"; + pg_dump_command = + "/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${serviceContainer}-db pg_dump --username=${user}"; + pg_restore_command = + "/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${serviceContainer}-db pg_restore --username=${user}"; + }]; + }; + + users.users."${user}" = { + group = "${user}"; + isNormalUser = true; + home = "${stateDirectory}"; + createHome = true; + linger = true; + autoSubUidGidRange = true; + }; + users.groups."${user}" = { }; + + home-manager.users."${user}" = { config, osConfig, ... }: { + imports = [ home-manager-quadlet-nix ]; + + home.stateVersion = "25.05"; + + virtualisation.quadlet = let + inherit (config.virtualisation.quadlet) containers; + inherit (config.virtualisation.quadlet) networks; + secrets = osConfig.millironx.podman-secrets.freshrss; + + in { + containers = { + "${serviceContainer}-db" = { + autoStart = true; + containerConfig = { + image = "docker.io/library/postgres:16"; + environments = { + POSTGRES_DB = "${user}"; + POSTGRES_USER = "${user}"; + }; + secrets = [ + "POSTGRES_PASSWORD,type=env" + "POSTGRES_PASSWORD,type=env,target=PGPASSWORD" + ]; + healthCmd = "pg_isready -d $\${POSTGRES_DB} -U $\${POSTGRES_USER}"; + healthInterval = "30s"; + healthRetries = 5; + healthStartPeriod = "20s"; + volumes = + [ "${stateDirectory}/database:/var/lib/postgresql/data:U" ]; + networks = [ networks."${serviceContainer}".ref ]; + }; + unitConfig.Requires = [ secrets.ref ]; + unitConfig.After = [ secrets.ref ]; + }; + + "${serviceContainer}" = { + autoStart = true; + containerConfig = { + image = "docker.io/freshrss/freshrss:1"; + # Required to allow the container to talk to the host ports, in + # other words, to resolve Authentik correctly + addHosts = [ "auth.millironx.com:host-gateway" ]; + environments = { + TZ = osConfig.time.timeZone; + CRON_MIN = "2,32"; + LISTEN = "0.0.0.0:${port}"; + TRUSTED_PROXY = "172.16.0.1/12 192.168.0.1/16"; + OIDC_ENABLED = "1"; + OIDC_PROVIDER_METADATA_URL = + "https://auth.millironx.com/application/o/freshrss/.well-known/openid-configuration"; + OIDC_REMOTE_USER_CLAIM = "preferred_username"; + OIDC_SCOPES = "openid email profile"; + OIDC_X_FORWARDED_HEADERS = + "X-Forwarded-Host X-Forwarded-Port X-Forwarded-Proto"; + }; + secrets = [ + "OIDC_CLIENT_ID,type=env" + "OIDC_CLIENT_SECRET,type=env" + "OIDC_CLIENT_CRYPTO_KEY,type=env" + ]; + healthCmd = "cli/health.php"; + healthTimeout = "10s"; + healthStartPeriod = "60s"; + healthStartupInterval = "11s"; + healthInterval = "75s"; + healthRetries = 3; + networks = [ networks."${serviceContainer}".ref ]; + publishPorts = [ "127.0.0.1:${port}:${port}" ]; + volumes = [ + "${stateDirectory}/data:/var/www/FreshRSS/data:U" + "${stateDirectory}/extensions:/var/www/FreshRSS/extensions:U" + ]; + }; + unitConfig.Requires = + [ secrets.ref containers."${serviceContainer}-db".ref ]; + unitConfig.After = + [ secrets.ref containers."${serviceContainer}-db".ref ]; + }; + }; + + networks."${serviceContainer}" = { }; + + autoUpdate.enable = true; + autoEscape = true; + }; + }; +} diff --git a/services/pihole.nix b/services/pihole.nix deleted file mode 100644 index ed4cd19..0000000 --- a/services/pihole.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ config, ... }: - -{ - age.secrets = { - pihole-credentials = { - file = ./../secrets/pihole.age; - owner = "root"; - group = "root"; - }; - }; - virtualisation = { - quadlet = { - containers = { - pihole = { - containerConfig = { - image = "docker.io/pihole/pihole:2025.06.2"; - publishPorts = - [ "53:53/tcp" "53:53/udp" "80:80/tcp" "443:443/tcp" ]; - environmentFiles = [ config.age.secrets.pihole-credentials.path ]; - networks = [ "bridge" ]; - dns = [ "127.0.0.1" "194.242.2.9" "9.9.9.9" ]; - }; - }; - }; - }; - }; -} diff --git a/services/samba.nix b/services/samba.nix index e6fa607..b248d33 100644 --- a/services/samba.nix +++ b/services/samba.nix @@ -2,7 +2,6 @@ services.samba = { enable = true; package = pkgs.sambaFull; - securityType = "user"; openFirewall = true; settings = { global = { diff --git a/systems/darwin/corianne.nix b/systems/darwin/corianne.nix index dea0f34..f1c1eb2 100644 --- a/systems/darwin/corianne.nix +++ b/systems/darwin/corianne.nix @@ -26,9 +26,27 @@ in { }; # Auto upgrade nix package and the daemon service. - nix.enable = true; - #services.nix-daemon.tempDir = "/nix/tmp"; - nix.package = pkgs.nix; + nix = { + enable = true; + gc = { + automatic = true; + interval = { Weekday = 1; }; + options = '' + --delete-older-than 14d + ''; + }; + # Needed for rosetta-builder, see + # + # + linux-builder = { + enable = true; + ephemeral = true; + }; + extraOptions = '' + extra-platforms = x86_64-darwin + ''; + }; + nix-rosetta-builder.onDemand = true; # Create /etc/zshrc that loads the nix-darwin environment. programs.zsh.enable = true; # default shell on catalina @@ -206,6 +224,7 @@ in { "rig" "rstudio" "signal" + "skim" "slack" "stats" "steam" diff --git a/systems/linux/bosephus.nix b/systems/linux/bosephus.nix index 98cf0e7..6c8d181 100644 --- a/systems/linux/bosephus.nix +++ b/systems/linux/bosephus.nix @@ -9,7 +9,6 @@ ./hardware-configuration/bosephus.nix ./hardware-configuration/bosephus-external-drives.nix ./../../services/samba.nix - ./../../services/pihole.nix ]; # Bootloader. @@ -17,8 +16,8 @@ boot.loader.efi.canTouchEfiVariables = true; # Ignore lid - so I can close without having the system go into sleep mode - services.logind.lidSwitch = "ignore"; - services.logind.lidSwitchDocked = "ignore"; + services.logind.settings.Login.HandleLidSwitch = "ignore"; + services.logind.settings.Login.HandleLidSwitchDocked = "ignore"; # Secrets age.secrets = { diff --git a/systems/linux/mcentire.nix b/systems/linux/mcentire.nix index 4bd52f3..fa0c08b 100644 --- a/systems/linux/mcentire.nix +++ b/systems/linux/mcentire.nix @@ -7,6 +7,7 @@ ./../../services/borgmatic.nix ./../../services/crowdsec.nix ./../../services/authentik.nix + ./../../services/freshrss.nix ]; # Use the GRUB 2 boot loader.