From b58906f0e55dd213585688894a627adfb4fcc82c Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Sun, 12 Apr 2026 20:36:28 -0400 Subject: [PATCH 1/6] feat(dev): add pinned openhands cli flake app --- flake.nix | 75 +++++++++++++++++++++++++++++- justfile | 5 ++ user/modules/utils/dev/default.nix | 1 - 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 76b1cc9..80b5e2c 100644 --- a/flake.nix +++ b/flake.nix @@ -27,8 +27,11 @@ }; }; - outputs = { nixpkgs, nixpkgs-unstable, nur, ... }@inputs: + outputs = { self, nixpkgs, nixpkgs-unstable, nur, ... }@inputs: let + openhandsCliVersion = "1.14.0"; + python312SlimDigest = "sha256:804ddf3251a60bbf9c92e73b7566c40428d54d0e79d3428194edf40da6521286"; + mkPkgs = system: import nixpkgs { inherit system; config = { @@ -58,6 +61,69 @@ ]; }; + mkOpenHandsCli = pkgs: pkgs.writeShellApplication { + name = "openhands-cli"; + runtimeInputs = with pkgs; [ docker coreutils ]; + text = '' + set -euo pipefail + + SANDBOX_VOLUMES="''${SANDBOX_VOLUMES:-$PWD:/workspace}" + STATE_DIR="''${OPENHANDS_STATE_DIR:-$HOME/.openhands}" + AGENT_SERVER_IMAGE_REPOSITORY="''${OPENHANDS_AGENT_SERVER_IMAGE_REPOSITORY:-ghcr.io/openhands/agent-server}" + AGENT_SERVER_IMAGE_TAG="''${OPENHANDS_AGENT_SERVER_IMAGE_TAG:-1.15.0-python}" + LLM_MODEL="''${OPENHANDS_LLM_MODEL:-openai/Qwen3-Coder-30B-A3B-Instruct-Q8_0.gguf}" + LLM_BASE_URL="''${OPENHANDS_LLM_BASE_URL:-http://192.168.0.23:8000/v1}" + LLM_API_KEY="''${OPENHANDS_LLM_API_KEY:-local-llm}" + LLM_TIMEOUT="''${OPENHANDS_LLM_TIMEOUT:-300}" + CLI_BASE_IMAGE="''${OPENHANDS_CLI_BASE_IMAGE:-python:3.12-slim@${python312SlimDigest}}" + CLI_VERSION="''${OPENHANDS_CLI_VERSION:-${openhandsCliVersion}}" + CLI_IMAGE="''${OPENHANDS_CLI_IMAGE:-local/openhands-cli:''${CLI_VERSION}}" + CONTAINER_NAME="''${OPENHANDS_CONTAINER_NAME:-openhands-cli-$(date +%Y%m%d%H%M%S)}" + + mkdir -p "$STATE_DIR" + + if ! docker image inspect "$CLI_IMAGE" >/dev/null 2>&1; then + docker build --pull \ + --build-arg BASE_IMAGE="$CLI_BASE_IMAGE" \ + --build-arg OPENHANDS_CLI_VERSION="$CLI_VERSION" \ + -t "$CLI_IMAGE" - <<'EOF' + ARG BASE_IMAGE + FROM ''${BASE_IMAGE} + ARG OPENHANDS_CLI_VERSION + RUN pip install --no-cache-dir uv \ + && uv tool install --python 3.12 "openhands==''${OPENHANDS_CLI_VERSION}" \ + && ln -sf /root/.local/bin/openhands /usr/local/bin/openhands \ + && ln -sf /root/.local/bin/openhands-acp /usr/local/bin/openhands-acp + ENV PATH="/root/.local/bin:''${PATH}" + ENTRYPOINT ["openhands"] + EOF + fi + + tty_flags=() + if [ -t 0 ] && [ -t 1 ]; then + tty_flags=(-it) + fi + + exec docker run "''${tty_flags[@]}" --rm \ + -e AGENT_SERVER_IMAGE_REPOSITORY="$AGENT_SERVER_IMAGE_REPOSITORY" \ + -e AGENT_SERVER_IMAGE_TAG="$AGENT_SERVER_IMAGE_TAG" \ + -e LLM_MODEL="$LLM_MODEL" \ + -e LLM_BASE_URL="$LLM_BASE_URL" \ + -e LLM_API_KEY="$LLM_API_KEY" \ + -e LLM_TIMEOUT="$LLM_TIMEOUT" \ + -e SANDBOX_USER_ID="$(id -u)" \ + -e SANDBOX_VOLUMES="$SANDBOX_VOLUMES" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v "$PWD":/workspace \ + -v "$STATE_DIR":/root/.openhands \ + -w /workspace \ + --add-host host.docker.internal:host-gateway \ + --name "$CONTAINER_NAME" \ + "$CLI_IMAGE" \ + "$@" + ''; + }; + in { nixosConfigurations = { @@ -85,5 +151,12 @@ age-plugin-yubikey ]; }; + + packages.x86_64-linux.openhands-cli = mkOpenHandsCli (mkPkgs "x86_64-linux"); + + apps.x86_64-linux.openhands-cli = { + type = "app"; + program = "${self.packages.x86_64-linux.openhands-cli}/bin/openhands-cli"; + }; }; } diff --git a/justfile b/justfile index 2869dcb..ad0e9e5 100644 --- a/justfile +++ b/justfile @@ -99,6 +99,11 @@ pkgs: options: @xdg-open https://search.nixos.org/options +# Run OpenHands CLI via flake app (requires a local Docker runtime) +[group('tools')] +openhands-cli *ARGS='': + @nix run .#openhands-cli -- {{ARGS}} + # NixOS-rebuild switch for the current system [group('nixos')] switch: diff --git a/user/modules/utils/dev/default.nix b/user/modules/utils/dev/default.nix index 1c10315..bf7232a 100644 --- a/user/modules/utils/dev/default.nix +++ b/user/modules/utils/dev/default.nix @@ -3,7 +3,6 @@ with lib; let cfg = config.modules.user.utils.dev; - in { options.modules.user.utils.dev = { enable = mkEnableOption "user.utils.dev"; }; config = mkIf cfg.enable { From 2bab2759e6554bcc9d5c1ee361f97bb739976e6a Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Sun, 12 Apr 2026 20:37:31 -0400 Subject: [PATCH 2/6] feat(server): restrict private services to LAN and WireGuard CIDRs --- .../machines/server/modules/frigate/default.nix | 5 +++++ .../machines/server/modules/immich/default.nix | 2 ++ system/machines/server/modules/nginx/default.nix | 16 ++++++++++++++++ .../machines/server/modules/sandpack/default.nix | 5 +++++ .../machines/server/modules/webdav/default.nix | 3 +++ 5 files changed, 31 insertions(+) diff --git a/system/machines/server/modules/frigate/default.nix b/system/machines/server/modules/frigate/default.nix index e067de0..402cfcc 100644 --- a/system/machines/server/modules/frigate/default.nix +++ b/system/machines/server/modules/frigate/default.nix @@ -7,6 +7,7 @@ let domain = "ramos.codes"; user = config.sops.placeholder."RTSP_USER"; pass = config.sops.placeholder."RTSP_PASS"; + privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") nginx.privateAllowCidrs + "\ndeny all;"; in { @@ -239,9 +240,13 @@ in services.nginx.virtualHosts."frigate.${domain}" = mkIf nginx.enable { useACMEHost = domain; forceSSL = true; + locations."/" = { + extraConfig = privateAccessRules; + }; locations."/go2rtc/" = { proxyPass = "http://127.0.0.1:1984/"; proxyWebsockets = true; + extraConfig = privateAccessRules; }; }; diff --git a/system/machines/server/modules/immich/default.nix b/system/machines/server/modules/immich/default.nix index 031336d..f38e079 100644 --- a/system/machines/server/modules/immich/default.nix +++ b/system/machines/server/modules/immich/default.nix @@ -6,6 +6,7 @@ let nginx = config.modules.system.nginx; domain = "ramos.codes"; port = 2283; + privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") nginx.privateAllowCidrs + "\ndeny all;"; in { @@ -51,6 +52,7 @@ in locations."/" = { proxyPass = "http://127.0.0.1:${toString port}"; proxyWebsockets = true; + extraConfig = privateAccessRules; }; }; }; diff --git a/system/machines/server/modules/nginx/default.nix b/system/machines/server/modules/nginx/default.nix index 44ad00f..ca93de7 100644 --- a/system/machines/server/modules/nginx/default.nix +++ b/system/machines/server/modules/nginx/default.nix @@ -4,11 +4,24 @@ with lib; let cfg = config.modules.system.nginx; domain = "ramos.codes"; + privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") cfg.privateAllowCidrs + "\ndeny all;"; in { options.modules.system.nginx = { enable = mkEnableOption "Nginx Reverse Proxy"; + + privateAllowCidrs = mkOption { + type = types.listOf types.str; + default = [ + "127.0.0.1/32" + "192.168.0.0/24" + "10.8.0.0/24" + ]; + description = '' + CIDR ranges allowed to access private vhosts (LAN + WireGuard). + ''; + }; }; config = mkIf cfg.enable { @@ -78,6 +91,7 @@ in locations."/" = { proxyPass = "http://192.168.0.23:3080"; proxyWebsockets = true; + extraConfig = privateAccessRules; }; }; @@ -87,6 +101,7 @@ in locations."/" = { proxyPass = "http://192.168.0.23:8000"; proxyWebsockets = true; + extraConfig = privateAccessRules; }; }; @@ -96,6 +111,7 @@ in locations."/" = { proxyPass = "http://192.168.0.23:8188"; proxyWebsockets = true; + extraConfig = privateAccessRules; }; }; }; diff --git a/system/machines/server/modules/sandpack/default.nix b/system/machines/server/modules/sandpack/default.nix index e4e5a9c..d8b46a1 100644 --- a/system/machines/server/modules/sandpack/default.nix +++ b/system/machines/server/modules/sandpack/default.nix @@ -4,6 +4,7 @@ with lib; let cfg = config.modules.system.sandpack; domain = "ramos.codes"; + privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") config.modules.system.nginx.privateAllowCidrs + "\ndeny all;"; staticBrowserServer = pkgs.stdenvNoCC.mkDerivation (finalAttrs: let pnpm = pkgs.pnpm_10; @@ -97,6 +98,8 @@ in locations."/" = { proxyPass = "http://127.0.0.1:4333"; extraConfig = '' + ${privateAccessRules} + add_header Access-Control-Allow-Origin "*" always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Content-Type, Authorization" always; @@ -116,6 +119,8 @@ in locations."/" = { proxyPass = "http://127.0.0.1:4324"; extraConfig = '' + ${privateAccessRules} + add_header Access-Control-Allow-Origin "*" always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Content-Type, Authorization" always; diff --git a/system/machines/server/modules/webdav/default.nix b/system/machines/server/modules/webdav/default.nix index 1b90573..1eb5684 100644 --- a/system/machines/server/modules/webdav/default.nix +++ b/system/machines/server/modules/webdav/default.nix @@ -4,6 +4,7 @@ with lib; let cfg = config.modules.system.webdav; domain = "ramos.codes"; + privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") config.modules.system.nginx.privateAllowCidrs + "\ndeny all;"; in { @@ -50,6 +51,8 @@ in locations."/" = { proxyPass = "http://127.0.0.1:8090"; extraConfig = '' + ${privateAccessRules} + proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; From f0b0371de6fb78150192b3cb9665a8a539b51b5a Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Sun, 12 Apr 2026 20:51:41 -0400 Subject: [PATCH 3/6] feat(server): add wireguard gateway with LAN access for VPN clients --- .../server/modules/wireguard/default.nix | 106 ++++++++++++++++++ system/machines/server/system.nix | 6 +- 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 system/machines/server/modules/wireguard/default.nix diff --git a/system/machines/server/modules/wireguard/default.nix b/system/machines/server/modules/wireguard/default.nix new file mode 100644 index 0000000..ff3f516 --- /dev/null +++ b/system/machines/server/modules/wireguard/default.nix @@ -0,0 +1,106 @@ +{ pkgs, lib, config, ... }: + +with lib; +let + cfg = config.modules.system.wireguard; +in +{ + options.modules.system.wireguard = { + enable = mkEnableOption "WireGuard VPN"; + + address = mkOption { + type = types.str; + default = "10.8.0.1/24"; + description = "WireGuard interface address with CIDR"; + }; + + subnet = mkOption { + type = types.str; + default = "10.8.0.0/24"; + description = "WireGuard subnet used for peer allocations"; + }; + + listenPort = mkOption { + type = types.port; + default = 51820; + description = "WireGuard UDP listen port"; + }; + + privateKeyFile = mkOption { + type = types.str; + default = "/var/lib/wireguard/server.key"; + description = "Path to WireGuard server private key"; + }; + + peers = mkOption { + type = types.listOf (types.submodule ({ ... }: { + options = { + publicKey = mkOption { + type = types.str; + description = "Peer public key"; + }; + + allowedIPs = mkOption { + type = types.listOf types.str; + description = "Allowed IPs for peer, usually a single /32"; + }; + + presharedKeyFile = mkOption { + type = types.nullOr types.str; + default = null; + description = "Optional preshared key file"; + }; + + persistentKeepalive = mkOption { + type = types.nullOr types.int; + default = 25; + description = "Persistent keepalive interval seconds"; + }; + }; + })); + default = [ ]; + description = "WireGuard peers"; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedUDPPorts = [ cfg.listenPort ]; + networking.nat.internalInterfaces = mkAfter [ "wg0" ]; + + systemd.tmpfiles.rules = [ + "d /var/lib/wireguard 0700 root root -" + ]; + + systemd.services.wireguard-generate-key = { + description = "Generate WireGuard server key if missing"; + before = [ "wireguard-wg0.service" ]; + wantedBy = [ "wireguard-wg0.service" ]; + serviceConfig = { + Type = "oneshot"; + }; + path = with pkgs; [ wireguard-tools coreutils ]; + script = '' + set -euo pipefail + + if [ ! -s "${cfg.privateKeyFile}" ]; then + umask 077 + wg genkey | tee "${cfg.privateKeyFile}" | wg pubkey > /var/lib/wireguard/server.pub + elif [ ! -s /var/lib/wireguard/server.pub ]; then + umask 077 + wg pubkey < "${cfg.privateKeyFile}" > /var/lib/wireguard/server.pub + fi + ''; + }; + + networking.wireguard.interfaces.wg0 = { + ips = [ cfg.address ]; + listenPort = cfg.listenPort; + privateKeyFile = cfg.privateKeyFile; + peers = map (peer: { + inherit (peer) publicKey allowedIPs; + presharedKeyFile = peer.presharedKeyFile; + persistentKeepalive = peer.persistentKeepalive; + }) cfg.peers; + }; + }; +} diff --git a/system/machines/server/system.nix b/system/machines/server/system.nix index c5c839a..b843f7e 100644 --- a/system/machines/server/system.nix +++ b/system/machines/server/system.nix @@ -19,6 +19,10 @@ frigate.enable = true; immich.enable = true; webdav.enable = false; + wireguard = { + enable = true; + peers = [ ]; + }; # bitcoin = { # enable = true; # electrum.enable = true; @@ -172,7 +176,7 @@ cache-size = 1000; # Camera network DHCP (isolated - no gateway = no internet) - interface = "enp2s0f1"; + interface = [ "enp2s0f1" "wg0" ]; bind-interfaces = true; dhcp-range = "192.168.1.100,192.168.1.200,24h"; From de3dd664e27fb3c986373561c0ed7ef890903409 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Sun, 12 Apr 2026 20:52:04 -0400 Subject: [PATCH 4/6] fix(server): order dnsmasq after wireguard interface --- system/machines/server/system.nix | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/system/machines/server/system.nix b/system/machines/server/system.nix index b843f7e..1d4065c 100644 --- a/system/machines/server/system.nix +++ b/system/machines/server/system.nix @@ -21,7 +21,12 @@ webdav.enable = false; wireguard = { enable = true; - peers = [ ]; + peers = [ + { + publicKey = "HRFsVXn3jeqKQLQIl0cB6KC/qia7M1gQf2lqG5HDxF8="; + allowedIPs = [ "10.8.0.2/32" ]; + } + ]; }; # bitcoin = { # enable = true; @@ -189,6 +194,11 @@ }; }; + systemd.services.dnsmasq = { + after = [ "wireguard-wg0.service" ]; + wants = [ "wireguard-wg0.service" ]; + }; + services.fail2ban = { enable = true; maxretry = 5; From b8bd06285950c12a226e56c26f0ad824965ee606 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Sun, 12 Apr 2026 21:23:34 -0400 Subject: [PATCH 5/6] fix(server): remove sslh and enforce direct nginx client IP filtering --- system/machines/server/modules/nginx/default.nix | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/system/machines/server/modules/nginx/default.nix b/system/machines/server/modules/nginx/default.nix index ca93de7..ca4fbf0 100644 --- a/system/machines/server/modules/nginx/default.nix +++ b/system/machines/server/modules/nginx/default.nix @@ -14,7 +14,6 @@ in privateAllowCidrs = mkOption { type = types.listOf types.str; default = [ - "127.0.0.1/32" "192.168.0.0/24" "10.8.0.0/24" ]; @@ -41,25 +40,12 @@ in }; }; - services.sslh = { - enable = true; - listenAddresses = [ "0.0.0.0" ]; - port = 443; - settings = { - protocols = [ - { name = "ssh"; host = "127.0.0.1"; port = "22"; } - { name = "tls"; host = "127.0.0.1"; port = "4443"; } - ]; - }; - }; - services.nginx = { enable = true; recommendedTlsSettings = true; recommendedOptimisation = true; recommendedGzipSettings = true; eventsConfig = "worker_connections 4096;"; - defaultSSLListenPort = 4443; # Catch-all default - friendly error for unknown subdomains virtualHosts."_" = { From 357db9eaa4bb6a01fe54c29872d1530dd3ca3c8c Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Sun, 12 Apr 2026 22:57:44 -0400 Subject: [PATCH 6/6] switched to opencode --- flake.nix | 73 ------------------------------ justfile | 5 -- user/modules/utils/dev/default.nix | 2 + 3 files changed, 2 insertions(+), 78 deletions(-) diff --git a/flake.nix b/flake.nix index 80b5e2c..1ecc950 100644 --- a/flake.nix +++ b/flake.nix @@ -29,9 +29,6 @@ outputs = { self, nixpkgs, nixpkgs-unstable, nur, ... }@inputs: let - openhandsCliVersion = "1.14.0"; - python312SlimDigest = "sha256:804ddf3251a60bbf9c92e73b7566c40428d54d0e79d3428194edf40da6521286"; - mkPkgs = system: import nixpkgs { inherit system; config = { @@ -61,69 +58,6 @@ ]; }; - mkOpenHandsCli = pkgs: pkgs.writeShellApplication { - name = "openhands-cli"; - runtimeInputs = with pkgs; [ docker coreutils ]; - text = '' - set -euo pipefail - - SANDBOX_VOLUMES="''${SANDBOX_VOLUMES:-$PWD:/workspace}" - STATE_DIR="''${OPENHANDS_STATE_DIR:-$HOME/.openhands}" - AGENT_SERVER_IMAGE_REPOSITORY="''${OPENHANDS_AGENT_SERVER_IMAGE_REPOSITORY:-ghcr.io/openhands/agent-server}" - AGENT_SERVER_IMAGE_TAG="''${OPENHANDS_AGENT_SERVER_IMAGE_TAG:-1.15.0-python}" - LLM_MODEL="''${OPENHANDS_LLM_MODEL:-openai/Qwen3-Coder-30B-A3B-Instruct-Q8_0.gguf}" - LLM_BASE_URL="''${OPENHANDS_LLM_BASE_URL:-http://192.168.0.23:8000/v1}" - LLM_API_KEY="''${OPENHANDS_LLM_API_KEY:-local-llm}" - LLM_TIMEOUT="''${OPENHANDS_LLM_TIMEOUT:-300}" - CLI_BASE_IMAGE="''${OPENHANDS_CLI_BASE_IMAGE:-python:3.12-slim@${python312SlimDigest}}" - CLI_VERSION="''${OPENHANDS_CLI_VERSION:-${openhandsCliVersion}}" - CLI_IMAGE="''${OPENHANDS_CLI_IMAGE:-local/openhands-cli:''${CLI_VERSION}}" - CONTAINER_NAME="''${OPENHANDS_CONTAINER_NAME:-openhands-cli-$(date +%Y%m%d%H%M%S)}" - - mkdir -p "$STATE_DIR" - - if ! docker image inspect "$CLI_IMAGE" >/dev/null 2>&1; then - docker build --pull \ - --build-arg BASE_IMAGE="$CLI_BASE_IMAGE" \ - --build-arg OPENHANDS_CLI_VERSION="$CLI_VERSION" \ - -t "$CLI_IMAGE" - <<'EOF' - ARG BASE_IMAGE - FROM ''${BASE_IMAGE} - ARG OPENHANDS_CLI_VERSION - RUN pip install --no-cache-dir uv \ - && uv tool install --python 3.12 "openhands==''${OPENHANDS_CLI_VERSION}" \ - && ln -sf /root/.local/bin/openhands /usr/local/bin/openhands \ - && ln -sf /root/.local/bin/openhands-acp /usr/local/bin/openhands-acp - ENV PATH="/root/.local/bin:''${PATH}" - ENTRYPOINT ["openhands"] - EOF - fi - - tty_flags=() - if [ -t 0 ] && [ -t 1 ]; then - tty_flags=(-it) - fi - - exec docker run "''${tty_flags[@]}" --rm \ - -e AGENT_SERVER_IMAGE_REPOSITORY="$AGENT_SERVER_IMAGE_REPOSITORY" \ - -e AGENT_SERVER_IMAGE_TAG="$AGENT_SERVER_IMAGE_TAG" \ - -e LLM_MODEL="$LLM_MODEL" \ - -e LLM_BASE_URL="$LLM_BASE_URL" \ - -e LLM_API_KEY="$LLM_API_KEY" \ - -e LLM_TIMEOUT="$LLM_TIMEOUT" \ - -e SANDBOX_USER_ID="$(id -u)" \ - -e SANDBOX_VOLUMES="$SANDBOX_VOLUMES" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v "$PWD":/workspace \ - -v "$STATE_DIR":/root/.openhands \ - -w /workspace \ - --add-host host.docker.internal:host-gateway \ - --name "$CONTAINER_NAME" \ - "$CLI_IMAGE" \ - "$@" - ''; - }; - in { nixosConfigurations = { @@ -151,12 +85,5 @@ age-plugin-yubikey ]; }; - - packages.x86_64-linux.openhands-cli = mkOpenHandsCli (mkPkgs "x86_64-linux"); - - apps.x86_64-linux.openhands-cli = { - type = "app"; - program = "${self.packages.x86_64-linux.openhands-cli}/bin/openhands-cli"; - }; }; } diff --git a/justfile b/justfile index ad0e9e5..2869dcb 100644 --- a/justfile +++ b/justfile @@ -99,11 +99,6 @@ pkgs: options: @xdg-open https://search.nixos.org/options -# Run OpenHands CLI via flake app (requires a local Docker runtime) -[group('tools')] -openhands-cli *ARGS='': - @nix run .#openhands-cli -- {{ARGS}} - # NixOS-rebuild switch for the current system [group('nixos')] switch: diff --git a/user/modules/utils/dev/default.nix b/user/modules/utils/dev/default.nix index bf7232a..89c4809 100644 --- a/user/modules/utils/dev/default.nix +++ b/user/modules/utils/dev/default.nix @@ -9,6 +9,8 @@ in home.packages = with pkgs; [ unstable.claude-code unstable.codex + unstable.opencode + bubblewrap nix-init