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
@@ -1,185 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds the desktop app (Tauri) and places the resulting bundles in dist-gui/.
|
||||
#
|
||||
# scripts/build-gui.sh linux # AppImage and .deb (native architecture)
|
||||
# scripts/build-gui.sh linux-aarch64 # AppImage and .deb for aarch64
|
||||
# scripts/build-gui.sh linux-x86_64 # AppImage and .deb for x86_64 (native or cross-built from aarch64)
|
||||
# scripts/build-gui.sh windows # NSIS installer, cross-built from Linux via cargo-xwin
|
||||
# scripts/build-gui.sh windows-docker # Same as above inside an isolated container
|
||||
#
|
||||
# Windows builds use cargo-xwin and NSIS (requires: nsis, lld, llvm, clang, and
|
||||
# `cargo install cargo-xwin`).
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
platform=${1:?usage: build-gui.sh <linux|linux-x86_64|linux-aarch64|windows|windows-docker>}
|
||||
tauri() { npx --yes @tauri-apps/cli@2 "$@"; }
|
||||
|
||||
|
||||
if [ "$platform" = windows-docker ]; then
|
||||
docker volume create acaua-cargo >/dev/null
|
||||
docker volume create acaua-rustup >/dev/null
|
||||
mkdir -p dist-gui
|
||||
exec docker run --rm -v "$PWD":/src -v acaua-cargo:/root/.cargo -v acaua-rustup:/root/.rustup \
|
||||
-w /src -e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" \
|
||||
ubuntu:24.04 bash -c '
|
||||
set -e
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq curl ca-certificates build-essential pkg-config nsis lld llvm clang nodejs npm unzip >/dev/null
|
||||
[ -x /root/.cargo/bin/rustup ] || curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal >/dev/null
|
||||
export PATH=/root/.cargo/bin:$PATH
|
||||
command -v cargo-xwin >/dev/null || cargo install cargo-xwin --locked
|
||||
scripts/build-gui.sh windows
|
||||
chown -R "$HOST_UID:$HOST_GID" dist-gui apps/gui/src-tauri/target 2>/dev/null || true'
|
||||
fi
|
||||
|
||||
rm -rf dist-gui
|
||||
mkdir -p dist-gui
|
||||
cd apps/gui/src-tauri
|
||||
|
||||
# Generate derived icons if missing.
|
||||
[ -f icons/icon.ico ] || tauri icon icons/source.png -o icons
|
||||
|
||||
tauri_blob_cache="$HOME/.cache/tauri"
|
||||
if [ -d /opt/hostedtoolcache ] && [ -w /opt/hostedtoolcache ]; then
|
||||
tauri_blob_cache="/opt/hostedtoolcache/tauri-tools"
|
||||
fi
|
||||
mkdir -p "$tauri_blob_cache" "$HOME/.cache/tauri"
|
||||
|
||||
fetch_cached_tool() {
|
||||
local name=$1 url=$2 patch_elf=${3:-0}
|
||||
local cached="$tauri_blob_cache/$name"
|
||||
if [ ! -s "$cached" ]; then
|
||||
local tmp="$cached.tmp.$$"
|
||||
curl -fsSL "$url" -o "$tmp"
|
||||
if [ "$patch_elf" = 1 ]; then
|
||||
dd if=/dev/zero of="$tmp" bs=1 count=3 seek=8 conv=notrunc 2>/dev/null
|
||||
fi
|
||||
chmod +x "$tmp"
|
||||
mv -f "$tmp" "$cached"
|
||||
fi
|
||||
if [ "$cached" != "$HOME/.cache/tauri/$name" ]; then
|
||||
cp -f "$cached" "$HOME/.cache/tauri/$name"
|
||||
chmod +x "$HOME/.cache/tauri/$name"
|
||||
fi
|
||||
}
|
||||
|
||||
case "$platform" in
|
||||
linux|linux-aarch64)
|
||||
bundle="${CARGO_TARGET_DIR:-target}/release/bundle"
|
||||
rm -rf "$bundle/appimage" "$bundle/deb"
|
||||
tauri build --bundles appimage,deb
|
||||
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
|
||||
;;
|
||||
linux-x86_64)
|
||||
if [ "$(uname -m)" = x86_64 ]; then
|
||||
bundle="${CARGO_TARGET_DIR:-target}/release/bundle"
|
||||
rm -rf "$bundle/appimage" "$bundle/deb"
|
||||
tauri build --bundles appimage,deb
|
||||
else
|
||||
rustup target add x86_64-unknown-linux-gnu >/dev/null
|
||||
mkdir -p /tmp/x86_64-cross-tools "$HOME/.cache/tauri"
|
||||
cat >/tmp/x86_64-cross-tools/ldd <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
exec qemu-x86_64 /lib64/ld-linux-x86-64.so.2 --list "$@"
|
||||
EOF
|
||||
chmod +x /tmp/x86_64-cross-tools/ldd
|
||||
ln -sf /usr/bin/x86_64-linux-gnu-strip /tmp/x86_64-cross-tools/strip
|
||||
for tool_rel in glib-2.0/gio-querymodules gdk-pixbuf-2.0/gdk-pixbuf-query-loaders libgtk-3-0t64/gtk-query-immodules-3.0 libgtk-3-0/gtk-query-immodules-3.0; do
|
||||
if [ -x "/usr/lib/x86_64-linux-gnu/$tool_rel" ]; then
|
||||
tname=$(basename "$tool_rel")
|
||||
printf '#!/usr/bin/env bash\nexec qemu-x86_64 "/usr/lib/x86_64-linux-gnu/%s" "$@"\n' "$tool_rel" >"/tmp/x86_64-cross-tools/$tname"
|
||||
chmod +x "/tmp/x86_64-cross-tools/$tname"
|
||||
fi
|
||||
done
|
||||
fetch_cached_tool "linuxdeploy-07333c6-aarch64.AppImage" \
|
||||
"https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy-07333c6/linuxdeploy-aarch64.AppImage" 1
|
||||
# tauri-bundler looks for this exact cache name (no arch suffix); the
|
||||
# architecture only appears in the download URL.
|
||||
fetch_cached_tool "linuxdeploy-plugin-appimage.AppImage" \
|
||||
"https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-aarch64.AppImage" 1
|
||||
fetch_cached_tool "AppRun-x86_64" \
|
||||
"https://github.com/tauri-apps/binary-releases/releases/download/apprun-old/AppRun-x86_64" 0
|
||||
fetch_cached_tool "runtime-x86_64" \
|
||||
"https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-x86_64" 0
|
||||
# tauri-bundler runs `dd ... seek=8` on this path to hide linuxdeploy from
|
||||
# AppImage integration tools. A shell-script wrapper loses its shebang and
|
||||
# then fails with ENOENT, so use a native ELF launcher: the dd writes into
|
||||
# ELF padding and the launcher stays executable.
|
||||
launcher_src=/tmp/x86_64-cross-tools/linuxdeploy-x86_64.c
|
||||
launcher_bin="$HOME/.cache/tauri/linuxdeploy-07333c6-x86_64.AppImage"
|
||||
cat >"$launcher_src" <<'EOF'
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *home = getenv("HOME");
|
||||
if (home == NULL) { fputs("HOME is not set\n", stderr); return 127; }
|
||||
char target[4096];
|
||||
char runtime[4096];
|
||||
char path[8192];
|
||||
const char *old_path = getenv("PATH");
|
||||
snprintf(target, sizeof target, "%s/.cache/tauri/linuxdeploy-07333c6-aarch64.AppImage", home);
|
||||
snprintf(runtime, sizeof runtime, "%s/.cache/tauri/runtime-x86_64", home);
|
||||
snprintf(path, sizeof path, "/tmp/x86_64-cross-tools:%s",
|
||||
old_path ? old_path : "/usr/local/bin:/usr/bin:/bin");
|
||||
if (setenv("ARCH", "x86_64", 1) ||
|
||||
setenv("NO_STRIP", "true", 1) ||
|
||||
setenv("APPIMAGE_EXTRACT_AND_RUN", "1", 1) ||
|
||||
setenv("LDAI_RUNTIME_FILE", runtime, 1) ||
|
||||
setenv("LD_GTK_LIBRARY_PATH", "/usr/lib/x86_64-linux-gnu", 1) ||
|
||||
setenv("DEB_HOST_MULTIARCH", "x86_64-linux-gnu", 1) ||
|
||||
setenv("PKG_CONFIG_ALLOW_CROSS", "1", 1) ||
|
||||
setenv("PKG_CONFIG_LIBDIR", "/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig", 1) ||
|
||||
setenv("PATH", path, 1)) {
|
||||
perror("setenv");
|
||||
return 127;
|
||||
}
|
||||
char **args = calloc((size_t)argc + 1, sizeof *args);
|
||||
if (args == NULL) { perror("calloc"); return 127; }
|
||||
args[0] = target;
|
||||
for (int i = 1; i < argc; i++) args[i] = argv[i];
|
||||
execv(target, args);
|
||||
perror(target);
|
||||
return 127;
|
||||
}
|
||||
EOF
|
||||
cc -O2 -Wall -Wextra -o "$launcher_bin" "$launcher_src"
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
export PKG_CONFIG_LIBDIR=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig
|
||||
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc
|
||||
export CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc
|
||||
export CXX_x86_64_unknown_linux_gnu=x86_64-linux-gnu-g++
|
||||
bundle="${CARGO_TARGET_DIR:-target}/x86_64-unknown-linux-gnu/release/bundle"
|
||||
rm -rf "$bundle/appimage" "$bundle/deb"
|
||||
tauri build --target x86_64-unknown-linux-gnu --bundles appimage,deb
|
||||
fi
|
||||
cp "$bundle"/appimage/*.AppImage "$bundle"/deb/*.deb ../../../dist-gui/
|
||||
;;
|
||||
windows)
|
||||
rustup target add x86_64-pc-windows-msvc >/dev/null
|
||||
bundle="${CARGO_TARGET_DIR:-target}/x86_64-pc-windows-msvc/release/bundle/nsis"
|
||||
rm -rf "$bundle"
|
||||
tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis
|
||||
cp "$bundle"/*-setup.exe ../../../dist-gui/
|
||||
;;
|
||||
*) echo "unknown platform: $platform" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
(
|
||||
cd ../../../dist-gui
|
||||
for asset in *; do
|
||||
[ -f "$asset" ] || continue
|
||||
case "$asset" in
|
||||
*.sha256) ;;
|
||||
*)
|
||||
sha256sum "$asset" > "$asset.sha256"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
)
|
||||
|
||||
ls -1 ../../../dist-gui
|
||||
+39
-17
@@ -17,6 +17,10 @@ step() { # step "name" command...
|
||||
if "$@"; then printf '\033[32mok\033[0m %s\n' "$name"; else printf '\033[31mFAILED\033[0m %s\n' "$name"; failures+=("$name"); fi
|
||||
}
|
||||
|
||||
duckdb_dir="${XDG_CACHE_HOME:-$HOME/.cache}/acaua/duckdb"
|
||||
scripts/fetch-libduckdb.sh "$duckdb_dir" >/dev/null || { echo "could not install libduckdb" >&2; exit 1; }
|
||||
export DUCKDB_LIB_DIR="$duckdb_dir" DUCKDB_INCLUDE_DIR="$duckdb_dir" LD_LIBRARY_PATH="$duckdb_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
|
||||
step "formatting (cargo fmt)" cargo fmt --all -- --check
|
||||
# Same flags as CI: warnings are treated as errors across all targets, including tests
|
||||
step "clippy (workspace, all targets)" env RUSTFLAGS="-D warnings" cargo clippy --workspace --all-targets
|
||||
@@ -32,9 +36,16 @@ terms = [
|
||||
'updater' + '.pubkey',
|
||||
'createUpdater' + 'Artifacts',
|
||||
'plugin' + '-updater',
|
||||
'Tau' + 'ri',
|
||||
'App' + 'Image',
|
||||
]
|
||||
skip_dirs = {'.git', 'target', '.wolf', '.claude', 'dist-gui'}
|
||||
allowed = {Path('docs/architecture.md')}
|
||||
skip_dirs = {'.git', 'target', '.wolf', '.claude'}
|
||||
allowed = {
|
||||
Path('docs/architecture.md'),
|
||||
Path('docs/pt-BR/arquitetura.md'),
|
||||
Path('CHANGELOG.md'),
|
||||
Path('CHANGELOG.pt-BR.md'),
|
||||
}
|
||||
hits = []
|
||||
for path in Path('.').rglob('*'):
|
||||
if not path.is_file() or any(part in skip_dirs for part in path.parts):
|
||||
@@ -61,9 +72,9 @@ import glob, yaml
|
||||
for f in glob.glob('.gitea/**/*.yml', recursive=True):
|
||||
yaml.safe_load(open(f))
|
||||
"
|
||||
step "brand assets" test -s logo.png -a -s apps/gui/src-tauri/icons/source.png -a -s apps/gui/src-tauri/icons/icon.ico
|
||||
step "brand assets" test -s logo.png
|
||||
step "consistent Semantic Versioning 2.0.0" python3 -c "
|
||||
import json, re, sys
|
||||
import re, sys
|
||||
from pathlib import Path
|
||||
|
||||
semver_re = re.compile(
|
||||
@@ -78,22 +89,33 @@ def cargo_version(path):
|
||||
sys.exit(f'version not found in {path}')
|
||||
return m.group(1)
|
||||
|
||||
versions = [
|
||||
cargo_version('Cargo.toml'),
|
||||
cargo_version('apps/gui/src-tauri/Cargo.toml'),
|
||||
json.load(open('apps/gui/src-tauri/tauri.conf.json'))['version'],
|
||||
]
|
||||
if len(set(versions)) != 1:
|
||||
sys.exit(f'version mismatch: Cargo.toml={versions[0]}, src-tauri={versions[1]}, tauri.conf={versions[2]}')
|
||||
if not semver_re.match(versions[0]):
|
||||
sys.exit(f'invalid SemVer 2.0.0 version: {versions[0]}')
|
||||
print(versions[0])
|
||||
version = cargo_version('Cargo.toml')
|
||||
if not semver_re.match(version):
|
||||
sys.exit(f'invalid SemVer 2.0.0 version: {version}')
|
||||
print(version)
|
||||
"
|
||||
|
||||
step "DuckDB pin matches the duckdb crate" python3 -c "
|
||||
import re, sys
|
||||
from pathlib import Path
|
||||
lock = Path('Cargo.lock').read_text()
|
||||
m = re.search(r'^name = \"duckdb\"\nversion = \"1\.(\d+)\.\d+\"', lock, re.M)
|
||||
if not m:
|
||||
sys.exit('duckdb crate not found in Cargo.lock')
|
||||
enc = int(m.group(1))
|
||||
crate = f'{enc // 10000}.{enc // 100 % 100}.{enc % 100}'
|
||||
pin = re.search(r'^DUCKDB_VERSION=(\S+)$', Path('scripts/fetch-libduckdb.sh').read_text(), re.M).group(1)
|
||||
if crate != pin:
|
||||
sys.exit(f'DuckDB mismatch: duckdb crate targets {crate}, scripts/fetch-libduckdb.sh pins {pin}')
|
||||
print(f'DuckDB {pin}')
|
||||
"
|
||||
|
||||
step "forbidden stack terms" check_forbidden_terms
|
||||
if [ -f apps/gui/src-tauri/Cargo.toml ] && pkg-config --exists webkit2gtk-4.1 2>/dev/null; then
|
||||
step "desktop app (cargo check)" env RUSTFLAGS="" cargo check --manifest-path apps/gui/src-tauri/Cargo.toml
|
||||
|
||||
if command -v docker >/dev/null && docker compose version >/dev/null 2>&1; then
|
||||
step "docker compose config" docker compose config --quiet
|
||||
else
|
||||
printf '\n(desktop app: skipped, WebKitGTK development libraries not installed; built in CI)\n'
|
||||
printf '\n(docker compose config: skipped, docker compose not available)\n'
|
||||
fi
|
||||
|
||||
if [ "$full" = 1 ]; then
|
||||
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs the official prebuilt DuckDB shared library (libduckdb.so and
|
||||
# duckdb.h) for the current CPU architecture into <dest-dir>, after checking
|
||||
# its SHA-256. DuckDB is never compiled from source in this project.
|
||||
#
|
||||
# scripts/fetch-libduckdb.sh <dest-dir>
|
||||
#
|
||||
# Then build with DUCKDB_LIB_DIR=<dest-dir> and run tests or the binary with
|
||||
# LD_LIBRARY_PATH=<dest-dir> (the Docker image uses ldconfig instead).
|
||||
#
|
||||
# The pinned version MUST match the duckdb crate in Cargo.lock: duckdb-rs
|
||||
# encodes the DuckDB version in its second component (DuckDB 1.5.6 <-> duckdb
|
||||
# 1.10506.x); scripts/check.sh enforces this. To upgrade, change the version
|
||||
# and both checksums together (from the "digest" field of each asset in
|
||||
# https://api.github.com/repos/duckdb/duckdb/releases/tags/v<version>).
|
||||
# DuckDB 2.x renamed these assets to duckdb-shared-libs-linux-<arch>.tar.gz.
|
||||
set -euo pipefail
|
||||
|
||||
DUCKDB_VERSION=1.5.6
|
||||
dest=${1:?usage: fetch-libduckdb.sh <dest-dir>}
|
||||
|
||||
case "$(uname -m)" in
|
||||
x86_64 | amd64) arch=amd64 sha256=b845005f5132a7d8180057c35e14a7626632258782f871a90861b19c1c03841b ;;
|
||||
aarch64 | arm64) arch=arm64 sha256=b72ed9f05003f5e9d2015f7ceada6416b377d9dd33169cdde3c9e33897856eee ;;
|
||||
*) echo "fetch-libduckdb: unsupported architecture: $(uname -m)" >&2; exit 1 ;;
|
||||
esac
|
||||
stamp="$DUCKDB_VERSION-linux-$arch"
|
||||
|
||||
installed() { [ -f "$dest/libduckdb.so" ] && [ "$(cat "$dest/.duckdb-release" 2>/dev/null)" = "$stamp" ]; }
|
||||
if installed; then
|
||||
echo "libduckdb $stamp already in $dest"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
command -v unzip >/dev/null || { echo "fetch-libduckdb: unzip is required" >&2; exit 1; }
|
||||
parent=$(dirname "$dest")
|
||||
mkdir -p "$parent"
|
||||
# Stage next to the destination so the final rename is atomic: parallel CI
|
||||
# jobs sharing /opt/hostedtoolcache never see a half-extracted library.
|
||||
stage=$(mktemp -d "$parent/.libduckdb.XXXXXX")
|
||||
trap 'rm -rf "$stage"' EXIT
|
||||
asset="libduckdb-linux-$arch.zip"
|
||||
curl -fsSL --retry 3 -o "$stage/$asset" \
|
||||
"https://github.com/duckdb/duckdb/releases/download/v$DUCKDB_VERSION/$asset"
|
||||
echo "$sha256 $stage/$asset" | sha256sum -c --quiet - \
|
||||
|| { echo "fetch-libduckdb: checksum mismatch for $asset" >&2; exit 1; }
|
||||
# The archive also carries libduckdb_static.a (~80 MB) and duckdb.hpp; only the
|
||||
# shared library and the C header are needed.
|
||||
unzip -o -j -q "$stage/$asset" libduckdb.so duckdb.h -d "$stage/lib"
|
||||
echo "$stamp" > "$stage/lib/.duckdb-release"
|
||||
if ! mv -T "$stage/lib" "$dest" 2>/dev/null; then
|
||||
# Either another job just installed the same release, or an older one is there.
|
||||
installed || { rm -rf "$dest" && mv -T "$stage/lib" "$dest"; }
|
||||
fi
|
||||
echo "libduckdb $stamp installed in $dest"
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generates the base Tauri icon from the official project logo.
|
||||
|
||||
The official logo is `logo.png` in the repository root (RGBA, transparent
|
||||
background, suitable for both light and dark themes). It is not modified here.
|
||||
Output:
|
||||
apps/gui/src-tauri/icons/source.png 1024 px artwork centered over a rounded
|
||||
dark background; CI and `npx @tauri-apps/cli icon`
|
||||
derive all remaining icon formats from it.
|
||||
|
||||
Usage: python3 scripts/generate-logo.py
|
||||
"""
|
||||
import os
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
|
||||
ICONS_DIR = os.path.join(ROOT, "apps/gui/src-tauri/icons")
|
||||
ICON_BG = (10, 16, 28) # dark navy background matching the original artwork
|
||||
|
||||
|
||||
def crop_square(im, margin=0.04):
|
||||
"""Crops to visible pixels (opacity > 16, ignoring faint artifacts) and centers in a square."""
|
||||
box = im.getchannel("A").point(lambda v: 255 if v > 16 else 0).getbbox()
|
||||
im = im.crop(box)
|
||||
w, h = im.size
|
||||
side = round(max(w, h) * (1 + 2 * margin))
|
||||
canvas = Image.new("RGBA", (side, side), (0, 0, 0, 0))
|
||||
canvas.paste(im, ((side - w) // 2, (side - h) // 2), im)
|
||||
return canvas
|
||||
|
||||
|
||||
def main():
|
||||
logo = crop_square(Image.open(os.path.join(ROOT, "logo.png")).convert("RGBA"))
|
||||
|
||||
side = 1024
|
||||
bg = Image.new("RGBA", (side, side), ICON_BG + (255,))
|
||||
art = logo.resize((round(side * 0.82),) * 2, Image.LANCZOS)
|
||||
bg.paste(art, ((side - art.width) // 2, (side - art.height) // 2), art)
|
||||
mask = Image.new("L", (side, side), 0)
|
||||
ImageDraw.Draw(mask).rounded_rectangle((0, 0, side - 1, side - 1), radius=round(side * 0.2237), fill=255)
|
||||
icon = Image.new("RGBA", (side, side), (0, 0, 0, 0))
|
||||
icon.paste(bg, (0, 0), mask)
|
||||
os.makedirs(ICONS_DIR, exist_ok=True)
|
||||
dest = os.path.join(ICONS_DIR, "source.png")
|
||||
icon.save(dest, optimize=True)
|
||||
os.chmod(dest, 0o644)
|
||||
print("icons/source.png")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds the CLI for a target platform and packages it as expected by the updater:
|
||||
# acaua-<tag>-<platform>.tar.gz alongside a .sha256 checksum file.
|
||||
#
|
||||
# scripts/package-cli.sh v1.0.3 linux-x86_64 x86_64-unknown-linux-gnu
|
||||
#
|
||||
# Supported platforms: linux-x86_64 and linux-aarch64. When cross-compiling
|
||||
# from a non-x86_64 host, uses gcc-x86-64-linux-gnu. Writes the archives to the
|
||||
# repository root and prints their filenames.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
tag=${1:?usage: package-cli.sh <tag> <platform> <rust-target>}
|
||||
platform=${2:?usage: package-cli.sh <tag> <platform> <rust-target>}
|
||||
target=${3:?usage: package-cli.sh <tag> <platform> <rust-target>}
|
||||
|
||||
case "$target" in
|
||||
x86_64-unknown-linux-gnu)
|
||||
if [ "$(uname -m)" != x86_64 ]; then
|
||||
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc
|
||||
export CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc
|
||||
fi ;;
|
||||
esac
|
||||
|
||||
rustup target add "$target" >/dev/null 2>&1 || true
|
||||
cargo build --release --locked -p acaua-cli --target "$target"
|
||||
|
||||
name="acaua-${tag}-${platform}"
|
||||
rm -rf "$name"; mkdir "$name"
|
||||
cp "${CARGO_TARGET_DIR:-target}/${target}/release/acaua" "$name/"
|
||||
cp README.md LICENSE-MIT LICENSE-APACHE "$name/"
|
||||
|
||||
tar czf "$name.tar.gz" "$name"
|
||||
asset="$name.tar.gz"
|
||||
rm -rf "$name"
|
||||
sha256sum "$asset" > "$asset.sha256"
|
||||
echo "$asset"
|
||||
echo "$asset.sha256"
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Uploads assets to a Gitea release, replacing any existing asset with the same
|
||||
# filename so re-running a CI job never fails on duplicate attachments.
|
||||
#
|
||||
# API=https://git.example.com/api/v1/repos/owner/repo TOKEN=... \
|
||||
# scripts/release-upload.sh v1.2.3 file1 file2
|
||||
set -euo pipefail
|
||||
|
||||
: "${API:?set API (Gitea repository API URL)}"
|
||||
: "${TOKEN:?set TOKEN}"
|
||||
tag=${1:?usage: release-upload.sh <tag> <file>...}
|
||||
shift
|
||||
|
||||
auth=(-H "Authorization: token $TOKEN")
|
||||
id=$(curl -fsS "${auth[@]}" "$API/releases/tags/$tag" | jq -r .id)
|
||||
|
||||
for file in "$@"; do
|
||||
name=$(basename "$file")
|
||||
encoded=$(jq -rn --arg n "$name" '$n|@uri')
|
||||
for old in $(curl -fsS "${auth[@]}" "$API/releases/$id/assets" | jq -r --arg n "$name" '.[] | select(.name == $n) | .id'); do
|
||||
curl -fsS -X DELETE "${auth[@]}" "$API/releases/$id/assets/$old"
|
||||
done
|
||||
curl -fsS -X POST "${auth[@]}" -F "attachment=@$file" "$API/releases/$id/assets?name=$encoded" >/dev/null
|
||||
echo "uploaded: $name"
|
||||
done
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke-tests an Acauã image: the API and the dashboard answer, libduckdb is
|
||||
# linked, a HEALTHCHECK is declared and SIGTERM stops the daemon cleanly.
|
||||
# Everything runs through `docker exec`, so no port has to be reachable from
|
||||
# the CI job container. The container is always removed.
|
||||
#
|
||||
# scripts/smoke-image.sh <image-ref>
|
||||
set -euo pipefail
|
||||
|
||||
image=${1:?usage: smoke-image.sh <image-ref>}
|
||||
name="acaua-smoke-$$-$RANDOM"
|
||||
trap 'docker rm -f "$name" >/dev/null 2>&1 || true' EXIT
|
||||
fail() { echo "smoke: $*" >&2; docker logs "$name" >&2 2>&1 || true; exit 1; }
|
||||
|
||||
docker run -d --name "$name" "$image" >/dev/null
|
||||
for _ in $(seq 1 30); do
|
||||
docker exec "$name" curl -fsS http://127.0.0.1:8787/api/health >/dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
docker exec "$name" curl -fsS http://127.0.0.1:8787/api/health | grep -q '"status":"ok"' || fail "/api/health did not answer"
|
||||
docker exec "$name" curl -fsS http://127.0.0.1:8787/ | grep -qi '<!doctype html' || fail "dashboard is not served"
|
||||
docker exec "$name" acaua doctor --json | grep -A1 '"name": "duckdb"' | grep -q '"ok": true' || fail "libduckdb is not usable"
|
||||
[ "$(docker inspect -f '{{if .Config.Healthcheck}}yes{{end}}' "$name")" = yes ] || fail "image has no HEALTHCHECK"
|
||||
[ "$(docker exec "$name" id -u)" != 0 ] || fail "daemon runs as root"
|
||||
docker stop -t 10 "$name" >/dev/null
|
||||
code=$(docker inspect -f '{{.State.ExitCode}}' "$name")
|
||||
[ "$code" = 0 ] || fail "exit code $code after SIGTERM (expected 0: SIGTERM must stop serve)"
|
||||
echo "smoke: $image ok"
|
||||
Reference in new issue
Block a user