feat(infra): full Nix migration — all 6 services + CI/CD
- flake.nix: 6 derivations (hub, scraper, tools-gateway, tools-workers, tools-frontend, llm-api) - Fetch submodule source via builtins.fetchGit with pinned revs - Fix cargo HOME/TMPDIR for Nix sandbox permission issues - Fix llm-api: CMake/Clang deps for llama.cpp-sys2 bindgen - Add LIBCLANG_PATH, LD_LIBRARY_PATH for Rust bindgen builds - Systemd units: tools-gateway (3501), tools-frontend (3500), tools-workers, llm-api (8080) - tools.target for grouped management - Env configs: /etc/tools/env, /etc/llm-api/env - GitHub Actions: nix-build.yml — matrix build + nix copy + deploy - Update Traefik apps.yaml: tools/host.docker.internal:3500, llm-api/host.docker.internal:8080 - iptables: allow Docker→host on 3099, 4091, 3500, 3501, 8080 - Add scripts/nix-deploy.sh for CI/CD deploy step
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
name: Nix Build & Deploy
|
name: Nix Build & Deploy — All Services
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -18,16 +18,20 @@ concurrency:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
VPS_HOST: ${{ secrets.VPS_HOST }}
|
||||||
|
VPS_USER: ${{ secrets.VPS_USER }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-deploy:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
service: [hub, scraper]
|
service: [hub, scraper, tools-gateway, tools-workers, tools-frontend, llm-api]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout with submodules
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -40,32 +44,63 @@ jobs:
|
|||||||
sandbox = false
|
sandbox = false
|
||||||
accept-flake-config = true
|
accept-flake-config = true
|
||||||
|
|
||||||
- name: Use Nix cache
|
- name: Cache Nix
|
||||||
uses: DeterminateSystems/magic-nix-cache-action@v8
|
uses: DeterminateSystems/magic-nix-cache-action@v8
|
||||||
|
|
||||||
- name: Build ${{ matrix.service }} with Nix
|
- name: Build ${{ matrix.service }}
|
||||||
run: nix build .#${{ matrix.service }} --impure --option sandbox false
|
run: nix build .#${{ matrix.service }} --impure --option sandbox false
|
||||||
|
|
||||||
- name: Nix copy via SSH
|
- name: Nix copy to VPS
|
||||||
env:
|
env:
|
||||||
SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
|
SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
|
||||||
VPS_HOST: ${{ secrets.VPS_HOST }}
|
|
||||||
VPS_USER: ${{ secrets.VPS_USER }}
|
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
echo "$SSH_KEY" > ~/.ssh/id_ed25519
|
echo "$SSH_KEY" > ~/.ssh/id_ed25519
|
||||||
chmod 600 ~/.ssh/id_ed25519
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
|
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
nix copy --to "ssh://$VPS_USER@$VPS_HOST" ./result
|
nix copy --to "ssh://$VPS_USER@$VPS_HOST" ./result
|
||||||
|
|
||||||
- name: Deploy ${{ matrix.service }} on VPS
|
deploy:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install Nix
|
||||||
|
uses: DeterminateSystems/nix-installer-action@v16
|
||||||
|
|
||||||
|
- name: Deploy all services on VPS
|
||||||
env:
|
env:
|
||||||
VPS_HOST: ${{ secrets.VPS_HOST }}
|
SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
|
||||||
VPS_USER: ${{ secrets.VPS_USER }}
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "$SSH_KEY" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
|
||||||
|
for service in hub scraper tools-gateway tools-workers tools-frontend llm-api; do
|
||||||
|
echo "=== Deploying $service ==="
|
||||||
|
ssh "$VPS_USER@$VPS_HOST" "
|
||||||
|
PROFILE=\"/nix/var/nix/profiles/\$service\"
|
||||||
|
CURRENT=\$(readlink -f \"\$PROFILE\" 2>/dev/null || echo "")
|
||||||
|
LATEST=\$(ls -1d /nix/store/*-\$service-* 2>/dev/null | tail -1)
|
||||||
|
if [ -n \"\$LATEST\" ] && [ \"\$CURRENT\" != \"\$LATEST\" ]; then
|
||||||
|
sudo /nix/var/nix/profiles/default/bin/nix-env --profile \"\$PROFILE\" --set \"\$LATEST\" &&
|
||||||
|
sudo systemctl restart \"\$service\" &&
|
||||||
|
echo \" ✅ \$service updated to \$LATEST\"
|
||||||
|
elif [ -z \"\$LATEST\" ]; then
|
||||||
|
echo \" ⚠️ \$service: no store path found\"
|
||||||
|
else
|
||||||
|
echo \" ➖ \$service: already up-to-date\"
|
||||||
|
fi
|
||||||
|
"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Verify services
|
||||||
|
env:
|
||||||
|
SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
|
||||||
run: |
|
run: |
|
||||||
STORE_PATH=$(readlink result)
|
|
||||||
PROFILE_NAME="${{ matrix.service }}"
|
|
||||||
ssh "$VPS_USER@$VPS_HOST" "
|
ssh "$VPS_USER@$VPS_HOST" "
|
||||||
sudo /nix/var/nix/profiles/default/bin/nix-env --profile /nix/var/nix/profiles/$PROFILE_NAME --set '$STORE_PATH' &&
|
for service in hub scraper tools-gateway tools-workers tools-frontend llm-api; do
|
||||||
sudo systemctl restart $PROFILE_NAME
|
state=\$(systemctl is-active \$service 2>/dev/null || echo 'not-found')
|
||||||
|
echo \" \$service: \$state\"
|
||||||
|
done
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -20,22 +20,25 @@
|
|||||||
inherit name src;
|
inherit name src;
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
cacert curl gcc gnumake openssl pkg-config python3
|
cacert curl gcc gnumake openssl pkg-config python3 libclang
|
||||||
] ++ nativeBuildInputs;
|
] ++ nativeBuildInputs;
|
||||||
|
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
nodejs openssl
|
nodejs openssl
|
||||||
] ++ buildInputs;
|
] ++ buildInputs;
|
||||||
|
|
||||||
|
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
||||||
|
LD_LIBRARY_PATH = "${pkgs.libclang.lib}/lib:${pkgs.stdenv.cc.cc.lib}/lib";
|
||||||
|
|
||||||
SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||||
NODE_EXTRA_CA_CERTS = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
NODE_EXTRA_CA_CERTS = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||||
NODE_ENV = "production";
|
NODE_ENV = "production";
|
||||||
|
|
||||||
HOME = "/tmp";
|
|
||||||
CARGO_HOME = "/tmp/.cargo";
|
|
||||||
|
|
||||||
phases = [ "unpackPhase" "buildPhase" "installPhase" ];
|
phases = [ "unpackPhase" "buildPhase" "installPhase" ];
|
||||||
buildPhase = buildScript;
|
buildPhase = ''
|
||||||
|
export HOME="$TMPDIR" CARGO_HOME="$TMPDIR/.cargo-${name}"
|
||||||
|
SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
|
||||||
|
'' + buildScript;
|
||||||
installPhase = installScript;
|
installPhase = installScript;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,7 +63,7 @@
|
|||||||
rev = if name == "hub" then "b007ced615dcc78392fc938350a23e8f022d83b7"
|
rev = if name == "hub" then "b007ced615dcc78392fc938350a23e8f022d83b7"
|
||||||
else if name == "scraper" then "bc782ae4f825892f2d28901f4a5527f353106db1"
|
else if name == "scraper" then "bc782ae4f825892f2d28901f4a5527f353106db1"
|
||||||
else if name == "tools" then "3956b90c3ce39ffa7ffba8084937f20e11364d6b"
|
else if name == "tools" then "3956b90c3ce39ffa7ffba8084937f20e11364d6b"
|
||||||
else if name == "llm-api" then "HEAD"
|
else if name == "llm-api" then "67861f384bd4f64d5236a9608b33c965fbebbc7f"
|
||||||
else "HEAD";
|
else "HEAD";
|
||||||
submodules = true;
|
submodules = true;
|
||||||
};
|
};
|
||||||
@@ -122,14 +125,14 @@ WRAPPER
|
|||||||
|
|
||||||
installScript = ''
|
installScript = ''
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp backend/target/release/tools-gateway $out/bin/tools-gateway
|
cp target/release/tools-gateway $out/bin/tools-gateway
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
tools-workers = mkApp {
|
tools-workers = mkApp {
|
||||||
name = "tools-workers-0.1.0";
|
name = "tools-workers-0.1.0";
|
||||||
src = submoduleSrc "tools";
|
src = submoduleSrc "tools";
|
||||||
nativeBuildInputs = cargoDeps ++ [ pkgs.tesseract ];
|
nativeBuildInputs = cargoDeps ++ [ pkgs.tesseract pkgs.leptonica ];
|
||||||
|
|
||||||
buildScript = ''
|
buildScript = ''
|
||||||
cd backend
|
cd backend
|
||||||
@@ -138,8 +141,9 @@ WRAPPER
|
|||||||
'';
|
'';
|
||||||
|
|
||||||
installScript = ''
|
installScript = ''
|
||||||
|
cd backend
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp backend/target/release/tools-workers $out/bin/tools-workers
|
cp target/release/tools-workers $out/bin/tools-workers
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -158,10 +162,10 @@ WRAPPER
|
|||||||
|
|
||||||
installScript = ''
|
installScript = ''
|
||||||
mkdir -p $out/share/tools-frontend $out/bin
|
mkdir -p $out/share/tools-frontend $out/bin
|
||||||
cp -r frontend/.next $out/share/tools-frontend/
|
cp -r .next $out/share/tools-frontend/
|
||||||
cp -r frontend/public $out/share/tools-frontend/ 2>/dev/null || true
|
cp -r public $out/share/tools-frontend/ 2>/dev/null || true
|
||||||
cp frontend/package.json $out/share/tools-frontend/
|
cp package.json $out/share/tools-frontend/
|
||||||
cp -r frontend/node_modules $out/share/tools-frontend/
|
cp -r node_modules $out/share/tools-frontend/
|
||||||
cat > $out/bin/tools-frontend << WRAPPER
|
cat > $out/bin/tools-frontend << WRAPPER
|
||||||
#!${pkgs.runtimeShell}
|
#!${pkgs.runtimeShell}
|
||||||
exec ${pkgs.bun}/bin/bun run --cwd $out/share/tools-frontend start
|
exec ${pkgs.bun}/bin/bun run --cwd $out/share/tools-frontend start
|
||||||
@@ -173,10 +177,17 @@ WRAPPER
|
|||||||
llm-api = mkApp {
|
llm-api = mkApp {
|
||||||
name = "llm-api-0.1.0";
|
name = "llm-api-0.1.0";
|
||||||
src = submoduleSrc "llm-api";
|
src = submoduleSrc "llm-api";
|
||||||
nativeBuildInputs = cargoDeps;
|
nativeBuildInputs = cargoDeps ++ [ pkgs.cmake pkgs.gcc ];
|
||||||
|
|
||||||
buildScript = ''
|
buildScript = ''
|
||||||
echo "=== Building llm-api ==="
|
echo "=== Building llm-api ==="
|
||||||
|
export HOME="$NIX_BUILD_TOP"
|
||||||
|
export CARGO_HOME="$NIX_BUILD_TOP/.cargo"
|
||||||
|
export NIX_CFLAGS_COMPILE="-idirafter ${pkgs.stdenv.cc.cc}/include/c++/${pkgs.stdenv.cc.cc.version} -idirafter ${pkgs.stdenv.cc.cc}/include/c++/${pkgs.stdenv.cc.cc.version}/x86_64-unknown-linux-gnu"
|
||||||
|
export NIX_LDFLAGS="-L${pkgs.stdenv.cc.cc.lib}/lib -lstdc++"
|
||||||
|
export LD_LIBRARY_PATH="${pkgs.stdenv.cc.cc.lib}/lib:${pkgs.libffi}/lib:$LD_LIBRARY_PATH"
|
||||||
|
export LLVM_CONFIG_PATH="${pkgs.clang}/bin/llvm-config"
|
||||||
|
export LIBCLANG_PATH="${pkgs.libclang.lib}/lib"
|
||||||
cargo build --release 2>&1
|
cargo build --release 2>&1
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|||||||
@@ -64,12 +64,12 @@ http:
|
|||||||
tools-service:
|
tools-service:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: 'http://tools:3000'
|
- url: 'http://host.docker.internal:3500'
|
||||||
|
|
||||||
llm-api-service:
|
llm-api-service:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: 'http://llm-api:8080'
|
- url: 'http://host.docker.internal:8080'
|
||||||
|
|
||||||
jaeger-service:
|
jaeger-service:
|
||||||
loadBalancer:
|
loadBalancer:
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ── Nix Deploy — VPS-side script ──
|
||||||
|
# Runs after `nix copy --to ssh://VPS ./result` from CI
|
||||||
|
# Usage: sudo ./deploy.sh <service-name>
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SERVICE="$1"
|
||||||
|
PROFILE="/nix/var/nix/profiles/${SERVICE}"
|
||||||
|
|
||||||
|
# Find the latest store path for this service
|
||||||
|
LATEST=$(ls -1d /nix/store/*-"${SERVICE}"-* 2>/dev/null | tail -1)
|
||||||
|
if [ -z "$LATEST" ]; then
|
||||||
|
echo "ERROR: No store path found for ${SERVICE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update profile
|
||||||
|
/nix/var/nix/profiles/default/bin/nix-env --profile "$PROFILE" --set "$LATEST"
|
||||||
|
|
||||||
|
# Restart service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now "${SERVICE}" 2>/dev/null || systemctl restart "${SERVICE}"
|
||||||
|
|
||||||
|
echo "Deployed ${SERVICE}: ${LATEST}"
|
||||||
|
systemctl is-active "${SERVICE}"
|
||||||
|
|
||||||
|
# Collect garbage (safe: only removes unreachable paths)
|
||||||
|
# nix-collect-garbage -d 2>/dev/null || true
|
||||||
Reference in New Issue
Block a user