Merge pull request #245 from anywherelan/config-defaults #828
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Test | |
| on: [ push, pull_request ] | |
| jobs: | |
| test: | |
| # run job on all pushes OR external PR, not both | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ ubuntu-latest, windows-latest, macos-latest ] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Install Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: 1.26.x | |
| cache: true | |
| - name: Create stub static/ | |
| run: mkdir static && touch static/index.html | |
| - name: Check deps | |
| shell: bash | |
| run: ./build.sh deps | |
| - name: gofmt && go mod tidy | |
| if: matrix.os == 'ubuntu-latest' | |
| run: | | |
| go mod tidy -compat=1.26 | |
| cd cmd/awl-tray && go mod tidy -compat=1.26 && cd ../.. | |
| test -z "$(gofmt -d .)" || (gofmt -d . && false) | |
| test -z "$(git status --porcelain)" || (git status; git diff && false) | |
| - name: Test | |
| run: go test -count=1 -v ./... | |
| - name: Test with -race | |
| run: go test -race -count=1 -v ./... | |
| - name: VPN gateway host-network integration test (Linux, root) | |
| # Hidden behind the `vpn_hostnet` build tag and excluded from `go test ./...`. | |
| # Exercises the real netlink/iptables plumbing (SetupNAT/SetupGatewayRoutes | |
| # + teardown/stale-recovery) against the runner's own network, so it needs | |
| # root and only runs on Linux. Compile as the normal user, then run the | |
| # binary under sudo so root never pollutes the Go build cache. | |
| # TODO: if a hard failure ever leaves the runner's egress black-holed | |
| # (default route via a dead awl0) and breaks log upload, add a separate | |
| # `if: always()` cleanup step | |
| if: matrix.os == 'ubuntu-latest' | |
| run: | | |
| go test -c -tags vpn_hostnet -o gw-hostnet.test ./vpn/routes/ | |
| sudo ./gw-hostnet.test -test.run '^TestGatewayHostNet' -test.v | |
| - name: Build cmd/awl | |
| run: go build github.com/anywherelan/awl/cmd/awl | |
| - name: Upload cmd/awl build | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: awl-build-${{ runner.os }} | |
| path: | | |
| awl | |
| awl.exe | |
| if-no-files-found: error | |
| end-to-end-test: | |
| # run only on pushes because we use repository secrets which are unavailable to forks | |
| if: ${{ github.event_name == 'push' }} | |
| needs: [ test ] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ ubuntu-latest, windows-latest, macos-latest ] | |
| runs-on: ${{ matrix.os }} | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - name: Retrieve saved cmd/awl build | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: awl-build-${{ runner.os }} | |
| - name: Download librespeed-cli | |
| env: | |
| CONFIG_AWL_LINUX: ${{ secrets.CONFIG_AWL_LINUX }} | |
| CONFIG_AWL_MACOS: ${{ secrets.CONFIG_AWL_MACOS }} | |
| CONFIG_AWL_WINDOWS: ${{ secrets.CONFIG_AWL_WINDOWS }} | |
| CONFIG_LIBRESPEED: ${{ secrets.CONFIG_LIBRESPEED }} | |
| run: | | |
| if [ "$RUNNER_OS" == "Linux" ]; then | |
| curl -sSL https://github.com/librespeed/speedtest-cli/releases/download/v1.0.10/librespeed-cli_1.0.10_linux_amd64.tar.gz | tar -xzf - | |
| echo $CONFIG_AWL_LINUX > config_awl.json | |
| elif [ "$RUNNER_OS" == "macOS" ]; then | |
| curl -sSL https://github.com/librespeed/speedtest-cli/releases/download/v1.0.10/librespeed-cli_1.0.10_darwin_arm64.tar.gz | tar -xzf - | |
| echo $CONFIG_AWL_MACOS > config_awl.json | |
| elif [ "$RUNNER_OS" == "Windows" ]; then | |
| curl -sSL https://github.com/librespeed/speedtest-cli/releases/download/v1.0.10/librespeed-cli_1.0.10_windows_amd64.zip > download.zip | |
| unzip download.zip | |
| echo $CONFIG_AWL_WINDOWS > config_awl.json | |
| else | |
| echo "$RUNNER_OS not supported" | |
| exit 1 | |
| fi | |
| echo $CONFIG_LIBRESPEED > config_librespeed.json | |
| - name: Run librespeed-cli, awl, librespeed-cli through awl | |
| if: matrix.os == 'ubuntu-latest' | |
| run: | | |
| chmod +x awl | |
| sudo ./awl & | |
| ./librespeed-cli --local-json config_librespeed.json --server 1 --json --share --telemetry-level disabled | python3 -m json.tool | |
| sleep 10 | |
| awl_pid=`jobs -l | grep './awl' | awk '{print $2}'` | |
| ./awl cli peers status | |
| ./librespeed-cli --local-json config_librespeed.json --server 2 --json --share --telemetry-level disabled | python3 -m json.tool | |
| ping 10.66.0.2 -w 20 -c 10 | |
| # TODO: remove this temporal hack for linux | |
| ping awl-tester.awl -w 20 -c 10 || true | |
| # ---- VPN gateway server (exit-node) mode: runtime enable/disable round-trips OS state ---- | |
| # awl runs as root here, so enabling the server installs real NAT | |
| # (iptables MASQUERADE + AWL-FORWARD chain) in this netns. We assert the | |
| # commands succeed AND that they actually mutate then fully restore the | |
| # netfilter state. `iptables -S` (no packet/byte counters, unlike | |
| # iptables-save) makes the before/after comparison stable. | |
| nat_state() { sudo iptables -S; sudo iptables -t nat -S; } | |
| NAT_BEFORE=$(nat_state) | |
| ./awl cli gateway server enable | |
| test "$(nat_state)" != "$NAT_BEFORE" # enable actually changed netfilter | |
| sudo iptables -S | grep -q AWL-FORWARD # exit-node chain installed | |
| ./awl cli gateway server disable | |
| test "$(nat_state)" = "$NAT_BEFORE" # disable restored netfilter exactly | |
| # ---- VPN gateway full-tunnel e2e: client = this runner, exit node = awl-tester ---- | |
| # Requires the awl-tester peer to have VPN gateway server enabled AND to | |
| # permit this CI peer as an exit node (WeAllowUsingAsExitNode); both are | |
| # advertised to us via the status protocol. Wait until it shows up as a | |
| # connected, available gateway. | |
| for i in $(seq 1 15); do | |
| if ./awl cli gateway list | grep -E 'awl-tester.*\[connected\]'; then break; fi | |
| sleep 2 | |
| done | |
| ./awl cli gateway list | grep -E 'awl-tester.*\[connected\]' # fail if never became available | |
| # Probe the egress IP via https://ifconfig.me | |
| curl4() { curl -4 -s --max-time 20 --retry 3 --retry-delay 2 https://ifconfig.me/; } | |
| IP_DIRECT=$(curl4); echo "egress IP (direct): $IP_DIRECT" | |
| ./awl cli gateway client use --name awl-tester | |
| ./awl cli gateway status | |
| RULE_ON=$(ip rule show); ROUTE_ON=$(ip route show) | |
| # Tolerate a failed curl here so we always reach `client stop` below | |
| # (a broken tunnel must not leave the runner black-holed); the | |
| # `test -n "$IP_GW"` assertion after teardown turns it into a clean failure. | |
| IP_GW=$(curl4 || true); echo "egress IP (via gateway): $IP_GW" | |
| ./awl cli gateway client stop | |
| RULE_OFF=$(ip rule show); ROUTE_OFF=$(ip route show) | |
| IP_REVERTED=$(curl4); echo "egress IP (reverted): $IP_REVERTED" | |
| # Assertions run with the gateway already disabled, so a failure here | |
| # can't leave the runner's egress black-holed. | |
| echo "$RULE_ON" | grep -q fwmark # client policy route installed | |
| echo "$ROUTE_ON" | grep -q 'default dev awl0' # full-tunnel default via TUN | |
| test -n "$IP_GW" # traffic really flowed through the exit node | |
| test "$IP_GW" != "$IP_DIRECT" # egress changed => full-tunnel works | |
| test "$IP_REVERTED" = "$IP_DIRECT" # runtime disable reverted egress | |
| ! echo "$RULE_OFF" | grep -q fwmark # policy route removed on disable | |
| ! echo "$ROUTE_OFF" | grep -q 'default dev awl0' # TUN default removed on disable | |
| # Re-enable so the SIGINT shutdown path (teardownGatewayAtShutdown) is exercised. | |
| ./awl cli gateway client use --name awl-tester | |
| sleep 1 | |
| sudo kill -SIGINT $awl_pid | |
| sleep 2 | |
| # awl is gone: its TUN and the default route via it vanished with the | |
| # process, but the fwmark ip rule is interface-independent — its absence | |
| # proves the shutdown teardown ran. (NAT/ip_forward are server-side only.) | |
| ! ip rule show | grep -q fwmark | |
| - name: Run librespeed-cli, awl, librespeed-cli through awl | |
| if: matrix.os == 'macos-latest' | |
| run: | | |
| chmod +x awl | |
| sudo ./awl & | |
| ./librespeed-cli --local-json config_librespeed.json --server 1 --json --share --telemetry-level disabled | python3 -m json.tool | |
| sleep 10 | |
| awl_pid=`jobs -l | grep './awl' | awk '{print $2}'` | |
| ./awl cli peers status | |
| ./librespeed-cli --local-json config_librespeed.json --server 2 --json --share --telemetry-level disabled | python3 -m json.tool | |
| ping 10.66.0.2 -c 10 | |
| ping awl-tester.awl -c 10 | |
| sleep 1 | |
| sudo kill -SIGINT $awl_pid | |
| sleep 1 | |
| - name: Run librespeed-cli, awl, librespeed-cli through awl | |
| if: matrix.os == 'windows-latest' | |
| run: | | |
| chmod +x awl.exe | |
| ./awl.exe & | |
| ./librespeed-cli.exe --local-json config_librespeed.json --server 1 --json --share --telemetry-level disabled | python3 -m json.tool | |
| sleep 10 | |
| awl_pid=`jobs -l | grep './awl' | awk '{print $2}'` | |
| ./awl.exe cli peers status | |
| ./librespeed-cli.exe --local-json config_librespeed.json --server 2 --json --share --telemetry-level disabled | python3 -m json.tool | |
| ping -w 20000 -n 10 10.66.0.2 | |
| ping -w 20000 -n 10 -a awl-tester.awl | |
| sleep 1 | |
| kill -SIGINT $awl_pid | |
| sleep 1 |