Skip to content

Nightly Debug Build #112

Nightly Debug Build

Nightly Debug Build #112

Workflow file for this run

name: Nightly Debug Build
on:
schedule:
- cron: "0 2 * * *" # every day at 02:00 UTC
workflow_dispatch:
inputs:
force:
description: 'Force a build even when change detection finds nothing'
type: boolean
default: false
permissions:
contents: write
actions: read # `gh run list` reads this workflow's own run history (see the gate)
# Per-ref: a newer run on the same branch supersedes the older one, but nightly
# runs on different branches don't fight over one global slot (a static group
# cancelled whichever started second — see two manual dispatches racing).
concurrency:
group: nightly-${{ github.ref }}
cancel-in-progress: true
jobs:
# ── Cheap gate: decide whether a nightly is warranted at all, so the two heavy
# build jobs below can fan out in parallel instead of one waiting on the other. ──
check:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check.outputs.skip }}
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Check whether a nightly build is warranted
id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
# Manual dispatch with force=true always builds, bypassing the gate below
# (`inputs.force` is empty on the schedule, so this only fires on a
# deliberate workflow_dispatch).
if [ "${{ inputs.force }}" = "true" ]; then
echo "skip=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Paths whose changes actually affect a shipped artifact — the Android
# module or the desktop installers (docs/ci/chore commits must not trigger
# a nightly). Kept in sync with release.yml's change detection.
paths=(
'module/*.sh'
'module/bin/kasumi-proxyctl'
'module/bin/utils.sh'
'module/webroot/cgi-bin/'
'frontend/src/'
'crates/'
'src-tauri/'
'scripts/fetch-binaries.sh'
'scripts/binaries.json'
'scripts/binary-versions.sh'
'scripts/package-release.sh'
)
# New code since the *previous nightly run*, not a rigid 24h window. The
# daily cron anchors the diff to the last time the nightly looked, so a
# commit 25h old still counts (the old window silently dropped it) and an
# unchanged tree never rebuilds. The previous run's head SHA is read from
# this workflow's own run history (built or skipped — a run always records
# the HEAD it saw, so the baseline never drifts). No prior run to compare
# against (first run under this logic, or history pruned) ⇒ build.
prev=$(gh run list --workflow nightly.yml -L 30 \
--json databaseId,headSha,createdAt \
--jq "map(select(.databaseId != ${GITHUB_RUN_ID})) | sort_by(.createdAt) | last | .headSha // empty")
if [ -z "$prev" ] || ! git cat-file -e "$prev^{commit}" 2>/dev/null; then
since_nightly=1
else
since_nightly=$(git log --oneline "$prev"..HEAD -- "${paths[@]}" 2>/dev/null | wc -l)
fi
# Code commits since the last release tag. Without this a build still
# fires right after a release: the release-bump commit touches an artifact
# path (binary-versions.sh), so it counts as new since the last nightly,
# yet the release already shipped everything — last_tag..HEAD has nothing
# new to build.
last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$last_tag" ]; then
since_release=$since_nightly
else
since_release=$(git log "$last_tag"..HEAD --oneline -- "${paths[@]}" 2>/dev/null | wc -l)
fi
if [ "$since_nightly" -gt 0 ] && [ "$since_release" -gt 0 ]; then
echo "skip=false" >> "$GITHUB_OUTPUT"
else
echo "skip=true" >> "$GITHUB_OUTPUT"
fi
# ── Android module debug zip. Mirrors release.yml's `module`/`desktop` split;
# runs in parallel with `desktop` once `check` clears them both. ──
module:
needs: check
if: needs.check.outputs.skip != 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14
with:
extra-conf: |
accept-flake-config = true
- name: Cachix
uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
with:
name: kasumi-proxy
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Install, lint & test
run: |
nix develop .#android --command bash -euo pipefail -c '
bun install --frozen-lockfile
bunx biome check
bun run frontend/scripts/check-i18n.ts
( cd frontend && bunx vitest run )
'
- name: Build debug zip
run: nix develop .#android --command bash -euo pipefail -c 'scripts/package-release.sh'
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: kasumi-proxy-debug-${{ github.sha }}
path: build/*.zip
retention-days: 7
# ── Desktop installers as nightly artifacts (unsigned), same fetch+bundle path
# as the release workflow but uploaded to the run instead of a GitHub release. ──
desktop:
needs: check
if: needs.check.outputs.skip != 'true'
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: windows-latest
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
targets: ${{ matrix.target }}
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest
- name: Install Tauri CLI
uses: taiki-e/install-action@15449e3094499af05d8d964a1c884208e4b8b595 # v2
with:
tool: tauri-cli
- name: Linux desktop deps
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev \
libayatana-appindicator3-dev patchelf
- name: Build the UI
run: |
bun install --frozen-lockfile
( cd frontend && bun run build )
- name: Fetch desktop binaries
shell: bash
run: scripts/fetch-binaries.sh desktop ${{ matrix.target }}
- name: Build desktop bundles
shell: bash
env:
# linuxdeploy/appimagetool mount themselves with FUSE, which the runner
# doesn't provide — extract-and-run instead, or AppImage bundling aborts
# the whole job ("failed to run linuxdeploy") after the .deb is built.
APPIMAGE_EXTRACT_AND_RUN: 1
run: |
# Build + stage the privilege helper as a sidecar (see release.yml).
t="${{ matrix.target }}"
ext=""; case "$t" in *windows*) ext=".exe";; esac
cargo build --release --target "$t" --bin kasumi-helper
cp "target/$t/release/kasumi-helper$ext" "src-tauri/binaries/kasumi-helper-$t$ext"
ver=$(scripts/app-version.sh) # product version from module.prop
# Nightly artifacts are unsigned: override the global
# `bundle.createUpdaterArtifacts` so `tauri build` doesn't demand the
# minisign key (only release.yml signs). The updater never points at
# nightly, so no .sig is needed here.
cargo tauri build --target "$t" \
--config src-tauri/tauri.bundle.conf.json \
--config "{\"version\":\"$ver\",\"bundle\":{\"createUpdaterArtifacts\":false}}"
- name: Collect artifacts
shell: bash
run: |
dir="target/${{ matrix.target }}/release/bundle"
mkdir -p dist
find "$dir" -type f \
\( -name '*.deb' -o -name '*.rpm' -o -name '*.AppImage' -o -name '*-setup.exe' -o -name '*.msi' \) \
-exec cp {} dist/ \;
ls -la dist
- name: Assemble Windows portable zip
if: runner.os == 'Windows'
shell: bash
run: |
# Portable build: app exe + cores (suffix stripped) + wintun.dll +
# libcronet.dll + a `portable.dat` marker that pins all state next to the
# exe at runtime.
t="${{ matrix.target }}"
out="dist/portable/Kasumi-Proxy"
mkdir -p "$out"
cp "target/$t/release/kasumi-desktop.exe" "$out/"
cp "target/$t/release/kasumi-helper.exe" "$out/"
for c in xray sing-box tun2socks hev-socks5-tunnel; do
cp "src-tauri/binaries/$c-$t.exe" "$out/$c.exe"
done
cp src-tauri/binaries/wintun.dll "$out/"
# libcronet.dll must sit next to sing-box for its naive outbound to load.
cp src-tauri/binaries/libcronet.dll "$out/"
# msys-2.0.dll must sit next to hev-socks5-tunnel (msys2 build) to load.
cp src-tauri/binaries/msys-2.0.dll "$out/"
: > "$out/portable.dat"
name=$(scripts/artifact-name.sh windows-portable)
( cd dist/portable && 7z a -tzip "../$name" Kasumi-Proxy >/dev/null )
rm -rf dist/portable
ls -la dist
- name: Assemble Linux portable zip
if: runner.os == 'Linux'
shell: bash
run: |
# Portable build: app binary + cores (suffix stripped) + a `portable.dat`
# marker that pins all state next to the binary at runtime (USB-stick run).
t="${{ matrix.target }}"
out="dist/portable/Kasumi-Proxy"
mkdir -p "$out"
cp "target/$t/release/kasumi-desktop" "$out/"
cp "target/$t/release/kasumi-helper" "$out/"
for c in xray sing-box tun2socks hev-socks5-tunnel; do
cp "src-tauri/binaries/$c-$t" "$out/$c"
done
# libcronet.so must sit next to sing-box for its naive outbound to load.
cp src-tauri/binaries/libcronet.so "$out/"
chmod +x "$out"/*
: > "$out/portable.dat"
name=$(scripts/artifact-name.sh linux-portable)
( cd dist/portable && zip -qr "../$name" Kasumi-Proxy )
rm -rf dist/portable
ls -la dist
- name: Upload desktop artifact
uses: actions/upload-artifact@v4
with:
name: kasumi-desktop-${{ matrix.target }}-${{ github.sha }}
path: dist/*
retention-days: 7