From 93185723cae00eb2b5f2f3f4694e03578c6ea0a7 Mon Sep 17 00:00:00 2001 From: Alex Chupryna Date: Mon, 13 Jul 2026 21:40:19 +0200 Subject: [PATCH 1/2] fix: fail fast on container runners lacking NET_ADMIN (OSS-123) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Twingate must create a TUN interface, which requires CAP_NET_ADMIN. Unprivileged container runners (ubuntu-slim, container jobs without extra options) drop that capability, so the daemon starts but the tunnel never comes online — surfacing only as an endless "status: not-running" retry loop with no diagnostics. - Add check_network_capabilities() to linux-helpers.sh: verifies /dev/net/tun and CAP_NET_ADMIN (bounding set, bit 12) and prints actionable guidance (container.options: --cap-add NET_ADMIN --device /dev/net/tun) when missing. - Call it right after setup, before the retry loop, to fail fast instead of retrying silently for ~75s. - In debug mode, dump /var/log/twingated.log when systemd is absent (journalctl is empty on container runners). Co-Authored-By: Claude Opus 4.8 (1M context) --- action.yml | 14 +++++++++++++- scripts/linux-helpers.sh | 31 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) 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..63feaee 100644 --- a/scripts/linux-helpers.sh +++ b/scripts/linux-helpers.sh @@ -31,6 +31,37 @@ 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) +# have neither, 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 + cap_bnd=$(awk '/^CapBnd:/ {print $2}' /proc/self/status 2>/dev/null) + 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) From 7b3ebbd6aea3a6e2d59fcd1b7200186e4d1e069c Mon Sep 17 00:00:00 2001 From: Alex Chupryna Date: Tue, 14 Jul 2026 09:34:31 +0200 Subject: [PATCH 2/2] fix: address Copilot review on preflight check - Guard the CapBnd awk with `|| true` so an unreadable /proc/self/status yields "" instead of tripping `set -e` in the calling step. - Clarify the comment: TUN node often exists while CAP_NET_ADMIN is dropped. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/linux-helpers.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/linux-helpers.sh b/scripts/linux-helpers.sh index 63feaee..e688bc8 100644 --- a/scripts/linux-helpers.sh +++ b/scripts/linux-helpers.sh @@ -34,8 +34,9 @@ get_os_version() { # 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) -# have neither, so the daemon starts but never comes online. Fail fast with -# guidance instead of retrying silently for ~75s. Returns non-zero if unusable. +# 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="" @@ -45,7 +46,9 @@ check_network_capabilities() { # 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 - cap_bnd=$(awk '/^CapBnd:/ {print $2}' /proc/self/status 2>/dev/null) + # `|| 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