485 lines
14 KiB
Bash
485 lines
14 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
WORKSPACE_MANIFEST="$ROOT_DIR/Cargo.toml"
|
|
CRATE_MANIFEST="$ROOT_DIR/traffic_core/Cargo.toml"
|
|
TARGET_BIN="$ROOT_DIR/target/release/awg2-harness"
|
|
REBOOT_BIN="$ROOT_DIR/target/release/awg2-reboot-scheduler"
|
|
RUST_TOOLCHAIN_FILE="$ROOT_DIR/rust-toolchain.toml"
|
|
RUST_TOOLCHAIN_VERSION="1.95.0"
|
|
|
|
PREPARE_BUILD_ENV=false
|
|
PREPARE_RUNTIME_ENV=false
|
|
BOOTSTRAP_RUST=false
|
|
DO_BUILD=false
|
|
PACKAGE=false
|
|
INSTALL=false
|
|
ENABLE=false
|
|
YES=false
|
|
SKIP_FMT=false
|
|
HAS_ACTION=false
|
|
|
|
DIST_DIR="$ROOT_DIR/dist"
|
|
PACKAGE_VERSION=""
|
|
|
|
RUN_AS_ROOT=()
|
|
|
|
BUILD_PACKAGES=(
|
|
ca-certificates
|
|
curl
|
|
build-essential
|
|
pkg-config
|
|
file
|
|
shellcheck
|
|
dpkg-dev
|
|
)
|
|
|
|
RUNTIME_PACKAGES=(
|
|
iproute2
|
|
wireguard-tools
|
|
nftables
|
|
kmod
|
|
util-linux
|
|
procps
|
|
systemd
|
|
)
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
./tools/build.sh
|
|
./tools/build.sh --prepare-build-env --yes
|
|
./tools/build.sh --prepare-runtime-env --yes
|
|
./tools/build.sh --bootstrap-rust --yes
|
|
./tools/build.sh --build
|
|
./tools/build.sh --package
|
|
./tools/build.sh --build --package
|
|
./tools/build.sh --install
|
|
./tools/build.sh --enable
|
|
|
|
Flags:
|
|
--prepare-build-env Install missing build dependencies via apt
|
|
--prepare-runtime-env Install missing runtime dependencies via apt
|
|
--bootstrap-rust Install rustup/toolchain pinned by this repo
|
|
--build Run build gate (default when no action flags)
|
|
--package Build Debian package into dist/ (runs build gate by default)
|
|
--dist-dir <path> Output directory for package artifacts (default: ./dist)
|
|
--version <version> Override package version (default: read from Cargo.toml)
|
|
--install Install binary and systemd unit (no enable/start)
|
|
--enable Enable and start awg2-harness.service
|
|
--yes Confirm system mutations (apt/rustup)
|
|
--skip-fmt Skip cargo fmt --check
|
|
--help Show this help
|
|
Notes:
|
|
--install is not the production deployment path; it is for manual source-tree installs (staging/debug).
|
|
Use generated .deb packages on production hosts: apt install ./awg2-harness_<version>_amd64.deb
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--prepare-build-env) PREPARE_BUILD_ENV=true; HAS_ACTION=true ;;
|
|
--prepare-runtime-env) PREPARE_RUNTIME_ENV=true; HAS_ACTION=true ;;
|
|
--bootstrap-rust) BOOTSTRAP_RUST=true; HAS_ACTION=true ;;
|
|
--build) DO_BUILD=true; HAS_ACTION=true ;;
|
|
--package) PACKAGE=true; HAS_ACTION=true ;;
|
|
--dist-dir)
|
|
[[ $# -ge 2 ]] || { echo "--dist-dir requires value"; exit 1; }
|
|
DIST_DIR="$2"
|
|
shift
|
|
;;
|
|
--version)
|
|
[[ $# -ge 2 ]] || { echo "--version requires value"; exit 1; }
|
|
PACKAGE_VERSION="$2"
|
|
shift
|
|
;;
|
|
--install) INSTALL=true; HAS_ACTION=true ;;
|
|
--enable) ENABLE=true; HAS_ACTION=true ;;
|
|
--yes) YES=true ;;
|
|
--skip-fmt) SKIP_FMT=true ;;
|
|
--help|-h) usage; exit 0 ;;
|
|
*) echo "Unknown arg: $1"; usage; exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [[ "$HAS_ACTION" == false ]]; then
|
|
DO_BUILD=true
|
|
fi
|
|
|
|
require_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || {
|
|
echo "Missing command: $1"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
setup_root_runner() {
|
|
if [[ "$(id -u)" -eq 0 ]]; then
|
|
RUN_AS_ROOT=()
|
|
else
|
|
require_cmd sudo
|
|
RUN_AS_ROOT=(sudo)
|
|
fi
|
|
}
|
|
|
|
ensure_supported_host() {
|
|
if [[ ! -f /etc/os-release ]]; then
|
|
echo "Unsupported OS: missing /etc/os-release. Only Debian 13 amd64 is supported."
|
|
exit 1
|
|
fi
|
|
|
|
# shellcheck disable=SC1091
|
|
source /etc/os-release
|
|
|
|
if [[ "${ID:-}" != "debian" || "${VERSION_ID:-}" != "13" ]]; then
|
|
echo "Unsupported OS: ${PRETTY_NAME:-unknown}. Only Debian 13 amd64 is supported."
|
|
exit 1
|
|
fi
|
|
|
|
local arch
|
|
arch="$(dpkg --print-architecture 2>/dev/null || uname -m)"
|
|
if [[ "$arch" != "amd64" && "$arch" != "x86_64" ]]; then
|
|
echo "Unsupported arch: $arch. Only amd64 is supported."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
apt_install_missing() {
|
|
local packages=("$@")
|
|
local missing=()
|
|
|
|
require_cmd dpkg-query
|
|
require_cmd apt-get
|
|
|
|
for pkg in "${packages[@]}"; do
|
|
if ! dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then
|
|
missing+=("$pkg")
|
|
fi
|
|
done
|
|
|
|
if [[ "${#missing[@]}" -eq 0 ]]; then
|
|
echo "All required packages already installed"
|
|
return 0
|
|
fi
|
|
|
|
echo "Missing packages: ${missing[*]}"
|
|
if [[ "$YES" != true ]]; then
|
|
echo "Re-run with --yes to install them"
|
|
exit 1
|
|
fi
|
|
|
|
setup_root_runner
|
|
"${RUN_AS_ROOT[@]}" apt-get update
|
|
"${RUN_AS_ROOT[@]}" apt-get install -y --no-install-recommends "${missing[@]}"
|
|
}
|
|
|
|
bootstrap_rust() {
|
|
require_cmd curl
|
|
|
|
if [[ "$YES" != true ]]; then
|
|
echo "This will install rustup and Rust toolchain $RUST_TOOLCHAIN_VERSION"
|
|
echo "Re-run with --yes to proceed"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v rustup >/dev/null 2>&1; then
|
|
local rustup_script
|
|
rustup_script="$(mktemp)"
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o "$rustup_script"
|
|
sh "$rustup_script" -y --profile minimal --default-toolchain "$RUST_TOOLCHAIN_VERSION"
|
|
rm -f "$rustup_script"
|
|
fi
|
|
|
|
export PATH="$HOME/.cargo/bin:$PATH"
|
|
require_cmd rustup
|
|
rustup toolchain install "$RUST_TOOLCHAIN_VERSION" --profile minimal --component rustfmt --component clippy
|
|
rustup component add rustfmt clippy --toolchain "$RUST_TOOLCHAIN_VERSION"
|
|
rustc --version
|
|
}
|
|
|
|
prepare_build_gate() {
|
|
require_cmd cargo
|
|
require_cmd rustc
|
|
require_cmd gcc
|
|
require_cmd pkg-config
|
|
require_cmd file
|
|
require_cmd ldd
|
|
|
|
if [[ ! -f "$RUST_TOOLCHAIN_FILE" ]]; then
|
|
echo "Missing rust toolchain pin file: $RUST_TOOLCHAIN_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$ROOT_DIR/Cargo.lock" ]]; then
|
|
echo "Missing lockfile: $ROOT_DIR/Cargo.lock"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$SKIP_FMT" != true ]]; then
|
|
cargo fmt --manifest-path "$WORKSPACE_MANIFEST" --all --check
|
|
fi
|
|
|
|
if [[ ! -f "$WORKSPACE_MANIFEST" ]]; then
|
|
echo "Missing workspace manifest: $WORKSPACE_MANIFEST"
|
|
exit 1
|
|
fi
|
|
|
|
cargo clippy --locked --workspace --all-targets --manifest-path "$WORKSPACE_MANIFEST" -- -D warnings
|
|
cargo test --locked --workspace --release --manifest-path "$WORKSPACE_MANIFEST"
|
|
cargo build --locked --workspace --release --manifest-path "$WORKSPACE_MANIFEST"
|
|
cargo metadata --locked --format-version 1 --manifest-path "$WORKSPACE_MANIFEST" >/dev/null
|
|
|
|
if command -v systemd-analyze >/dev/null 2>&1; then
|
|
local docker_unit_present=false
|
|
if [[ -e /lib/systemd/system/docker.service || -e /usr/lib/systemd/system/docker.service || -e /etc/systemd/system/docker.service ]]; then
|
|
docker_unit_present=true
|
|
fi
|
|
|
|
if [[ -x /usr/sbin/awg2-harness && -x /usr/sbin/awg2-reboot-scheduler && "$docker_unit_present" == true ]]; then
|
|
systemd-analyze verify "$ROOT_DIR/awg2-harness.service"
|
|
systemd-analyze verify "$ROOT_DIR/awg2-reboot-scheduler.service"
|
|
else
|
|
echo "Skipping source-tree systemd-analyze verify: package runtime files or docker.service are not present on this build host"
|
|
fi
|
|
fi
|
|
|
|
file "$TARGET_BIN"
|
|
file "$REBOOT_BIN"
|
|
ldd "$TARGET_BIN" || true
|
|
ldd "$REBOOT_BIN" || true
|
|
"$TARGET_BIN" --help >/dev/null
|
|
"$REBOOT_BIN" --help >/dev/null
|
|
sha256sum "$TARGET_BIN"
|
|
sha256sum "$REBOOT_BIN"
|
|
ls -lh "$TARGET_BIN"
|
|
ls -lh "$REBOOT_BIN"
|
|
}
|
|
|
|
install_artifacts() {
|
|
require_cmd install
|
|
require_cmd systemctl
|
|
|
|
if [[ ! -f "$TARGET_BIN" || ! -f "$REBOOT_BIN" ]]; then
|
|
echo "Binary not found: $TARGET_BIN or $REBOOT_BIN"
|
|
echo "Run build first: ./tools/build.sh"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f /etc/awg2-harness/config.toml ]]; then
|
|
echo "Missing /etc/awg2-harness/config.toml"
|
|
echo "Refusing to install example config as production config"
|
|
echo "Create config manually from $ROOT_DIR/config.example.toml and re-run with --install"
|
|
exit 1
|
|
fi
|
|
|
|
setup_root_runner
|
|
"${RUN_AS_ROOT[@]}" install -d -m 0755 /usr/sbin
|
|
"${RUN_AS_ROOT[@]}" install -m 0755 "$TARGET_BIN" /usr/sbin/awg2-harness
|
|
"${RUN_AS_ROOT[@]}" install -m 0755 "$REBOOT_BIN" /usr/sbin/awg2-reboot-scheduler
|
|
"${RUN_AS_ROOT[@]}" install -d -m 0755 /lib/systemd/system
|
|
"${RUN_AS_ROOT[@]}" install -m 0644 "$ROOT_DIR/awg2-harness.service" /lib/systemd/system/awg2-harness.service
|
|
"${RUN_AS_ROOT[@]}" install -m 0644 "$ROOT_DIR/awg2-reboot-scheduler.service" /lib/systemd/system/awg2-reboot-scheduler.service
|
|
|
|
if command -v systemd-analyze >/dev/null 2>&1; then
|
|
"${RUN_AS_ROOT[@]}" systemd-analyze verify /lib/systemd/system/awg2-harness.service
|
|
"${RUN_AS_ROOT[@]}" systemd-analyze verify /lib/systemd/system/awg2-reboot-scheduler.service
|
|
fi
|
|
"${RUN_AS_ROOT[@]}" systemctl daemon-reload
|
|
}
|
|
|
|
read_package_version_from_manifest() {
|
|
awk -F '"' '/^[[:space:]]*version[[:space:]]*=[[:space:]]*"/ { print $2; exit }' "$CRATE_MANIFEST"
|
|
}
|
|
|
|
package_deb() {
|
|
require_cmd dpkg-deb
|
|
require_cmd install
|
|
require_cmd sha256sum
|
|
require_cmd file
|
|
require_cmd ldd
|
|
require_cmd mktemp
|
|
|
|
if [[ "$DO_BUILD" != true ]]; then
|
|
prepare_build_gate
|
|
fi
|
|
|
|
if [[ ! -f "$TARGET_BIN" || ! -f "$REBOOT_BIN" ]]; then
|
|
echo "Binary not found after build: $TARGET_BIN or $REBOOT_BIN"
|
|
exit 1
|
|
fi
|
|
|
|
local version
|
|
if [[ -n "$PACKAGE_VERSION" ]]; then
|
|
version="$PACKAGE_VERSION"
|
|
else
|
|
version="$(read_package_version_from_manifest)"
|
|
fi
|
|
|
|
if [[ -z "$version" ]]; then
|
|
echo "Unable to determine package version"
|
|
exit 1
|
|
fi
|
|
|
|
local staging
|
|
staging="$(mktemp -d)"
|
|
trap 'rm -rf "$staging"' RETURN
|
|
|
|
install -d -m 0755 "$staging/DEBIAN"
|
|
install -d -m 0755 "$staging/usr/sbin"
|
|
install -d -m 0755 "$staging/lib/systemd/system"
|
|
install -d -m 0755 "$staging/etc/awg2-harness"
|
|
install -d -m 0755 "$staging/usr/share/doc/awg2-harness/repo"
|
|
install -d -m 0755 "$staging/usr/share/doc/awg2-harness/repo/docs"
|
|
install -d -m 0755 "$staging/usr/share/doc/awg2-harness/repo/tools"
|
|
install -d -m 0755 "$staging/usr/share/doc/awg2-harness/repo/reboot_module"
|
|
|
|
install -m 0755 "$TARGET_BIN" "$staging/usr/sbin/awg2-harness"
|
|
install -m 0755 "$REBOOT_BIN" "$staging/usr/sbin/awg2-reboot-scheduler"
|
|
install -m 0644 "$ROOT_DIR/awg2-harness.service" "$staging/lib/systemd/system/awg2-harness.service"
|
|
install -m 0644 "$ROOT_DIR/awg2-reboot-scheduler.service" "$staging/lib/systemd/system/awg2-reboot-scheduler.service"
|
|
install -m 0644 "$ROOT_DIR/config.example.toml" "$staging/usr/share/doc/awg2-harness/repo/config.example.toml"
|
|
install -m 0644 "$ROOT_DIR/README.md" "$staging/usr/share/doc/awg2-harness/repo/README.md"
|
|
local doc
|
|
for doc in "$ROOT_DIR"/docs/*.md; do
|
|
install -m 0644 "$doc" "$staging/usr/share/doc/awg2-harness/repo/docs/$(basename "$doc")"
|
|
done
|
|
install -m 0644 "$ROOT_DIR/tools/README.md" "$staging/usr/share/doc/awg2-harness/repo/tools/README.md"
|
|
install -m 0644 "$ROOT_DIR/reboot_module/README.md" "$staging/usr/share/doc/awg2-harness/repo/reboot_module/README.md"
|
|
|
|
cat >"$staging/DEBIAN/control" <<EOF
|
|
Package: awg2-harness
|
|
Version: $version
|
|
Section: net
|
|
Priority: optional
|
|
Architecture: amd64
|
|
Maintainer: AWG2 Harness
|
|
Depends: libc6, iproute2, wireguard-tools, nftables, kmod, util-linux, procps, systemd
|
|
Description: Host-side orchestrator for AmneziaWG traffic shaping and lifecycle recovery
|
|
AWG2 Harness manages AmneziaWG traffic shaping, IFB ingress shaping,
|
|
nftables anti-torrent guard, reboot scheduling, swap setup and daemonized
|
|
drift recovery on dedicated Debian hosts. Includes external reboot scheduler.
|
|
EOF
|
|
|
|
cat >"$staging/DEBIAN/postinst" <<'EOF'
|
|
#!/bin/sh
|
|
set -e
|
|
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
systemctl daemon-reload || true
|
|
fi
|
|
|
|
echo "AWG2 Harness installed."
|
|
echo "Runtime config is not created automatically by design."
|
|
echo "Create /etc/awg2-harness/config.toml manually before enabling services:"
|
|
echo " install -d -m 0755 /etc/awg2-harness"
|
|
echo " cp /usr/share/doc/awg2-harness/repo/config.example.toml /etc/awg2-harness/config.toml"
|
|
echo " nano /etc/awg2-harness/config.toml"
|
|
echo "Then validate and enable services:"
|
|
echo " awg2-harness --config /etc/awg2-harness/config.toml validate-config"
|
|
echo " systemctl enable --now awg2-harness.service awg2-reboot-scheduler.service"
|
|
|
|
exit 0
|
|
EOF
|
|
|
|
cat >"$staging/DEBIAN/preinst" <<'EOF'
|
|
#!/bin/sh
|
|
set -e
|
|
|
|
if [ ! -r /etc/os-release ]; then
|
|
echo "awg2-harness supports only Debian 13 amd64" >&2
|
|
exit 1
|
|
fi
|
|
|
|
. /etc/os-release
|
|
|
|
if [ "${ID:-}" != "debian" ] || [ "${VERSION_ID:-}" != "13" ]; then
|
|
echo "awg2-harness supports only Debian 13 amd64; got ${PRETTY_NAME:-unknown}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if command -v dpkg >/dev/null 2>&1; then
|
|
arch="$(dpkg --print-architecture)"
|
|
if [ "$arch" != "amd64" ]; then
|
|
echo "awg2-harness supports only amd64; got $arch" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
exit 0
|
|
EOF
|
|
|
|
cat >"$staging/DEBIAN/postrm" <<'EOF'
|
|
#!/bin/sh
|
|
set -e
|
|
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
systemctl daemon-reload || true
|
|
fi
|
|
|
|
exit 0
|
|
EOF
|
|
|
|
chmod 0755 "$staging/DEBIAN/preinst" "$staging/DEBIAN/postinst" "$staging/DEBIAN/postrm"
|
|
|
|
mkdir -p "$DIST_DIR"
|
|
local pkg_name
|
|
pkg_name="awg2-harness_${version}_amd64.deb"
|
|
local pkg_path
|
|
pkg_path="$DIST_DIR/$pkg_name"
|
|
|
|
dpkg-deb --build "$staging" "$pkg_path"
|
|
dpkg-deb --info "$pkg_path"
|
|
dpkg-deb --contents "$pkg_path" | sort
|
|
|
|
(
|
|
cd "$DIST_DIR"
|
|
sha256sum "$pkg_name" > SHA256SUMS
|
|
)
|
|
|
|
echo "Package created: $pkg_path"
|
|
echo "Checksums: $DIST_DIR/SHA256SUMS"
|
|
}
|
|
|
|
enable_service() {
|
|
require_cmd systemctl
|
|
setup_root_runner
|
|
"${RUN_AS_ROOT[@]}" systemctl enable --now awg2-harness.service
|
|
"${RUN_AS_ROOT[@]}" systemctl enable --now awg2-reboot-scheduler.service
|
|
}
|
|
|
|
ensure_supported_host
|
|
|
|
if [[ "$PREPARE_BUILD_ENV" == true ]]; then
|
|
apt_install_missing "${BUILD_PACKAGES[@]}"
|
|
fi
|
|
|
|
if [[ "$PREPARE_RUNTIME_ENV" == true ]]; then
|
|
apt_install_missing "${RUNTIME_PACKAGES[@]}"
|
|
fi
|
|
|
|
if [[ "$BOOTSTRAP_RUST" == true ]]; then
|
|
bootstrap_rust
|
|
fi
|
|
|
|
if [[ "$DO_BUILD" == true ]]; then
|
|
prepare_build_gate
|
|
fi
|
|
|
|
if [[ "$PACKAGE" == true ]]; then
|
|
package_deb
|
|
fi
|
|
|
|
if [[ "$INSTALL" == true ]]; then
|
|
install_artifacts
|
|
fi
|
|
|
|
if [[ "$ENABLE" == true ]]; then
|
|
enable_service
|
|
fi
|
|
|
|
if [[ "$DO_BUILD" == true ]]; then
|
|
echo "Build completed: $TARGET_BIN"
|
|
fi
|