diff --git a/action.yml b/action.yml index 59dee62..a86f48b 100644 --- a/action.yml +++ b/action.yml @@ -223,6 +223,11 @@ runs: source "$GITHUB_ACTION_PATH/scripts/linux-helpers.sh" echo '${{ inputs.service-key }}' | $SUDO twingate setup --headless - + + # Fail fast on runners that fundamentally can't run a VPN client + # (e.g. ubuntu-slim), instead of retrying silently until timeout. + check_network_capabilities || exit 1 + MAX_RETRIES=5 WAIT_TIME=5 n=0 @@ -247,7 +252,14 @@ runs: else twingate stop if [ "${{ inputs.debug }}" = "true" ]; then - journalctl -u twingate --no-pager + # journalctl is empty without systemd (container runners); the + # forked daemon logs to /var/log/twingated.log instead. + if [ -d /run/systemd/system ]; then + journalctl -u twingate --no-pager + else + log INFO "systemd not present; dumping /var/log/twingated.log" + $SUDO cat /var/log/twingated.log 2>/dev/null || log INFO "no daemon log found" + fi fi fi diff --git a/scripts/linux-helpers.sh b/scripts/linux-helpers.sh index 09ec63c..e688bc8 100644 --- a/scripts/linux-helpers.sh +++ b/scripts/linux-helpers.sh @@ -31,6 +31,40 @@ get_os_version() { grep VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"' } +# Verify the runner can actually run a VPN client before we try to start it. +# Twingate needs the TUN device node AND CAP_NET_ADMIN (for ioctl(TUNSETIFF) and +# route setup). Unprivileged container runners (ubuntu-slim, some container jobs) +# lack at least one of these — often the TUN node exists but CAP_NET_ADMIN is +# dropped — so the daemon starts but never comes online. Fail fast with guidance +# instead of retrying silently for ~75s. Returns non-zero if unusable. +check_network_capabilities() { + local missing="" + + [ -e /dev/net/tun ] || missing="$missing /dev/net/tun" + + # CAP_NET_ADMIN is capability bit 12. CapBnd is the bounding set: if the cap is + # absent there, not even root (via sudo) can acquire it. If CapBnd is + # unreadable we skip this check rather than risk a false failure. + local cap_bnd + # `|| true` so an unreadable /proc/self/status yields "" instead of tripping + # `set -e` in the calling step (this runs before the loop's `set +xe`). + cap_bnd=$(awk '/^CapBnd:/ {print $2}' /proc/self/status 2>/dev/null || true) + if [ -n "$cap_bnd" ] && [ $(( (0x$cap_bnd >> 12) & 1 )) -ne 1 ]; then + missing="$missing CAP_NET_ADMIN" + fi + + if [ -n "$missing" ]; then + log ERROR "Twingate needs a TUN device and CAP_NET_ADMIN, but this runner is missing:$missing" + log ERROR "This is expected on minimal/unprivileged container runners such as ubuntu-slim." + log ERROR "For container jobs, grant them via your job's container config:" + log ERROR " container:" + log ERROR " options: --cap-add NET_ADMIN --device /dev/net/tun" + log ERROR "See https://www.twingate.com/docs/linux-headless/#working-with-the-linux-client-in-headless-mode" + return 1 + fi + return 0 +} + validate_cache_linux() { local deb_file deb_file=$(ls ~/.twingate-cache/twingate*.deb 2>/dev/null | head -1)