summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.envrc18
-rw-r--r--.gitignore1
-rw-r--r--flake.lock133
-rw-r--r--flake.nix79
-rw-r--r--module/default.nix140
-rw-r--r--treefmt.nix81
-rwxr-xr-xupdate.sh15
7 files changed, 467 insertions, 0 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..cb4ff0a
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,18 @@
+#!/usr/bin/env sh
+
+# rocie - An enterprise grocery management system
+#
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of Rocie.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+use flake
+
+root="$(git rev-parse --show-toplevel)"
+
+PATH_add ./scripts
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..92b2793
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.direnv
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..0e1c398
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,133 @@
+{
+  "nodes": {
+    "crane": {
+      "locked": {
+        "lastModified": 1773857772,
+        "narHash": "sha256-5xsK26KRHf0WytBtsBnQYC/lTWDhQuT57HJ7SzuqZcM=",
+        "owner": "ipetkov",
+        "repo": "crane",
+        "rev": "b556d7bbae5ff86e378451511873dfd07e4504cd",
+        "type": "github"
+      },
+      "original": {
+        "owner": "ipetkov",
+        "repo": "crane",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1773907119,
+        "narHash": "sha256-xwDXh9uEMUbTQ3bcQEFRVLNVpYPKuzb9TcnDVbe45uQ=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "3c0730b8b60607525c36af3e58226f2038077ae9",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable-small",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "rocie-mobile": {
+      "inputs": {
+        "crane": "crane",
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "rust-overlay": "rust-overlay",
+        "treefmt-nix": [
+          "treefmt-nix"
+        ]
+      },
+      "locked": {
+        "dirtyRev": "f6a3fb9c4d8dd86f78c9f75a23c1ac35bf35d4eb-dirty",
+        "dirtyShortRev": "f6a3fb9-dirty",
+        "lastModified": 1773902714,
+        "narHash": "sha256-90eMtdHW/J6Oigfha/a2vjMepGlna5va9gqP6SQYx04=",
+        "type": "git",
+        "url": "file:../rocie-mobile"
+      },
+      "original": {
+        "type": "git",
+        "url": "file:../rocie-mobile"
+      }
+    },
+    "rocie-server": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "treefmt-nix": [
+          "treefmt-nix"
+        ]
+      },
+      "locked": {
+        "lastModified": 1773896435,
+        "narHash": "sha256-WK8Nv8F9pfXHnNL1Tj3J/MvEEa28NZHKgK1r6xm2Wwc=",
+        "ref": "refs/heads/prime",
+        "rev": "15dfb099b13ad68b4f286fa9def77f083f79e1b2",
+        "revCount": 49,
+        "type": "git",
+        "url": "file:../rocie-server"
+      },
+      "original": {
+        "type": "git",
+        "url": "file:../rocie-server"
+      }
+    },
+    "root": {
+      "inputs": {
+        "nixpkgs": "nixpkgs",
+        "rocie-mobile": "rocie-mobile",
+        "rocie-server": "rocie-server",
+        "treefmt-nix": "treefmt-nix"
+      }
+    },
+    "rust-overlay": {
+      "inputs": {
+        "nixpkgs": [
+          "rocie-mobile",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1773889863,
+        "narHash": "sha256-tSsmZOHBgq4qfu5MNCAEsKZL1cI4avNLw2oUTXWeb74=",
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "rev": "dbfd51be2692cb7022e301d14c139accb4ee63f0",
+        "type": "github"
+      },
+      "original": {
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "type": "github"
+      }
+    },
+    "treefmt-nix": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1773297127,
+        "narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=",
+        "owner": "numtide",
+        "repo": "treefmt-nix",
+        "rev": "71b125cd05fbfd78cab3e070b73544abe24c5016",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "treefmt-nix",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..b793868
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,79 @@
+# rocie - An enterprise grocery management system
+#
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of Rocie.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+  description = "rocie";
+
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small";
+
+    rocie-mobile = {
+      url = "git+file:../rocie-mobile";
+      inputs = {
+        nixpkgs.follows = "nixpkgs";
+        treefmt-nix.follows = "treefmt-nix";
+      };
+    };
+    rocie-server = {
+      url = "git+file:../rocie-server";
+      inputs = {
+        nixpkgs.follows = "nixpkgs";
+        treefmt-nix.follows = "treefmt-nix";
+      };
+    };
+
+    treefmt-nix = {
+      url = "github:numtide/treefmt-nix";
+      inputs = {
+        nixpkgs.follows = "nixpkgs";
+      };
+    };
+  };
+
+  outputs = {
+    self,
+    nixpkgs,
+    treefmt-nix,
+    rocie-mobile,
+    rocie-server,
+  }: let
+    system = "x86_64-linux";
+    pkgs = nixpkgs.legacyPackages."${system}";
+    inherit (pkgs) lib;
+
+    treefmtEval = import ./treefmt.nix {inherit treefmt-nix pkgs;};
+
+    module = import ./module {
+      rocie-mobile =
+        rocie-mobile.outputs.packages."${system}".rocie;
+      rocie-server =
+        rocie-server.outputs.packages."${system}".rocie;
+    };
+  in {
+    checks."${system}" = {
+      formatting = treefmtEval.config.build.check self;
+    };
+
+    nixosModules = {
+      default = module;
+    };
+
+    formatter."${system}" = treefmtEval.config.build.wrapper;
+
+    devShells."${system}".default = pkgs.mkShell {
+      packages = [
+        # Releng
+        pkgs.git-bug
+        pkgs.reuse
+        pkgs.cocogitto
+      ];
+    };
+  };
+}
diff --git a/module/default.nix b/module/default.nix
new file mode 100644
index 0000000..608559b
--- /dev/null
+++ b/module/default.nix
@@ -0,0 +1,140 @@
+{
+  rocie-mobile,
+  rocie-server,
+}: {
+  lib,
+  config,
+  ...
+}: let
+  cfg = config.rocie;
+in {
+  options.rocie = {
+    enable = lib.mkEnableOption "rocie";
+    domain = lib.mkOption {
+      description = "Under which domain (vhost) to deploy rocie?";
+      type = lib.types.str;
+    };
+
+    port = lib.mkOption {
+      description = "Which port to use";
+      type = lib.types.int.port;
+      default = 8543;
+    };
+    address = lib.mkOption {
+      description = "Which address to use";
+      type = lib.types.str;
+      default = "127.0.0.1";
+    };
+
+    dbPath = lib.mkOption {
+      description = "Where to store the database";
+      type = lib.types.str;
+      default = "$STATE_DIRECTORY/storage.db";
+    };
+
+    secretKeyFile = lib.mkOption {
+      description = "Which file to use as signing key for user log-ins";
+      type = lib.types.nullOr lib.types.str;
+    };
+
+    webFrontEnd = lib.mkOption {
+      description = "Which package to use for the web front end";
+      type = lib.types.package;
+      default = rocie-mobile;
+    };
+    server = lib.mkOption {
+      description = "Which package to use for the server";
+      type = lib.types.package;
+      default = rocie-server;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.packages = [cfg.server];
+    systemd.services.rocie = {
+      wantedBy = ["default.target"];
+      serviceConfig =
+        {
+          StateDirectory = "rocie";
+
+          # Hardening
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          PrivateIPC = true;
+          PrivateTmp = true;
+          ProcSubset = "pid";
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "full";
+          RemoveIPC = true;
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+          ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [
+            "~@mount"
+            "~@swap"
+            "~@resources"
+            "~@reboot"
+            "~@raw-io"
+            "~@obsolete"
+            "~@module"
+            "~@debug"
+            "~@cpu-emulation"
+            "~@clock"
+            "~@privileged"
+          ];
+          UMask = "0027";
+
+          ExecStart = [
+            ""
+            "${lib.getExe cfg.server} \
+                        serve \
+                        --port ${cfg.port} \
+                        ${lib.strings.optionalString (cfg.secretKeyFile != null) "--secret-key-file ${cfg.secretKeyFile}"} \
+                        --host ${cfg.address} \
+                        --db-path ${cfg.dbPath}"
+          ];
+        }
+        // (
+          if (cfg.port < 1024)
+          then {
+            AmbientCapabilities = ["CAP_NET_BIND_SERVICE"];
+            CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"];
+          }
+          else {
+            # A private user cannot have process capabilities on the host's user
+            # namespace and thus CAP_NET_BIND_SERVICE has no effect.
+            PrivateUsers = true;
+            CapabilityBoundingSet = false;
+          }
+        );
+    };
+
+    services.nginx.virtualHosts."${cfg.domain}" = {
+      locations."/api" = {
+        proxyPass = "http://127.0.0.1:${cfg.port}";
+
+        recommendedProxySettings = true;
+        proxyWebsockets = true;
+      };
+
+      root = "${cfg.webFrontEnd}";
+
+      enableACME = true;
+      forceSSL = true;
+    };
+  };
+}
diff --git a/treefmt.nix b/treefmt.nix
new file mode 100644
index 0000000..8f78ebb
--- /dev/null
+++ b/treefmt.nix
@@ -0,0 +1,81 @@
+# rocie - An enterprise grocery management system
+#
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of Rocie.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+{
+  treefmt-nix,
+  pkgs,
+}:
+treefmt-nix.lib.evalModule pkgs (
+  {pkgs, ...}: {
+    # Used to find the project root
+    projectRootFile = "flake.nix";
+
+    programs = {
+      alejandra.enable = true;
+      rustfmt.enable = true;
+      clang-format.enable = true;
+      mdformat.enable = true;
+      shfmt = {
+        enable = true;
+        indent_size = 4;
+      };
+      shellcheck.enable = true;
+      prettier = {
+        enable = true;
+        settings = {
+          arrowParens = "always";
+          bracketSameLine = false;
+          bracketSpacing = true;
+          editorconfig = true;
+          embeddedLanguageFormatting = "auto";
+          endOfLine = "lf";
+          # experimentalTernaries = false;
+          htmlWhitespaceSensitivity = "css";
+          insertPragma = false;
+          jsxSingleQuote = true;
+          printWidth = 80;
+          proseWrap = "always";
+          quoteProps = "consistent";
+          requirePragma = false;
+          semi = true;
+          singleAttributePerLine = true;
+          singleQuote = false;
+          trailingComma = "all";
+          useTabs = false;
+          vueIndentScriptAndStyle = false;
+
+          tabWidth = 2;
+        };
+      };
+      stylua.enable = true;
+      ruff = {
+        enable = true;
+        format = true;
+      };
+      taplo.enable = true;
+    };
+
+    settings = {
+      global.excludes = [
+        "CHANGELOG.md"
+        "NEWS.md"
+      ];
+      formatter = {
+        clang-format = {
+          options = ["--style" "GNU"];
+        };
+        shfmt = {
+          includes = ["*.bash"];
+        };
+      };
+    };
+  }
+)
diff --git a/update.sh b/update.sh
new file mode 100755
index 0000000..f3598dd
--- /dev/null
+++ b/update.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env sh
+
+# rocie - An enterprise grocery management system
+#
+# Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of Rocie.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+nix flake update
+# vim: ft=sh