mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
* fix: add missing resolved/integrity fields to package-lock.json npm omits resolved URLs and integrity hashes for workspace-local node_modules overrides. This breaks offline installers like Nix's npm ci. Add the missing fields for 25 workspace-hoisted packages. * feat: add Nix flake with package and NixOS module Add a Nix flake that builds the Paseo daemon (server + CLI) and provides a NixOS module for declarative deployment. Package (nix/package.nix): - Builds relay, server, and CLI workspaces - Skips onnxruntime-node install script (sandbox-incompatible) - Rebuilds only node-pty for native terminal support - Source filter excludes app/website/desktop workspaces NixOS module (nix/module.nix): - Systemd service with configurable user, port, listen address - allowedHosts for DNS rebinding protection - relay.enable to toggle remote access via app.paseo.sh - inheritUserEnvironment to expose user tools (git, ssh) to agents - openFirewall and extra environment variables ci: add Nix hash maintenance scripts and workflows scripts/fix-lockfile.mjs: Adds missing resolved/integrity fields to package-lock.json for workspace-local overrides. Idempotent, uses `npm view`. scripts/update-nix.sh: Runs fix-lockfile.mjs, prefetches deps, computes NAR hash, and updates npmDepsHash in nix/package.nix. Supports --check for CI. .github/workflows/nix-build.yml: Builds the Nix package on push/PR and verifies the lockfile and hash are up to date. .github/workflows/fix-nix-hash.yml: Auto-fixes lockfile signatures and Nix hash on dependabot PRs. fix: update npmDepsHash after upstream sync nix: allowlist workspace symlinks instead of blocklist Prevents build failures when upstream adds new workspace packages. * don't block PRs on nix failures * better document npm workaround * fix hash update script, and update hash * integrate with npm run build:daemon * ci: trigger nix build on highlight changes * fix(nix): update npmDepsHash --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
173 lines
5.2 KiB
Nix
173 lines
5.2 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.services.paseo;
|
|
in
|
|
{
|
|
options.services.paseo = {
|
|
enable = lib.mkEnableOption "Paseo, a self-hosted daemon for AI coding agents";
|
|
|
|
package = lib.mkPackageOption pkgs "paseo" { };
|
|
|
|
user = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "paseo";
|
|
description = "User account under which Paseo runs.";
|
|
};
|
|
|
|
group = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "paseo";
|
|
description = "Group under which Paseo runs.";
|
|
};
|
|
|
|
dataDir = lib.mkOption {
|
|
type = lib.types.str;
|
|
default =
|
|
if cfg.user == "paseo"
|
|
then "/var/lib/paseo"
|
|
else "/home/${cfg.user}/.paseo";
|
|
defaultText = lib.literalExpression ''
|
|
if cfg.user == "paseo"
|
|
then "/var/lib/paseo"
|
|
else "/home/''${cfg.user}/.paseo"
|
|
'';
|
|
description = "Directory for Paseo state (PASEO_HOME). Stores agent data, config, and logs.";
|
|
};
|
|
|
|
port = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 6767;
|
|
description = "Port for the Paseo daemon to listen on.";
|
|
};
|
|
|
|
listenAddress = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "127.0.0.1";
|
|
description = "Address for the Paseo daemon to bind to.";
|
|
};
|
|
|
|
openFirewall = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Whether to open the firewall for the Paseo daemon port.";
|
|
};
|
|
|
|
allowedHosts = lib.mkOption {
|
|
type = lib.types.either (lib.types.enum [ true ]) (lib.types.listOf lib.types.str);
|
|
default = [ ];
|
|
example = [ ".example.com" "myhost.local" ];
|
|
description = ''
|
|
Hosts allowed to connect to the Paseo daemon (DNS rebinding protection).
|
|
Localhost and IP addresses are always allowed by default.
|
|
|
|
Use a leading dot to match a domain and all its subdomains
|
|
(e.g. `".example.com"` matches `example.com` and `foo.example.com`).
|
|
|
|
Set to `true` to allow any host (not recommended).
|
|
'';
|
|
};
|
|
|
|
relay = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Whether to enable the relay connection for remote access via app.paseo.sh.";
|
|
};
|
|
};
|
|
|
|
inheritUserEnvironment = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = cfg.user != "paseo";
|
|
defaultText = lib.literalExpression ''cfg.user != "paseo"'';
|
|
description = ''
|
|
Whether to include the user's profile PATH in the service environment.
|
|
|
|
When Paseo runs as a real user (not the default system user), AI agents
|
|
need access to the user's tools (git, ssh, etc.). This adds the user's
|
|
NixOS profile and system paths so agents can use them without manually
|
|
setting PATH.
|
|
|
|
Enabled by default when `user` is set to a non-default value.
|
|
'';
|
|
};
|
|
|
|
environment = lib.mkOption {
|
|
type = lib.types.attrsOf lib.types.str;
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
PASEO_RELAY_ENDPOINT = "relay.paseo.sh:443";
|
|
}
|
|
'';
|
|
description = "Extra environment variables for the Paseo daemon.";
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
users.users.${cfg.user} = lib.mkIf (cfg.user == "paseo") {
|
|
isSystemUser = true;
|
|
group = cfg.group;
|
|
home = cfg.dataDir;
|
|
};
|
|
|
|
users.groups.${cfg.group} = lib.mkIf (cfg.group == "paseo") { };
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d ${cfg.dataDir} 0700 ${cfg.user} ${cfg.group} - -"
|
|
];
|
|
|
|
systemd.services.paseo = {
|
|
description = "Paseo - self-hosted daemon for AI coding agents";
|
|
after = [ "network.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
environment = {
|
|
NODE_ENV = "production";
|
|
PASEO_HOME = cfg.dataDir;
|
|
PASEO_LISTEN = "${cfg.listenAddress}:${toString cfg.port}";
|
|
} // lib.optionalAttrs cfg.inheritUserEnvironment {
|
|
# mkForce overrides the default PATH from NixOS's systemd module (which
|
|
# only includes store paths for coreutils/grep/sed/systemd). Our PATH
|
|
# includes /run/current-system/sw/bin which is a superset of those.
|
|
PATH = lib.mkForce (lib.concatStringsSep ":" [
|
|
"/etc/profiles/per-user/${cfg.user}/bin"
|
|
"/run/current-system/sw/bin"
|
|
"/run/wrappers/bin"
|
|
"/nix/var/nix/profiles/default/bin"
|
|
]);
|
|
} // lib.optionalAttrs (cfg.allowedHosts == true) {
|
|
PASEO_ALLOWED_HOSTS = "true";
|
|
} // lib.optionalAttrs (lib.isList cfg.allowedHosts && cfg.allowedHosts != [ ]) {
|
|
PASEO_ALLOWED_HOSTS = lib.concatStringsSep "," cfg.allowedHosts;
|
|
} // cfg.environment;
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
|
|
ExecStart =
|
|
"${cfg.package}/bin/paseo-server"
|
|
+ lib.optionalString (!cfg.relay.enable) " --no-relay";
|
|
|
|
Restart = "on-failure";
|
|
RestartSec = 5;
|
|
|
|
# Graceful shutdown (server handles SIGTERM with a 10s timeout)
|
|
KillSignal = "SIGTERM";
|
|
TimeoutStopSec = 15;
|
|
};
|
|
};
|
|
|
|
environment.systemPackages = [ cfg.package ];
|
|
|
|
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
|
|
};
|
|
}
|