Skip to content

Commit 68cd8d6

Browse files
committed
feat(release): add Arch packaging/CI smoke tests and harden real-kernel firewall validation
1 parent bb0ac8e commit 68cd8d6

13 files changed

Lines changed: 883 additions & 143 deletions

File tree

.github/workflows/ci.yml

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- '**'
7+
tags:
8+
- 'v*'
9+
pull_request:
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
lint-and-tests:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
21+
- name: Bash syntax checks
22+
run: |
23+
bash -n \
24+
cmd/boxctl \
25+
lib/*.sh \
26+
lib/firewall/*.sh \
27+
lib/supervisor/*.sh \
28+
tests/integration/test_phase2.sh \
29+
tests/integration/test_real_kernel.sh \
30+
tests/integration/test_arch_package_smoke.sh \
31+
tests/fixtures/mockbin/ip \
32+
tests/fixtures/mockbin/iptables \
33+
packaging/scripts/systemd-lifecycle.sh \
34+
packaging/arch/box4linux.install
35+
36+
- name: ShellCheck (if available)
37+
run: |
38+
if command -v shellcheck >/dev/null 2>&1; then
39+
shellcheck \
40+
cmd/boxctl \
41+
lib/*.sh \
42+
lib/firewall/*.sh \
43+
lib/supervisor/*.sh \
44+
tests/integration/test_phase2.sh \
45+
tests/integration/test_real_kernel.sh \
46+
tests/integration/test_arch_package_smoke.sh \
47+
packaging/scripts/systemd-lifecycle.sh
48+
shellcheck -s sh packaging/arch/box4linux.install
49+
else
50+
echo "shellcheck not available; skipping"
51+
fi
52+
53+
- name: Mock integration tests
54+
run: ./tests/integration/test_phase2.sh
55+
56+
- name: Real-kernel integration tests (skip-capable)
57+
run: sudo ./tests/integration/test_real_kernel.sh
58+
59+
build-arch-package:
60+
runs-on: ubuntu-latest
61+
needs:
62+
- lint-and-tests
63+
steps:
64+
- name: Checkout
65+
uses: actions/checkout@v4
66+
67+
- name: Build Arch package in container
68+
run: |
69+
docker run --rm \
70+
-v "$PWD":/work \
71+
-w /work \
72+
archlinux:base-devel \
73+
bash -lc '
74+
set -euo pipefail
75+
pacman -Syu --noconfirm --needed base-devel bash coreutils tar zstd
76+
chmod -R a+rwX /work
77+
su nobody -s /bin/bash -c "cd /work/packaging/arch && makepkg --nodeps --noconfirm -f"
78+
'
79+
80+
- name: Capture package path
81+
id: pkg
82+
run: |
83+
pkg_path="$(ls -1 packaging/arch/*.pkg.tar.* | head -n 1)"
84+
echo "package_path=${pkg_path}" >> "${GITHUB_OUTPUT}"
85+
echo "Built package: ${pkg_path}"
86+
87+
- name: Upload Arch package artifact
88+
uses: actions/upload-artifact@v4
89+
with:
90+
name: box4linux-arch-pkg
91+
path: ${{ steps.pkg.outputs.package_path }}
92+
93+
smoke-package:
94+
runs-on: ubuntu-latest
95+
needs:
96+
- build-arch-package
97+
steps:
98+
- name: Checkout
99+
uses: actions/checkout@v4
100+
101+
- name: Download Arch package artifact
102+
uses: actions/download-artifact@v4
103+
with:
104+
name: box4linux-arch-pkg
105+
path: ./dist
106+
107+
- name: Package smoke test
108+
run: |
109+
pkg_path="$(ls -1 ./dist/*.pkg.tar.* | head -n 1)"
110+
./tests/integration/test_arch_package_smoke.sh "${pkg_path}"
111+
112+
release:
113+
runs-on: ubuntu-latest
114+
if: startsWith(github.ref, 'refs/tags/v')
115+
needs:
116+
- smoke-package
117+
permissions:
118+
contents: write
119+
steps:
120+
- name: Download Arch package artifact
121+
uses: actions/download-artifact@v4
122+
with:
123+
name: box4linux-arch-pkg
124+
path: ./dist
125+
126+
- name: Publish release assets
127+
uses: softprops/action-gh-release@v2
128+
with:
129+
files: ./dist/*.pkg.tar.*
130+
generate_release_notes: true

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@ box-reference/
1212

1313
# Local Linux dev runtime state
1414
.box-dev/
15+
16+
# Arch packaging build artifacts
17+
packaging/arch/*.pkg.tar.*
18+
packaging/arch/pkg/
19+
packaging/arch/src/

README.md

Lines changed: 89 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# box4linux workspace
22

3-
Linux-native control plane (phase 2) lives in:
3+
Linux-native control plane lives in:
44
- `cmd/boxctl`
55
- `lib/common.sh`
66
- `lib/config.sh`
77
- `lib/supervisor/`
88
- `lib/firewall/`
99
- `systemd/`
1010
- `tests/integration/`
11+
- `packaging/arch/`
1112

1213
Android reference artifacts are kept untouched in `box-reference/`.
1314

@@ -26,60 +27,106 @@ Android reference artifacts are kept untouched in `box-reference/`.
2627
- `./cmd/boxctl firewall dry-run`
2728
4. Run integration checks:
2829
- `./tests/integration/test_phase2.sh`
29-
- `sudo ./tests/integration/test_real_kernel.sh` (skips automatically when root/CAP_SYS_ADMIN/CAP_NET_ADMIN or backend tooling is unavailable)
30+
- `sudo ./tests/integration/test_real_kernel.sh`
3031

31-
## Systemd units
32+
## Arch Package Build/Install
3233

33-
- `systemd/box.service`
34-
- `systemd/box-firewall.service`
34+
Build package from repo root:
35+
- `cd packaging/arch && makepkg --noconfirm -f`
3536

36-
Copy/symlink these to your systemd unit path and ensure `boxctl` is installed as `/usr/bin/boxctl`.
37+
Install package:
38+
- `sudo pacman -U ./box4linux-*.pkg.tar.zst`
3739

38-
## Phase 3 notes
40+
Installed layout:
41+
- `/usr/bin/boxctl`
42+
- `/usr/lib/box4linux/cmd/boxctl`
43+
- `/usr/lib/box4linux/lib/...`
44+
- `/etc/box/box.toml`
45+
- `/usr/lib/systemd/system/box.service`
46+
- `/usr/lib/systemd/system/box-firewall.service`
47+
- `/usr/share/doc/box4linux/`
48+
49+
Config upgrade behavior:
50+
- Package marks `/etc/box/box.toml` as backup config.
51+
- Local edits are preserved across upgrades.
52+
- New template versions land as `.pacnew` when needed.
53+
54+
## Service Lifecycle (Packaged Install)
55+
56+
Use helper script from package docs:
57+
- `sudo /usr/share/doc/box4linux/systemd-lifecycle.sh enable`
58+
- `sudo /usr/share/doc/box4linux/systemd-lifecycle.sh status`
59+
- `sudo /usr/share/doc/box4linux/systemd-lifecycle.sh disable`
60+
61+
Manual equivalent:
62+
- `sudo systemctl daemon-reload`
63+
- `sudo systemctl enable --now box.service box-firewall.service`
64+
- `sudo systemctl disable --now box-firewall.service box.service`
65+
66+
## Packaged Operational Quickstart
67+
68+
1. Verify command and units:
69+
- `boxctl service status --json`
70+
- `boxctl firewall status --json`
71+
2. Preview firewall operations:
72+
- `boxctl firewall dry-run`
73+
3. Start runtime:
74+
- `sudo systemctl start box.service`
75+
4. Renew firewall policy safely:
76+
- `sudo systemctl reload box-firewall.service`
77+
78+
## CI/Release Flow
79+
80+
Workflow file: `.github/workflows/ci.yml`
81+
82+
On push/PR:
83+
- `bash -n` checks on shell scripts
84+
- `shellcheck` when available
85+
- mock integration: `./tests/integration/test_phase2.sh`
86+
- privileged integration: `sudo ./tests/integration/test_real_kernel.sh` (suite prints `SKIP` when capabilities/tooling are unavailable)
87+
- Arch package build in Arch container
88+
- package smoke test: `./tests/integration/test_arch_package_smoke.sh <pkg>`
89+
90+
On tags (`v*`):
91+
- built package artifact is published to GitHub Releases
92+
93+
## Phase 3 Notes
3994

4095
- Supported cores: `mihomo`, `sing-box`
41-
- Core overlay mutators render runtime configs under `/run/box/rendered` (or dev fallback).
96+
- Runtime overlays rendered under `/run/box/rendered` (or dev fallback)
4297
- Firewall backends:
43-
- `iptables`: parity path with staged apply/rollback
44-
- `nftables`: MVP parity path with staged apply/rollback
45-
- Supported modes on both backends: `tun`, `tproxy`, `redirect`, `mixed`, `enhance`.
46-
- DNS strategy handling: `tproxy`, `redirect`, `disable`.
47-
- Tailscale coexistence defaults to `dns_coexist_mode=preserve_tailnet`.
48-
- Coexistence mode semantics:
49-
- `preserve_tailnet`: apply tailscale bypass (`tailscale0`, `100.64.0.0/10`) and MagicDNS resolver exclusion (`100.100.100.100:53`).
50-
- `strict_box`: do not add tailscale bypass/MagicDNS exclusion rules; still never delete non-BOX routes/rules.
51-
- Tailscale safeguards include:
52-
- bypass `tailscale0`
53-
- bypass `100.64.0.0/10` and preserve `fd7a:115c:a1e0::/48` by not touching ip6tables in this backend
54-
- bypass `100.100.100.100:53` (MagicDNS resolver)
55-
- preserve table `52` / fwmark `0x80000/0xff0000` ownership
56-
- Route convergence: renew/reapply prunes stale BOX fwmark rules for the same fwmark+table (any old pref) and installs exactly one rule with current `route_pref`.
57-
- `enable|renew|disable` paths are idempotent and lock-protected.
58-
- `boxctl firewall dry-run` prints intended backend operations without applying.
59-
- `BOX_TRACE_COMMANDS=1` logs external command execution with `component`, `action`, and command string.
60-
- `boxctl firewall status --json` is side-effect free and includes stable diagnostics:
61-
- `backend` / `backend_selected`
62-
- `backend_available`
63-
- `mode`
64-
- `dns_hijack_mode`
65-
- `dns_coexist_mode`
66-
- `dns_coexist_mode_active`
67-
- `cap_ipv4`, `cap_ipv6`, `cap_tproxy`
68-
- `dry_run_supported`
69-
- `last_error`
70-
71-
## Backend capability notes
98+
- `iptables` (mature path)
99+
- `nftables` (MVP parity)
100+
- Supported modes on both backends: `tun`, `tproxy`, `redirect`, `mixed`, `enhance`
101+
- DNS strategies: `tproxy`, `redirect`, `disable`
102+
- Coexistence modes:
103+
- `preserve_tailnet` (default): apply tailscale and MagicDNS bypasses
104+
- `strict_box`: skip tailscale/MagicDNS bypass insertion
105+
- Route convergence: renew/reapply prunes stale BOX fwmark rules and enforces one current `route_pref` rule
106+
- Idempotent + lock-protected: `enable|renew|disable`
107+
- `BOX_TRACE_COMMANDS=1` logs external command executions with component/action context
108+
- `boxctl firewall status --json` exposes stable diagnostics (backend, capabilities, coexist fields, errors)
109+
110+
## Backend Capability Notes
72111

73112
- `iptables`:
74113
- `cap_ipv4=true`
75-
- `cap_ipv6=false` (no ip6tables graph yet)
114+
- `cap_ipv6=false` (full ip6tables graph pending)
76115
- `nftables`:
77116
- `cap_ipv4=true`
78-
- `cap_ipv6=false` (full IPv6 interception/hijack graph still pending)
117+
- `cap_ipv6=false` (full IPv6 interception/hijack graph pending)
118+
119+
## Rollback/Uninstall
120+
121+
Safe uninstall (keeps config backups/data unless manually removed):
122+
- `sudo pacman -R box4linux`
123+
124+
Optional manual purge of local state:
125+
- `sudo rm -rf /etc/box /var/lib/box /run/box /var/log/box`
79126

80127
## Remaining TODO
81128

82-
- Full UID/GID/interface/MAC policy graph in firewall (currently placeholder stage).
83-
- Full kernel-capability probing for nft/iptables modules across all distro variants.
129+
- Full UID/GID/interface/MAC policy graph in firewall.
130+
- Full IPv6 interception/hijack parity.
84131
- API-based reload hooks for `mihomo` and `sing-box`.
85-
- Explicit IPv6 tailnet bypass/interception parity for both backends.
132+
- Broader kernel-capability probing across distro variants.

cmd/boxctl

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,32 @@
55
set -euo pipefail
66

77
CMD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8-
LIB_DIR="$(cd "${CMD_DIR}/../lib" && pwd)"
8+
LIB_DIR_CANDIDATES=(
9+
"${BOX_LIB_DIR:-}"
10+
"${CMD_DIR}/../lib"
11+
"/usr/lib/box4linux/lib"
12+
)
13+
LIB_DIR=""
14+
15+
find_lib_dir() {
16+
local candidate
17+
for candidate in "${LIB_DIR_CANDIDATES[@]}"; do
18+
[[ -n "${candidate}" ]] || continue
19+
if [[ -f "${candidate}/common.sh" ]]; then
20+
LIB_DIR="${candidate}"
21+
export BOX_LIB_DIR="${LIB_DIR}"
22+
return 0
23+
fi
24+
done
25+
return 1
26+
}
27+
28+
if ! find_lib_dir; then
29+
printf 'failed to locate box4linux lib directory; checked:\n' >&2
30+
local_candidate_list="$(printf ' %s\n' "${LIB_DIR_CANDIDATES[@]}")"
31+
printf '%s' "${local_candidate_list}" >&2
32+
exit 1
33+
fi
934

1035
source "${LIB_DIR}/common.sh"
1136
source "${LIB_DIR}/config.sh"

0 commit comments

Comments
 (0)