-
Notifications
You must be signed in to change notification settings - Fork 2
Add Nix support #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| { | ||
| description = "crosspoint-sync flake"; | ||
|
|
||
| inputs = { | ||
| nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; | ||
| flake-utils.url = "github:numtide/flake-utils"; | ||
| }; | ||
|
|
||
| outputs = | ||
| { | ||
| nixpkgs, | ||
| flake-utils, | ||
| ... | ||
| }: | ||
| { | ||
| nixosModules = { | ||
| crosspoint-sync = import ./nix/module.nix; | ||
| }; | ||
| } | ||
| // flake-utils.lib.eachDefaultSystem ( | ||
| system: | ||
| let | ||
| pkgs = import nixpkgs { inherit system; }; | ||
| crosspoint-sync = pkgs.callPackage ./nix/package.nix { }; | ||
| in | ||
| { | ||
| packages = { | ||
| inherit crosspoint-sync; | ||
| default = crosspoint-sync; | ||
| }; | ||
| } | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| { | ||
| pkgs, | ||
| lib, | ||
| config, | ||
| ... | ||
| }: | ||
|
|
||
| let | ||
|
|
||
| cfg = config.services.crosspoint-sync; | ||
| crosspoint-sync = pkgs.callPackage ./package.nix { }; | ||
| in | ||
| with lib; | ||
| { | ||
|
|
||
| options.services.crosspoint-sync = { | ||
| enable = mkEnableOption "Enable crosspoint-sync server"; | ||
|
|
||
| port = mkOption { | ||
| type = types.int; | ||
| description = "Port to run crosspoint-sync server on"; | ||
| default = 8080; | ||
| }; | ||
|
|
||
| dataDir = mkOption { | ||
| type = types.str; | ||
| description = "Path to the folder containing all data"; | ||
| default = "/var/lib/crosspoint-sync"; | ||
| }; | ||
|
|
||
| databaseFile = mkOption { | ||
| type = types.str; | ||
| description = "Filename of the SQLite database in, in the dataDir"; | ||
| default = "crosspoint.db"; | ||
| }; | ||
|
|
||
| registration = mkOption { | ||
| type = types.bool; | ||
| description = "Have registration enabled or not"; | ||
| default = true; | ||
| }; | ||
|
|
||
| tokenEncryptionKeyFile = mkOption { | ||
| type = types.nullOr types.str; | ||
| description = "Path to the secret that contains the encryption key"; | ||
| }; | ||
|
|
||
| authRateLimit = mkOption { | ||
| type = types.int; | ||
| description = "Per-IP limit on registrations"; | ||
| default = 30; | ||
| }; | ||
|
|
||
| user = mkOption { | ||
| type = types.str; | ||
| description = "User to run crosspoint-sync with"; | ||
| default = "crosspoint-sync"; | ||
| }; | ||
|
|
||
| group = mkOption { | ||
| type = types.str; | ||
| description = "Group to run crosspoint-sync with"; | ||
| default = "crosspoint-sync"; | ||
| }; | ||
|
|
||
| }; | ||
|
|
||
| config = mkIf cfg.enable { | ||
| systemd.services.crosspoint-sync = { | ||
| description = "crosspoint-sync server"; | ||
| wantedBy = [ "multi-user.target" ]; | ||
|
|
||
| serviceConfig = { | ||
| Type = "simple"; | ||
| ExecStart = | ||
| if (cfg.tokenEncryptionKeyFile != null) then | ||
| pkgs.writeShellScript "crosspoint-sync-credential-loader" '' | ||
| export TOKEN_ENC_KEY = $(cat ${cfg.tokenEncryptionKeyFile}) | ||
| exec ${lib.getExe crosspoint-sync} | ||
| '' | ||
| else | ||
| "${lib.getExe crosspoint-sync}"; | ||
| Restart = "on-failure"; | ||
|
|
||
| User = cfg.user; | ||
| Group = cfg.group; | ||
|
|
||
| ReadWritePaths = [ cfg.dataDir ]; | ||
| StateDirectory = mkIf (cfg.dataDir == "/var/lib/crosspoint-sync") "crosspoint-sync"; | ||
| ProtectSystem = "strict"; | ||
| ProtectHome = true; | ||
| NoNewPrivileges = true; | ||
| PrivateDevices = true; | ||
| AmbientCapabilities = mkIf (cfg.port <= 1024) [ "CAP_NET_BIND_SERVICE" ]; | ||
| }; | ||
|
Comment on lines
+73
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- nix/module.nix ---'
sed -n '1,150p' nix/module.nix
printf '%s\n' '--- references to service identity and data directory ---'
rg -n -C 3 'dataDir|User|DynamicUser|DATABASE_PATH|TOKEN_ENC_KEY|cfgtokenEncryptionKeyFile|serviceConfig' nixRepository: crosspoint-reader/crosspoint-sync Length of output: 4445 🌐 Web query:
💡 Result: When using DynamicUser= in a systemd service, managing directory ownership and permissions requires understanding how systemd isolates these resources to prevent security issues related to UID/GID recycling [1][2]. Key mechanisms and best practices include: 1. Use Built-in Directory Directives Instead of using generic ReadWritePaths= to manage custom directory access, it is strongly recommended to use StateDirectory=, CacheDirectory=, and LogsDirectory= [1][2]. These directives handle directory creation and ownership automatically in a secure manner [1][3]. - Systemd creates these directories in private sub-paths (e.g., /var/lib/private/) which are inaccessible to other users, preventing other services from accessing files if a dynamic UID is reused [4][3][5]. - Systemd automatically manages ownership of these directories for the dynamic user assigned to your service [3][5]. 2. Ownership and Persistence - When DynamicUser= is active, systemd ensures these directories are owned by the service's current dynamic user [3][5]. - If the directory already exists, systemd may recursively change the ownership to match the current dynamic UID to ensure the service has access [3][6]. - As an optimization, if the directory is already owned by the correct user, systemd skips recursive changes to existing files, which helps with performance [3]. - To further aid efficiency, systemd attempts to reuse UIDs that already own these specified state/cache/log directories, minimizing the need for frequent recursive chown operations [7][8]. 3. Handling Custom Paths If you absolutely must use custom directories outside of the standard State/Cache/Logs structures: - You must use ReadWritePaths= to allow-list the directory access [1][2]. - Caution is required: because systemd does not automatically manage ownership for these arbitrary paths, you are responsible for ensuring the dynamic user has the necessary permissions. - Using BindPaths= or other manual workarounds to expose directories can bypass the security guarantees provided by DynamicUser= and is generally discouraged due to the risks of UID/GID recycling [1][2]. In summary, to avoid manual ownership management and security risks, define your service's data requirements using StateDirectory=, CacheDirectory=, or LogsDirectory=, and allow systemd to manage the underlying ownership and namespace mapping automatically [1][3][5]. Citations:
🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- service and storage behavior ---'
rg -n -C 4 'DATABASE_PATH|dataDir|databaseFile|sqlite|writeFile|mkdir|listen|PORT' --glob '!nix/module.nix' .
printf '%s\n' '--- module documentation and examples ---'
rg -n -C 5 'crosspoint-sync|dataDir|tokenEncryptionKeyFile' README* docs nix flake.nix 2>/dev/null || trueRepository: crosspoint-reader/crosspoint-sync Length of output: 23147 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
module = Path("nix/module.nix").read_text()
service = module[module.index("serviceConfig = {"):module.index(" };", module.index("serviceConfig = {"))]
assert not re.search(r'^\s*(User|DynamicUser)\s*=', service, re.M)
assert 'ReadWritePaths = [ cfg.dataDir ];' in service
assert 'StateDirectory = mkIf (cfg.dataDir == "/var/lib/crosspoint-sync") "crosspoint-sync";' in service
source = Path("src/db/db.ts").read_text()
assert "fs.mkdirSync(path.dirname(dbPath), { recursive: true });" in source
print("serviceConfig has no User/DynamicUser; it allow-lists cfg.dataDir.")
print("StateDirectory is enabled only for the default dataDir.")
print("The application creates the database parent directory and writes the SQLite database there.")
PYRepository: crosspoint-reader/crosspoint-sync Length of output: 386 Run the service with a non-root identity. Set 🤖 Prompt for AI Agents |
||
|
|
||
| environment = { | ||
| PORT = builtins.toString cfg.port; | ||
| DATABASE_PATH = builtins.toString (lib.path.append (/. + cfg.dataDir) cfg.databaseFile); | ||
| REGISTRATION_DISABLED = if !cfg.registration then "true" else "false"; | ||
| AUTH_RATE_LIMIT_PER_MINUTE = builtins.toString cfg.authRateLimit; | ||
| }; | ||
|
|
||
| }; | ||
|
|
||
| networking.firewall.allowedTCPPorts = [ cfg.port ]; | ||
|
|
||
| users = { | ||
| users.crosspoint-sync = mkIf (cfg.user == "crosspoint-sync") { | ||
| description = "crosspoint-sync service user"; | ||
| isSystemUser = true; | ||
| group = cfg.group; | ||
| }; | ||
|
|
||
| groups.crosspoint-sync = mkIf (cfg.group == "crosspoint-sync") { }; | ||
| }; | ||
|
|
||
| }; | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| { pkgs, lib }: | ||
|
|
||
| with pkgs; | ||
| buildNpmPackage { | ||
| pname = "crosspoint-sync"; | ||
| version = "git"; | ||
| src = ../.; | ||
|
|
||
| npmDeps = importNpmLock { | ||
| npmRoot = ../.; | ||
| }; | ||
|
|
||
| npmConfigHook = importNpmLock.npmConfigHook; | ||
|
|
||
| buildPhase = '' | ||
| npm run build | ||
| ''; | ||
|
|
||
| installPhase = '' | ||
| runHook preInstall | ||
| mkdir -p $out/lib $out/bin | ||
| cp -r package.json dist node_modules assets migrations $out/lib/ | ||
| makeWrapper ${lib.getExe nodejs} $out/bin/crosspoint-sync \ | ||
| --add-flags "$out/lib/dist/index.js" | ||
| runHook postInstall | ||
| ''; | ||
|
|
||
| nativeBuildInputs = [ pkgs.makeWrapper ]; | ||
|
|
||
| meta = { | ||
| description = "Lightweight KoSync Server for Syncing Crosspoint/CrossInk stats & progress"; | ||
| homepage = "https://github.com/crosspoint-reader/crosspoint-sync"; | ||
| mainProgram = "crosspoint-sync"; | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /nix/store/5hycgszh612nxnbjbb0lbgaws9ay8l8g-crosspoint-sync-git |
Uh oh!
There was an error while loading. Please reload this page.