From 9561853d2c5c18749dd7d8d3eca25a360e2d8dfe Mon Sep 17 00:00:00 2001 From: Collin Williams <96917990+bluedragon1221@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:34:57 -0600 Subject: Add copilot-api package and service - Package copilot-api (GitHub Copilot API proxy) using bun - Create systemd service module for copilot-api - Enable service on ganymede listening on localhost:4141 - Add SSH port forwarding from mercury to ganymede for copilot-api - Update gitignore to exclude Claude Code artifacts Co-Authored-By: Claude (claude-sonnet-4.5) --- .gitignore | 4 ++ hosts/ganymede/config.nix | 10 ++++ hosts/ganymede/secrets/copilot-token.age | 11 ++++ hosts/mercury/hjem.nix | 2 + hosts/mercury/nixos.nix | 2 +- modules/services/nixos/copilot-api.nix | 62 +++++++++++++++++++++ modules/services/nixos/default.nix | 1 + modules/services/options.nix | 19 +++++++ pkgs/copilot-api/default.nix | 93 ++++++++++++++++++++++++++++++++ secrets.nix | 1 + 10 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 hosts/ganymede/secrets/copilot-token.age create mode 100644 modules/services/nixos/copilot-api.nix create mode 100644 pkgs/copilot-api/default.nix diff --git a/.gitignore b/.gitignore index b2be92b..b40218b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ result + +# Claude Code artifacts +CLAUDE.md +.claude/ diff --git a/hosts/ganymede/config.nix b/hosts/ganymede/config.nix index b968e3f..70cbfb2 100644 --- a/hosts/ganymede/config.nix +++ b/hosts/ganymede/config.nix @@ -22,6 +22,11 @@ file = ./secrets/wireguard-pk.age; owner = "systemd-network"; }; + + "copilot-token" = { + file = ./secrets/copilot-token.age; + owner = "copilot-api"; + }; }; terminal = { @@ -112,6 +117,11 @@ enable = true; envFile = config.collinux.secrets."caddy-env".path; }; + + copilot-api = { + enable = true; + githubToken = config.collinux.secrets."copilot-token".path; + }; }; }; } diff --git a/hosts/ganymede/secrets/copilot-token.age b/hosts/ganymede/secrets/copilot-token.age new file mode 100644 index 0000000..7dd6e51 --- /dev/null +++ b/hosts/ganymede/secrets/copilot-token.age @@ -0,0 +1,11 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEpnNHlXUSBDSlBB +cXJaNHU0V3VjVnB5czdya0lYMjBTdFFWRlhvWE5lVTRJenBhL3hRCmREcnJwUEo4 +UDVXQTFxdkk3ZXJrVE05cGZWT040U282bldhcm5QVkdSWW8KLT4gUCl4fUBGLWdy +ZWFzZSB4LHJZRVt7ICVYIjVGdlVbICR6fCBkdAp4T0pZM1hBRm9WcmFFQjJqaW9Y +Z2x5cE9mQk83U0pDRzdMUXozTkxjUC9FaW5MOUM4NjM3SlhTTnJvVldjK01KCm03 +TTBFdGtEWkZHVmc4RDVMc1QrNndNR2FHOXRreW5FZEhYdG9BdWZpYVBBb0ZSSFl2 +bFN1UUFWCi0tLSBVaUpmVjhibW5jWW9nS2pJV1JXVjJaZ2JYZ0VHQkY0emwrd1NL +QXFpNmN3Cv69mt8dZSWNQqM8qDledKrYS7PBZDoIXy+FivL42kaUxF0jXjHXefED +DsjubBCEK0OiAh6nyvuEn9wbSkaALmpqjMARM32QyAOiam2skY0LB8E= +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/mercury/hjem.nix b/hosts/mercury/hjem.nix index d066f69..d76f76f 100644 --- a/hosts/mercury/hjem.nix +++ b/hosts/mercury/hjem.nix @@ -8,6 +8,8 @@ prismlauncher mpv + claude-code + lagrange musescore diff --git a/hosts/mercury/nixos.nix b/hosts/mercury/nixos.nix index 21907e5..5d8a63f 100644 --- a/hosts/mercury/nixos.nix +++ b/hosts/mercury/nixos.nix @@ -25,7 +25,7 @@ name = "ganymede"; user = "collin"; monitoringPort = 20000; - extraArguments = "-N -D 9090 collin@williamsfam.us.com"; + extraArguments = "-N -D 9090 -L 4141:localhost:4141 collin@williamsfam.us.com"; } ]; diff --git a/modules/services/nixos/copilot-api.nix b/modules/services/nixos/copilot-api.nix new file mode 100644 index 0000000..d9d4da3 --- /dev/null +++ b/modules/services/nixos/copilot-api.nix @@ -0,0 +1,62 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.collinux.services.copilot-api; +in { + config = lib.mkIf cfg.enable { + users.groups."copilot-api" = {}; + users.users."copilot-api" = { + isSystemUser = true; + group = "copilot-api"; + home = "/var/lib/copilot-api"; + createHome = true; + }; + + systemd.services."copilot-api" = { + description = "GitHub Copilot API Proxy"; + restartIfChanged = true; + wants = ["network-online.target"]; + after = ["network-online.target"]; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + User = "copilot-api"; + Group = "copilot-api"; + Type = "simple"; + Restart = "on-failure"; + RestartSec = "5s"; + + # Load environment variables from file (e.g., GH_TOKEN) + EnvironmentFile = lib.mkIf (cfg.githubToken != null) cfg.githubToken; + + # Security hardening + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + NoNewPrivileges = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + RestrictSUIDSGID = true; + + # Allow writing to state directory + StateDirectory = "copilot-api"; + WorkingDirectory = "/var/lib/copilot-api"; + + ExecStart = let + copilot-api = pkgs.callPackage ../../../pkgs/copilot-api {}; + # Use bash to read token from environment and pass to command + startScript = pkgs.writeShellScript "copilot-api-start" '' + exec ${copilot-api}/bin/copilot-api start \ + --port ${toString cfg.port} \ + --host ${cfg.listenAddr} \ + ${lib.optionalString (cfg.githubToken != null) "--github-token \"$GH_TOKEN\""} + ''; + in "${startScript}"; + }; + }; + }; +} diff --git a/modules/services/nixos/default.nix b/modules/services/nixos/default.nix index d0e170c..3533908 100644 --- a/modules/services/nixos/default.nix +++ b/modules/services/nixos/default.nix @@ -15,5 +15,6 @@ ./minecraft.nix ./copyparty.nix ./qbittorrent.nix + ./copilot-api.nix ]; } diff --git a/modules/services/options.nix b/modules/services/options.nix index 5744471..17a4e6b 100644 --- a/modules/services/options.nix +++ b/modules/services/options.nix @@ -164,5 +164,24 @@ in { example = "/run/secrets.d/caddy-env"; }; }; + + copilot-api = { + enable = mkEnableOption "GitHub Copilot API proxy"; + listenAddr = mkOption { + description = "Address to listen on"; + type = ipAddr; + default = "127.0.0.1"; + }; + port = mkOption { + description = "Port to listen on"; + type = types.port; + default = 4141; + }; + githubToken = mkOption { + description = "Path to file containing GitHub token"; + type = types.nullOr types.str; + default = null; + }; + }; }; } diff --git a/pkgs/copilot-api/default.nix b/pkgs/copilot-api/default.nix new file mode 100644 index 0000000..a66fe1a --- /dev/null +++ b/pkgs/copilot-api/default.nix @@ -0,0 +1,93 @@ +{ + pkgs, + lib, + ... +}: let + inherit (pkgs) stdenv bun makeBinaryWrapper fetchFromGitHub; + + node_modules = stdenv.mkDerivation { + pname = "copilot-api-node_modules"; + version = "0.7.0"; + + src = fetchFromGitHub { + owner = "ericc-ch"; + repo = "copilot-api"; + tag = "v0.7.0"; + hash = "sha256-rUUqf9QalVZDN3aw9ze5Uh+y5xvH6zdSgGN6ZLDjkDQ="; + }; + + nativeBuildInputs = [bun]; + + impureEnvVars = lib.fetchers.proxyImpureEnvVars; + dontPatchShebangs = true; + + patchPhase = '' + # Remove prepare script that tries to setup git hooks + sed -i '/"prepare":/d' package.json + ''; + + buildPhase = '' + runHook preBuild + export HOME=$TMPDIR + bun install --no-progress --frozen-lockfile + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir -p $out + cp -R ./node_modules $out + runHook postInstall + ''; + + outputHash = "sha256-+AH8eRG3SwwU86Pa/hFSW5nWD0v1po6z7xoLKUzq3qY="; + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + }; +in + stdenv.mkDerivation { + pname = "copilot-api"; + version = "0.7.0"; + + src = fetchFromGitHub { + owner = "ericc-ch"; + repo = "copilot-api"; + tag = "v0.7.0"; + hash = "sha256-rUUqf9QalVZDN3aw9ze5Uh+y5xvH6zdSgGN6ZLDjkDQ="; + }; + + nativeBuildInputs = [makeBinaryWrapper]; + buildInputs = [bun pkgs.nodejs]; + + configurePhase = '' + runHook preConfigure + # Don't copy node_modules - let bun install them fresh + runHook postConfigure + ''; + + buildPhase = '' + runHook preBuild + export HOME=$TMPDIR + # Use the cached node_modules + cp -R ${node_modules}/node_modules . + chmod -R +w node_modules + # Try using bun's native build with correct entry point + mkdir -p dist + ${bun}/bin/bun build src/main.ts --outdir dist --target bun --format esm --sourcemap + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/lib/copilot-api + cp -r dist node_modules package.json $out/lib/copilot-api/ + + mkdir -p $out/bin + makeWrapper ${bun}/bin/bun $out/bin/copilot-api \ + --prefix PATH : ${lib.makeBinPath [bun]} \ + --add-flags "$out/lib/copilot-api/dist/main.js" + + runHook postInstall + ''; + } diff --git a/secrets.nix b/secrets.nix index a46e065..67ab5fd 100644 --- a/secrets.nix +++ b/secrets.nix @@ -8,4 +8,5 @@ in { "hosts/ganymede/secrets/collin-copyparty-password.age".publicKeys = [mercury.host_pubkey ganymede.host_pubkey]; "hosts/ganymede/secrets/collin-forgejo-password.age".publicKeys = [mercury.host_pubkey ganymede.host_pubkey]; "hosts/ganymede/secrets/wireguard-pk.age".publicKeys = [ganymede.host_pubkey]; + "hosts/ganymede/secrets/copilot-token.age".publicKeys = [ganymede.host_pubkey]; } -- cgit v1.3.1