diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index a2c676e..b2c1a08 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -1,4 +1,4 @@ -name: Nix Build & Deploy +name: Nix Build & Deploy — All Services on: push: @@ -18,16 +18,20 @@ concurrency: permissions: contents: read +env: + VPS_HOST: ${{ secrets.VPS_HOST }} + VPS_USER: ${{ secrets.VPS_USER }} + jobs: - build-and-deploy: + build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - service: [hub, scraper] + service: [hub, scraper, tools-gateway, tools-workers, tools-frontend, llm-api] steps: - - name: Checkout with submodules + - name: Checkout uses: actions/checkout@v4 with: submodules: recursive @@ -40,32 +44,63 @@ jobs: sandbox = false accept-flake-config = true - - name: Use Nix cache + - name: Cache Nix 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 - - name: Nix copy via SSH + - name: Nix copy to VPS env: SSH_KEY: ${{ secrets.VPS_SSH_KEY }} - VPS_HOST: ${{ secrets.VPS_HOST }} - 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 + ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts 2>/dev/null 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: - VPS_HOST: ${{ secrets.VPS_HOST }} - VPS_USER: ${{ secrets.VPS_USER }} + SSH_KEY: ${{ secrets.VPS_SSH_KEY }} + 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: | - STORE_PATH=$(readlink result) - PROFILE_NAME="${{ matrix.service }}" ssh "$VPS_USER@$VPS_HOST" " - sudo /nix/var/nix/profiles/default/bin/nix-env --profile /nix/var/nix/profiles/$PROFILE_NAME --set '$STORE_PATH' && - sudo systemctl restart $PROFILE_NAME + for service in hub scraper tools-gateway tools-workers tools-frontend llm-api; do + state=\$(systemctl is-active \$service 2>/dev/null || echo 'not-found') + echo \" \$service: \$state\" + done " diff --git a/flake.nix b/flake.nix index e8a1433..c3da5ec 100644 --- a/flake.nix +++ b/flake.nix @@ -20,22 +20,25 @@ inherit name src; nativeBuildInputs = with pkgs; [ - cacert curl gcc gnumake openssl pkg-config python3 + cacert curl gcc gnumake openssl pkg-config python3 libclang ] ++ nativeBuildInputs; buildInputs = with pkgs; [ nodejs openssl ] ++ 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"; NODE_EXTRA_CA_CERTS = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; NODE_ENV = "production"; - HOME = "/tmp"; - CARGO_HOME = "/tmp/.cargo"; - 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; }; @@ -60,7 +63,7 @@ rev = if name == "hub" then "b007ced615dcc78392fc938350a23e8f022d83b7" else if name == "scraper" then "bc782ae4f825892f2d28901f4a5527f353106db1" else if name == "tools" then "3956b90c3ce39ffa7ffba8084937f20e11364d6b" - else if name == "llm-api" then "HEAD" + else if name == "llm-api" then "67861f384bd4f64d5236a9608b33c965fbebbc7f" else "HEAD"; submodules = true; }; @@ -122,14 +125,14 @@ WRAPPER installScript = '' 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 { name = "tools-workers-0.1.0"; src = submoduleSrc "tools"; - nativeBuildInputs = cargoDeps ++ [ pkgs.tesseract ]; + nativeBuildInputs = cargoDeps ++ [ pkgs.tesseract pkgs.leptonica ]; buildScript = '' cd backend @@ -138,8 +141,9 @@ WRAPPER ''; installScript = '' + cd backend 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 = '' mkdir -p $out/share/tools-frontend $out/bin - cp -r frontend/.next $out/share/tools-frontend/ - cp -r frontend/public $out/share/tools-frontend/ 2>/dev/null || true - cp frontend/package.json $out/share/tools-frontend/ - cp -r frontend/node_modules $out/share/tools-frontend/ + cp -r .next $out/share/tools-frontend/ + cp -r public $out/share/tools-frontend/ 2>/dev/null || true + cp package.json $out/share/tools-frontend/ + cp -r node_modules $out/share/tools-frontend/ cat > $out/bin/tools-frontend << WRAPPER #!${pkgs.runtimeShell} exec ${pkgs.bun}/bin/bun run --cwd $out/share/tools-frontend start @@ -173,10 +177,17 @@ WRAPPER llm-api = mkApp { name = "llm-api-0.1.0"; src = submoduleSrc "llm-api"; - nativeBuildInputs = cargoDeps; + nativeBuildInputs = cargoDeps ++ [ pkgs.cmake pkgs.gcc ]; buildScript = '' 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 ''; diff --git a/infra/traefik/dynamic/apps.yaml b/infra/traefik/dynamic/apps.yaml index a3d885f..fa47646 100644 --- a/infra/traefik/dynamic/apps.yaml +++ b/infra/traefik/dynamic/apps.yaml @@ -64,12 +64,12 @@ http: tools-service: loadBalancer: servers: - - url: 'http://tools:3000' + - url: 'http://host.docker.internal:3500' llm-api-service: loadBalancer: servers: - - url: 'http://llm-api:8080' + - url: 'http://host.docker.internal:8080' jaeger-service: loadBalancer: diff --git a/scripts/nix-deploy.sh b/scripts/nix-deploy.sh new file mode 100644 index 0000000..500603a --- /dev/null +++ b/scripts/nix-deploy.sh @@ -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 +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