refactor: distribute Acaua as a Docker image instead of a desktop app
Release / Plan (reuse the published image?) (push) Successful in 3s
CI / Docker image (build and smoke test) (push) Skipped
CI / Format and clippy (push) Successful in 4m32s
CI / Dependency audit (push) Successful in 11s
CI / Tests (Linux) (push) Successful in 9m15s
Release / ci (push) Successful in 9m18s
Release / Build image (arm64) (push) Failing after 7m7s
Release / Build image (amd64) (push) Failing after 13m53s
Release / Publish tags (push) Skipped
Release / Gitea release (push) Skipped
Release / Plan (reuse the published image?) (push) Successful in 3s
CI / Docker image (build and smoke test) (push) Skipped
CI / Format and clippy (push) Successful in 4m32s
CI / Dependency audit (push) Successful in 11s
CI / Tests (Linux) (push) Successful in 9m15s
Release / ci (push) Successful in 9m18s
Release / Build image (arm64) (push) Failing after 7m7s
Release / Build image (amd64) (push) Failing after 13m53s
Release / Publish tags (push) Skipped
Release / Gitea release (push) Skipped
Acaua is a collective SOC daemon consuming only Zeek log files, not a single-user tool needing local hardware like its siblings. Drop the Tauri desktop shell, installers and self-update entirely; the server and its web dashboard ship as a container, updated by pulling a new image tag. Link against the official prebuilt libduckdb instead of compiling it from source via the bundled feature, cutting image build time from 15+ minutes to about 3. Fix a real bug found along the way: as PID 1 in a container, the daemon ignored SIGTERM and was always killed after the stop timeout; it now shuts down gracefully. Also corrects the Pillar 1 beaconing score: the period estimate had a harmonic alias (a perfectly periodic 4h series resolved to 600s), and the old score formula flagged pure random traffic as suspicious (p95 = 0.839 on Poisson noise). The new formula was validated against 300 samples per scenario. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
This commit is contained in:
1 parent
1b974bb90d
commit
8a80504c45
72 files changed
+2524
-6384
No files matched your search
+25
-20
@@ -2,11 +2,9 @@ name: CI
|
||||
|
||||
on:
|
||||
# Documentation-only or local agent state changes do not trigger CI jobs.
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore: ['**.md', 'docs/**', 'LICENSE*', '.gitignore', '.githooks/**', '.wolf/**', '.claude/**', '.opencode/**', '.codex/**', '.cursor/**', '.superpowers/**', '.superset/**', '.gitea/ISSUE_TEMPLATE/**', 'scripts/release-notes.sh', 'scripts/release-upload.sh', '.gitea/workflows/release.yml']
|
||||
pull_request:
|
||||
paths-ignore: ['**.md', 'docs/**', 'LICENSE*', '.gitignore', '.githooks/**', '.wolf/**', '.claude/**', '.opencode/**', '.codex/**', '.cursor/**', '.superpowers/**', '.superset/**', '.gitea/ISSUE_TEMPLATE/**', 'scripts/release-notes.sh', 'scripts/release-upload.sh', '.gitea/workflows/release.yml']
|
||||
paths-ignore: ['**.md', 'docs/**', 'LICENSE*', '.gitignore', '.githooks/**', '.wolf/**', '.claude/**', '.opencode/**', '.codex/**', '.cursor/**', '.superpowers/**', '.superset/**', '.gitea/ISSUE_TEMPLATE/**', 'scripts/release-notes.sh', '.gitea/workflows/release.yml']
|
||||
workflow_call:
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ gitea.ref }}
|
||||
@@ -22,6 +20,11 @@ env:
|
||||
CARGO_HOME: /opt/hostedtoolcache/cargo
|
||||
RUSTUP_HOME: /opt/hostedtoolcache/rustup
|
||||
NPM_CONFIG_CACHE: /opt/hostedtoolcache/npm-cache
|
||||
# Prebuilt libduckdb (scripts/fetch-libduckdb.sh), cached on the persistent
|
||||
# runner volume. Every CI job runs on linux-arm64.
|
||||
DUCKDB_LIB_DIR: /opt/hostedtoolcache/duckdb/linux-arm64
|
||||
DUCKDB_INCLUDE_DIR: /opt/hostedtoolcache/duckdb/linux-arm64
|
||||
LD_LIBRARY_PATH: /opt/hostedtoolcache/duckdb/linux-arm64
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@@ -39,6 +42,10 @@ jobs:
|
||||
node-version: 22
|
||||
- name: UI translation coverage
|
||||
run: node scripts/check-ui-i18n.mjs
|
||||
- name: Prebuilt libduckdb
|
||||
run: |
|
||||
command -v unzip >/dev/null || scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/unzip unzip
|
||||
scripts/fetch-libduckdb.sh "$DUCKDB_LIB_DIR"
|
||||
- run: cargo fmt --all -- --check
|
||||
- run: cargo clippy --workspace --all-targets
|
||||
|
||||
@@ -51,6 +58,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Prebuilt libduckdb
|
||||
run: |
|
||||
command -v unzip >/dev/null || scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/unzip unzip
|
||||
scripts/fetch-libduckdb.sh "$DUCKDB_LIB_DIR"
|
||||
- run: cargo test --workspace
|
||||
|
||||
audit:
|
||||
@@ -67,22 +78,16 @@ jobs:
|
||||
command -v cargo-audit >/dev/null || cargo install cargo-audit --locked
|
||||
cargo audit
|
||||
|
||||
gui:
|
||||
name: Check desktop app (Linux)
|
||||
image:
|
||||
name: Docker image (build and smoke test)
|
||||
if: ${{ gitea.event_name == 'pull_request' }}
|
||||
runs-on: linux-arm64
|
||||
env:
|
||||
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/acaua-gui
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Tauri system dependencies
|
||||
timeout-minutes: 25
|
||||
run: scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/tauri-native libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
- name: Generate icons
|
||||
run: npx --yes @tauri-apps/cli@2 icon apps/gui/src-tauri/icons/source.png -o apps/gui/src-tauri/icons
|
||||
- run: cargo check --manifest-path apps/gui/src-tauri/Cargo.toml
|
||||
env:
|
||||
RUSTFLAGS: ""
|
||||
- name: Build
|
||||
run: docker build -t "acaua-ci:${{ gitea.run_id }}" .
|
||||
- name: Smoke test
|
||||
run: scripts/smoke-image.sh "acaua-ci:${{ gitea.run_id }}"
|
||||
- name: Clean up
|
||||
if: always()
|
||||
run: docker rmi -f "acaua-ci:${{ gitea.run_id }}" >/dev/null 2>&1 || true
|
||||
+136
-99
@@ -1,25 +1,146 @@
|
||||
name: Release
|
||||
|
||||
# Triggered when a v* tag is pushed: creates the Gitea Release entry with notes
|
||||
# from CHANGELOG.md and uploads the compiled CLI (scripts/package-cli.sh) with
|
||||
# a .sha256 checksum alongside each archive, plus the desktop bundles (Linux
|
||||
# aarch64, Linux x86_64, and Windows via cargo-xwin).
|
||||
# Publishes the multi-arch Acauã image (linux/amd64 + linux/arm64) to the Gitea
|
||||
# container registry. A merge into main publishes `edge`; a v* tag re-points the
|
||||
# image already built for the same inputs to X.Y.Z, X.Y and latest (no rebuild)
|
||||
# and creates the Gitea Release. Needs the secret REGISTRY_TOKEN (Gitea token
|
||||
# with write:package) visible to this repository.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: release-${{ gitea.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
# Rust toolchains, cargo registry, and caches live on the persistent runner volume.
|
||||
CARGO_HOME: /opt/hostedtoolcache/cargo
|
||||
RUSTUP_HOME: /opt/hostedtoolcache/rustup
|
||||
NPM_CONFIG_CACHE: /opt/hostedtoolcache/npm-cache
|
||||
XWIN_CACHE_DIR: /opt/hostedtoolcache/xwin-cache
|
||||
REGISTRY: git.lucasrguerra.dev.br
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Create release
|
||||
runs-on: ubuntu-latest
|
||||
# The image is built only from these paths, so their tree hashes identify it.
|
||||
# When inputs-<hash> already exists (docs-only merge, workflow change, or a tag
|
||||
# on a commit main already published), nothing is built or tested again.
|
||||
plan:
|
||||
name: Plan (reuse the published image?)
|
||||
runs-on: linux-arm64
|
||||
outputs:
|
||||
inputs: ${{ steps.plan.outputs.inputs }}
|
||||
reuse: ${{ steps.plan.outputs.reuse }}
|
||||
image: ${{ steps.plan.outputs.image }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- id: plan
|
||||
env:
|
||||
REPO: ${{ gitea.repository }}
|
||||
run: |
|
||||
inputs=$(git rev-parse HEAD:Dockerfile HEAD:.dockerignore HEAD:Cargo.toml HEAD:Cargo.lock \
|
||||
HEAD:crates HEAD:apps/gui/ui HEAD:logo.png HEAD:scripts/fetch-libduckdb.sh | sha256sum | cut -c1-16)
|
||||
ns="${REPO,,}"
|
||||
token=$(curl -fsS "https://$REGISTRY/v2/token?service=container_registry&scope=repository:$ns:pull" | jq -r .token)
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' -I \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H 'Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json' \
|
||||
"https://$REGISTRY/v2/$ns/manifests/inputs-$inputs")
|
||||
echo "inputs-$inputs -> HTTP $code"
|
||||
reuse=false; [ "$code" = 200 ] && reuse=true
|
||||
echo "inputs=$inputs" >> "$GITHUB_OUTPUT"
|
||||
echo "reuse=$reuse" >> "$GITHUB_OUTPUT"
|
||||
echo "image=$REGISTRY/$ns" >> "$GITHUB_OUTPUT"
|
||||
|
||||
ci:
|
||||
needs: plan
|
||||
if: ${{ needs.plan.outputs.reuse != 'true' }}
|
||||
uses: ./.gitea/workflows/ci.yml
|
||||
|
||||
# Each architecture is built on a native runner (no QEMU) and pushed under a
|
||||
# per-architecture tag; publish joins them into one manifest list.
|
||||
build:
|
||||
name: Build image (${{ matrix.arch }})
|
||||
needs: [plan, ci]
|
||||
if: ${{ needs.plan.outputs.reuse != 'true' }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { arch: amd64, platform: linux/amd64, runner: linux-amd64 }
|
||||
- { arch: arm64, platform: linux/arm64, runner: linux-arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- name: Build and push (per architecture)
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
push: true
|
||||
tags: ${{ needs.plan.outputs.image }}:build-${{ gitea.sha }}-${{ matrix.arch }}
|
||||
cache-from: type=registry,ref=${{ needs.plan.outputs.image }}:buildcache-${{ matrix.arch }}
|
||||
cache-to: type=registry,ref=${{ needs.plan.outputs.image }}:buildcache-${{ matrix.arch }},mode=max
|
||||
# Runs the image that was just pushed, on its own architecture.
|
||||
- name: Smoke test
|
||||
run: scripts/smoke-image.sh "${{ needs.plan.outputs.image }}:build-${{ gitea.sha }}-${{ matrix.arch }}"
|
||||
|
||||
publish:
|
||||
name: Publish tags
|
||||
needs: [plan, build]
|
||||
# build is skipped, not successful, when the image is reused.
|
||||
if: ${{ !cancelled() && needs.plan.result == 'success' && (needs.build.result == 'success' || needs.plan.outputs.reuse == 'true') }}
|
||||
runs-on: linux-arm64
|
||||
steps:
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
- name: Tags
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ needs.plan.outputs.image }}
|
||||
flavor: latest=false
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=latest,enable=${{ startsWith(gitea.ref, 'refs/tags/v') && !contains(gitea.ref_name, '-') }}
|
||||
type=raw,value=edge,enable=${{ gitea.ref == 'refs/heads/main' }}
|
||||
type=sha,format=long
|
||||
- name: Apply tags
|
||||
run: |
|
||||
image='${{ needs.plan.outputs.image }}'
|
||||
tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}')
|
||||
inputs='${{ needs.plan.outputs.inputs }}'
|
||||
if [ '${{ needs.plan.outputs.reuse }}' = true ]; then
|
||||
docker buildx imagetools create $tags "$image:inputs-$inputs"
|
||||
else
|
||||
docker buildx imagetools create $tags -t "$image:inputs-$inputs" \
|
||||
"$image:build-${{ gitea.sha }}-amd64" "$image:build-${{ gitea.sha }}-arm64"
|
||||
fi
|
||||
- name: Check architectures
|
||||
run: |
|
||||
docker buildx imagetools inspect "${{ needs.plan.outputs.image }}:inputs-${{ needs.plan.outputs.inputs }}" \
|
||||
--format '{{json .Manifest}}' \
|
||||
| jq -r '.manifests[] | select(.platform.os == "linux") | "\(.platform.os)/\(.platform.architecture)"' \
|
||||
| tee platforms.txt
|
||||
grep -qx linux/amd64 platforms.txt && grep -qx linux/arm64 platforms.txt
|
||||
- name: Summary
|
||||
run: |
|
||||
{ echo "### Acauã image published (linux/amd64, linux/arm64)"; echo; echo '```'
|
||||
echo "${{ steps.meta.outputs.tags }}"; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
gitea-release:
|
||||
name: Gitea release
|
||||
needs: [plan, publish]
|
||||
if: ${{ !cancelled() && needs.publish.result == 'success' && startsWith(gitea.ref, 'refs/tags/v') }}
|
||||
runs-on: linux-arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
@@ -29,98 +150,14 @@ jobs:
|
||||
TAG: ${{ gitea.ref_name }}
|
||||
TOKEN: ${{ gitea.token }}
|
||||
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
|
||||
IMAGE: ${{ needs.plan.outputs.image }}
|
||||
run: |
|
||||
# Re-running the workflow does not recreate an existing release.
|
||||
if curl -fsS -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" >/dev/null 2>&1; then
|
||||
echo "release $TAG already exists"; exit 0
|
||||
fi
|
||||
scripts/release-notes.sh "$TAG" > notes.md
|
||||
{ scripts/release-notes.sh "$TAG"; echo; echo '```bash'; echo "docker pull $IMAGE:${TAG#v}"; echo '```'; } > notes.md
|
||||
pre=false; case "$TAG" in *-*) pre=true ;; esac
|
||||
jq -n --arg tag "$TAG" --arg name "Acauã ${TAG#v}" --rawfile body notes.md --argjson pre "$pre" \
|
||||
'{tag_name:$tag, name:$name, body:$body, prerelease:$pre}' > release.json
|
||||
curl -fsS -X POST "$API/releases" -H "Authorization: token $TOKEN" \
|
||||
-H 'Content-Type: application/json' -d @release.json >/dev/null
|
||||
|
||||
cli:
|
||||
name: CLI (${{ matrix.platform }})
|
||||
needs: release
|
||||
runs-on: ${{ matrix.runner }}
|
||||
env:
|
||||
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/acaua-release-cli-${{ matrix.platform }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { platform: linux-aarch64, target: aarch64-unknown-linux-gnu, apt: "", runner: linux-arm64 }
|
||||
- { platform: linux-x86_64, target: x86_64-unknown-linux-gnu, apt: "gcc-x86-64-linux-gnu", runner: linux-amd64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
- name: Platform cross-linker
|
||||
if: ${{ matrix.apt != '' && matrix.runner != 'linux-amd64' }}
|
||||
run: scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/cli-x86_64 ${{ matrix.apt }}
|
||||
- name: Build and package
|
||||
run: scripts/package-cli.sh "${{ gitea.ref_name }}" "${{ matrix.platform }}" "${{ matrix.target }}" | tee packages.txt
|
||||
- name: Upload to release
|
||||
env:
|
||||
TOKEN: ${{ gitea.token }}
|
||||
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
|
||||
run: scripts/release-upload.sh "${{ gitea.ref_name }}" $(cat packages.txt)
|
||||
|
||||
gui:
|
||||
name: Desktop app (${{ matrix.platform }})
|
||||
needs: release
|
||||
runs-on: ${{ matrix.runner }}
|
||||
env:
|
||||
CARGO_TARGET_DIR: /opt/hostedtoolcache/cargo-target/acaua-release-gui-${{ matrix.platform }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { platform: linux-aarch64, runner: linux-arm64 }
|
||||
- { platform: linux-x86_64, runner: linux-amd64 }
|
||||
- { platform: windows, runner: linux-amd64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Tauri dependencies (Linux aarch64)
|
||||
if: matrix.platform == 'linux-aarch64'
|
||||
timeout-minutes: 25
|
||||
run: scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/tauri-native libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
- name: Tauri dependencies (Linux x86_64)
|
||||
if: matrix.platform == 'linux-x86_64'
|
||||
timeout-minutes: 25
|
||||
run: |
|
||||
if [ "$(uname -m)" = aarch64 ]; then
|
||||
dpkg --add-architecture amd64
|
||||
. /etc/os-release
|
||||
if [ "${ID:-}" = ubuntu ]; then
|
||||
if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then
|
||||
grep -q '^Architectures:' /etc/apt/sources.list.d/ubuntu.sources || sed -i '/^Types: deb/a Architectures: arm64' /etc/apt/sources.list.d/ubuntu.sources
|
||||
printf 'Types: deb\nURIs: http://archive.ubuntu.com/ubuntu/\nSuites: %s %s-updates %s-security\nComponents: main universe\nArchitectures: amd64\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\n' "$VERSION_CODENAME" "$VERSION_CODENAME" "$VERSION_CODENAME" >/etc/apt/sources.list.d/amd64.sources
|
||||
elif [ -f /etc/apt/sources.list ]; then
|
||||
sed -i 's/^deb http/deb [arch=arm64] http/' /etc/apt/sources.list
|
||||
printf 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ %s main universe\ndeb [arch=amd64] http://archive.ubuntu.com/ubuntu/ %s-updates main universe\ndeb [arch=amd64] http://archive.ubuntu.com/ubuntu/ %s-security main universe\n' "$VERSION_CODENAME" "$VERSION_CODENAME" "$VERSION_CODENAME" >/etc/apt/sources.list.d/amd64.list
|
||||
fi
|
||||
fi
|
||||
scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/tauri-amd64 gcc-x86-64-linux-gnu g++-x86-64-linux-gnu pkg-config patchelf file squashfs-tools qemu-user libglib2.0-bin libwebkit2gtk-4.1-dev:amd64 libayatana-appindicator3-dev:amd64 librsvg2-dev:amd64
|
||||
else
|
||||
scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/tauri-native libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
fi
|
||||
- name: Cross-compilation tools (Windows)
|
||||
if: matrix.platform == 'windows'
|
||||
run: |
|
||||
scripts/apt-install.sh /opt/hostedtoolcache/apt-cache/tauri-win nsis lld llvm clang
|
||||
command -v cargo-xwin >/dev/null || cargo install cargo-xwin --locked
|
||||
- name: Build and package
|
||||
run: scripts/build-gui.sh "${{ matrix.platform }}"
|
||||
- name: Upload to release
|
||||
env:
|
||||
TOKEN: ${{ gitea.token }}
|
||||
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
|
||||
run: scripts/release-upload.sh "${{ gitea.ref_name }}" dist-gui/*
|
||||
Reference in new issue
Block a user