diff --git a/hosts/default.nix b/hosts/default.nix index bdcb5b2a..da7d45c7 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -79,6 +79,7 @@ self.nixosModules.pub-solar self.nixosModules.acme self.nixosModules.invoiceplane + self.nixosModules.actual ]; }; diff --git a/hosts/fae/actual.nix b/hosts/fae/actual.nix new file mode 100644 index 00000000..0ab8033d --- /dev/null +++ b/hosts/fae/actual.nix @@ -0,0 +1,28 @@ +{ + flake, + config, + pkgs, + lib, + ... +}: +let + psCfg = config.pub-solar; + xdg = config.home-manager.users."${psCfg.user.name}".xdg; +in +{ + security.acme.certs = { + "actual.faenix.eu" = { }; + }; + + services.nginx.virtualHosts = { + "actual.faenix.eu" = { + forceSSL = true; + useACMEHost = "actual.faenix.eu"; + locations."/".proxyPass = "http://127.0.0.1:${builtins.toString config.services.actual.settings.port}"; + }; + }; + + services.actual = { + enable = true; + }; +} diff --git a/hosts/fae/default.nix b/hosts/fae/default.nix index b6fc66d7..5c1ef737 100644 --- a/hosts/fae/default.nix +++ b/hosts/fae/default.nix @@ -1,6 +1,7 @@ { ... }: { imports = [ + ./actual.nix ./paperless.nix ./invoiceplane.nix ./fae.nix diff --git a/modules/actual/default.nix b/modules/actual/default.nix new file mode 100644 index 00000000..3af5d0c8 --- /dev/null +++ b/modules/actual/default.nix @@ -0,0 +1,121 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) + getExe + mkDefault + mkEnableOption + mkIf + mkOption + mkPackageOption + types + ; + + cfg = config.services.actual; + configFile = formatType.generate "config.json" cfg.settings; + dataDir = "/var/lib/actual"; + + formatType = pkgs.formats.json { }; +in +{ + options.services.actual = { + enable = mkEnableOption "actual, a privacy focused app for managing your finances"; + package = mkPackageOption pkgs "actual-server" { }; + + openFirewall = mkOption { + default = false; + type = types.bool; + description = "Whether to open the firewall for the specified port."; + }; + + settings = mkOption { + default = { }; + description = "Server settings, refer to (the documentation)[https://actualbudget.org/docs/config/] for available options."; + type = types.submodule { + freeformType = formatType.type; + + options = { + hostname = mkOption { + type = types.str; + description = "The address to listen on"; + default = "::"; + }; + + port = mkOption { + type = types.port; + description = "The port to listen on"; + default = 3000; + }; + }; + + config = { + serverFiles = mkDefault "${dataDir}/server-files"; + userFiles = mkDefault "${dataDir}/user-files"; + dataDir = mkDefault dataDir; + }; + }; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.port ]; + + systemd.services.actual = { + description = "Actual server, a local-first personal finance app"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment.ACTUAL_CONFIG_PATH = configFile; + serviceConfig = { + ExecStart = getExe cfg.package; + DynamicUser = true; + User = "actual"; + Group = "actual"; + StateDirectory = "actual"; + WorkingDirectory = dataDir; + LimitNOFILE = "1048576"; + PrivateTmp = true; + PrivateDevices = true; + StateDirectoryMode = "0700"; + Restart = "always"; + + # Hardening + CapabilityBoundingSet = ""; + LockPersonality = true; + #MemoryDenyWriteExecute = true; # Leads to coredump because V8 does JIT + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "@pkey" + ]; + UMask = "0077"; + }; + }; + }; + + meta.maintainers = [ + lib.maintainers.oddlama + lib.maintainers.patrickdag + ]; +} diff --git a/modules/default.nix b/modules/default.nix index 2274ae30..0fab57b2 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -3,6 +3,7 @@ flake = { nixosModules = rec { acme = import ./acme; + actual = import ./actual; audio = import ./audio; bluetooth = import ./bluetooth; core = import ./core; diff --git a/pkgs/actual-server.nix b/pkgs/actual-server.nix new file mode 100644 index 00000000..c729e74d --- /dev/null +++ b/pkgs/actual-server.nix @@ -0,0 +1,108 @@ +self: +with self; +let + version = "24.11.0"; + src = fetchFromGitHub { + owner = "actualbudget"; + repo = "actual-server"; + rev = "v${version}"; + hash = "sha256-GwtJ42dBJXrOBIxwdrSvNeqQCl91m1XrtS3RBpEuZX0="; + }; + + # We cannot use fetchYarnDeps because that doesn't support yarn2/berry + # lockfiles (see https://github.com/NixOS/nixpkgs/issues/254369) + offlineCache = stdenvNoCC.mkDerivation { + name = "actual-server-${version}-offline-cache"; + inherit src; + + nativeBuildInputs = [ + cacert # needed for git + gitMinimal # needed to download git dependencies + yarn + ]; + + SUPPORTED_ARCHITECTURES = builtins.toJSON { + os = [ + "darwin" + "linux" + ]; + cpu = [ + "arm" + "arm64" + "ia32" + "x64" + ]; + libc = [ + "glibc" + "musl" + ]; + }; + + buildPhase = '' + runHook preBuild + + export HOME=$(mktemp -d) + yarn config set enableTelemetry 0 + yarn config set cacheFolder $out + yarn config set --json supportedArchitectures "$SUPPORTED_ARCHITECTURES" + yarn + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out + cp -r ./node_modules $out/node_modules + + runHook postInstall + ''; + dontFixup = true; + + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + outputHash = "sha256-O/KsHAGa+zIWM3Q8+rD/MtyWBuFuce3yUSkF/t9ihMw="; + }; +in +stdenv.mkDerivation { + pname = "actual-server"; + inherit version src; + + nativeBuildInputs = [ + makeWrapper + yarn + ]; + + installPhase = '' + runHook preInstall + + mkdir -p $out/{bin,lib,lib/actual} + cp -r ${offlineCache}/node_modules/ $out/lib/actual + cp -r ./ $out/lib/actual + + makeWrapper ${lib.getExe nodejs} "$out/bin/actual-server" \ + --add-flags "$out/lib/actual/app.js" \ + --set NODE_PATH "$out/node_modules" + + runHook postInstall + ''; + + passthru = { + inherit offlineCache; + tests = nixosTests.actual; + updateScript = nix-update-script { }; + }; + + meta = { + changelog = "https://github.com/actualbudget/actual-server/releases/tag/v${version}"; + description = "A super fast privacy-focused app for managing your finances"; + homepage = "https://actualbudget.org/"; + mainProgram = "actual-server"; + license = lib.licenses.mit; + maintainers = [ + lib.maintainers.oddlama + lib.maintainers.patrickdag + ]; + }; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 9c2c9b8a..97898446 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -25,6 +25,7 @@ final: prev: with prev; { record-screen = writeShellScriptBin "record-screen" (import ./record-screen.nix final); cockroach-bin = import ./cockroach.nix final; prison-break = import ./prison-break.nix final; + actual-server = import ./actual-server.nix final; # ps-fixes }