Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,39 @@ jobs:

- run: echo "SUCCESS!!! 🤩 This job's status is ${{ job.status }}."

test-linux-docker:
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
options: --privileged
timeout-minutes: 10
steps:
- uses: actions/checkout@v6

- uses: ./
with:
service-key: ${{ secrets.SERVICE_KEY }}

- name: Verify Twingate status
run: twingate status

- name: Access a secure resource
env:
TEST_URL: http://business.prod.beamreachinc.int/
run: |
curl -v $TEST_URL

- name: Print client logs
if: always()
run: |
if command -v journalctl >/dev/null 2>&1; then
journalctl -u twingate
elif [ -f /var/log/twingated.log ]; then
cat /var/log/twingated.log
Comment on lines +104 to +106
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In container environments journalctl may exist but still exit non-zero (e.g., no systemd/journald). Because run steps use bash -e, a non-zero journalctl here will fail the job even though this is a best-effort log dump. Consider appending || true (and --no-pager) to the journalctl/cat commands to ensure the step never fails.

Suggested change
journalctl -u twingate
elif [ -f /var/log/twingated.log ]; then
cat /var/log/twingated.log
journalctl -u twingate --no-pager || true
elif [ -f /var/log/twingated.log ]; then
cat /var/log/twingated.log || true

Copilot uses AI. Check for mistakes.
else
echo "No Twingate logs found"
fi

test-windows:
strategy:
max-parallel: 4
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/integration-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,39 @@ jobs:
if: always()
run: journalctl -u twingate

test-linux-docker:
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
options: --privileged
timeout-minutes: 10
steps:
- name: Test published action
uses: twingate/github-action@main
with:
service-key: ${{ secrets.SERVICE_KEY }}
debug: ${{ inputs.debug || 'false' }}

- name: Verify Twingate status
run: twingate status

- name: Access a secure resource
env:
TEST_URL: http://business.prod.beamreachinc.int/
run: |
curl -v $TEST_URL
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Docker integration test uses curl -v without --fail, so HTTP 4xx/5xx responses won't fail the step. Add --fail (as used in the existing test-linux job) to ensure the job actually validates access to the protected resource.

Suggested change
curl -v $TEST_URL
curl -v --fail $TEST_URL

Copilot uses AI. Check for mistakes.

- name: Print client logs
if: always()
run: |
if command -v journalctl >/dev/null 2>&1; then
journalctl -u twingate
elif [ -f /var/log/twingated.log ]; then
cat /var/log/twingated.log
Comment on lines +80 to +82
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to CI, journalctl may be present in the container but still fail if systemd/journald isn't running. With bash -e, that would fail the whole job even though this is an if: always() diagnostics step. Make the journalctl/cat calls non-fatal (e.g., ... || true) so log collection never causes a failure.

Suggested change
journalctl -u twingate
elif [ -f /var/log/twingated.log ]; then
cat /var/log/twingated.log
journalctl -u twingate || true
elif [ -f /var/log/twingated.log ]; then
cat /var/log/twingated.log || true

Copilot uses AI. Check for mistakes.
else
echo "No Twingate logs found"
fi

test-windows:
strategy:
max-parallel: 4
Expand Down
62 changes: 41 additions & 21 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,35 @@ runs:
if: runner.os != 'Linux' && runner.os != 'Windows'
shell: bash
run: |
source ${{ github.action_path }}/scripts/linux-helpers.sh
source "$GITHUB_ACTION_PATH/scripts/linux-helpers.sh"
log ERROR "Unsupported Runner OS: ${{ runner.os }}"
exit 1

- name: Install prerequisites (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
# Check if we're in a minimal container environment missing required tools
MISSING_DEPS=""
command -v curl >/dev/null 2>&1 || MISSING_DEPS="$MISSING_DEPS curl"
command -v gpg >/dev/null 2>&1 || MISSING_DEPS="$MISSING_DEPS gnupg"

if [ -n "$MISSING_DEPS" ]; then
# Detect if we need sudo for package installation
source "$GITHUB_ACTION_PATH/scripts/sudo-detect.sh"

echo "Installing missing dependencies: $MISSING_DEPS"
$SUDO apt-get update
$SUDO apt-get install -y $MISSING_DEPS
fi
Comment on lines +42 to +49
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If dependencies are missing and the environment is non-root without sudo installed, this step will attempt apt-get without privileges and fail, but with a confusing error. Add an explicit check for this case (e.g., detect id -u != 0 and no sudo) and fail fast with a clear message (or document that the action requires root/sudo in containers).

Copilot uses AI. Check for mistakes.

- name: Get latest Twingate version (Linux)
if: runner.os == 'Linux' && inputs.cache == 'true'
id: twingate-version-linux
shell: bash
run: |
export DEBUG_MODE='${{ inputs.debug }}'
source ${{ github.action_path }}/scripts/linux-helpers.sh
source "$GITHUB_ACTION_PATH/scripts/linux-helpers.sh"

VERSION=$(get_twingate_version)
echo "version=$VERSION" >> $GITHUB_OUTPUT
Expand All @@ -50,7 +68,7 @@ runs:
shell: powershell
run: |
$env:DEBUG_MODE = '${{ inputs.debug }}'
. ${{ github.action_path }}/scripts/windows-helpers.ps1
. "$env:GITHUB_ACTION_PATH/scripts/windows-helpers.ps1"

$version = Get-TwingateVersion
Add-Content -Path $env:GITHUB_OUTPUT -Value "version=$version"
Expand Down Expand Up @@ -95,7 +113,7 @@ runs:
shell: bash
run: |
export DEBUG_MODE='${{ inputs.debug }}'
source ${{ github.action_path }}/scripts/linux-helpers.sh
source "$GITHUB_ACTION_PATH/scripts/linux-helpers.sh"

VALID=$(validate_cache_linux)
echo "valid=$VALID" >> $GITHUB_OUTPUT
Expand All @@ -106,7 +124,7 @@ runs:
shell: powershell
run: |
$env:DEBUG_MODE = '${{ inputs.debug }}'
. ${{ github.action_path }}/scripts/windows-helpers.ps1
. "$env:GITHUB_ACTION_PATH/scripts/windows-helpers.ps1"

$cacheDir = "${{ runner.temp }}\twingate-cache"
$isValid = Validate-CacheWindows -CacheDir $cacheDir
Expand All @@ -117,19 +135,19 @@ runs:
shell: bash
run: |
export DEBUG_MODE='${{ inputs.debug }}'
source ${{ github.action_path }}/scripts/linux-helpers.sh
source "$GITHUB_ACTION_PATH/scripts/linux-helpers.sh"

log DEBUG "Installing Twingate from cache"
# Install all packages from cache directory (twingate + dependencies)
sudo dpkg -i ~/.twingate-cache/*.deb || true
sudo apt-get install -f -yq
$SUDO dpkg -i ~/.twingate-cache/*.deb || true
$SUDO apt-get install -f -yq

- name: Copy cached MSI to working directory (Windows)
if: runner.os == 'Windows' && inputs.cache == 'true' && steps.validate-cache-windows.outputs.valid == 'true'
shell: powershell
run: |
$env:DEBUG_MODE = '${{ inputs.debug }}'
. ${{ github.action_path }}/scripts/windows-helpers.ps1
. "$env:GITHUB_ACTION_PATH/scripts/windows-helpers.ps1"

log DEBUG "Copying cached MSI to working directory"
$cacheDir = "${{ runner.temp }}\twingate-cache"
Expand All @@ -147,34 +165,36 @@ runs:
if: runner.os == 'Linux' && (inputs.cache != 'true' || steps.cache-twingate-linux.outputs.cache-hit != 'true' || steps.validate-cache-linux.outputs.valid != 'true' || steps.twingate-version-linux.outputs.version == 'unknown')
shell: bash
run: |
source "$GITHUB_ACTION_PATH/scripts/linux-helpers.sh"

# Import Twingate GPG key for signature verification
curl -fsSL https://packages.twingate.com/apt/gpg.key | sudo gpg --batch --yes --no-tty --dearmor -o /usr/share/keyrings/twingate-client-keyring.gpg
curl -fsSL https://packages.twingate.com/apt/gpg.key | $SUDO gpg --batch --yes --no-tty --dearmor -o /usr/share/keyrings/twingate-client-keyring.gpg

# Add Twingate repository with GPG key verification
echo "deb [signed-by=/usr/share/keyrings/twingate-client-keyring.gpg] https://packages.twingate.com/apt/ * *" | sudo tee /etc/apt/sources.list.d/twingate.list
echo "deb [signed-by=/usr/share/keyrings/twingate-client-keyring.gpg] https://packages.twingate.com/apt/ * *" | $SUDO tee /etc/apt/sources.list.d/twingate.list

sudo apt update
sudo apt-get update -o Dir::Etc::sourcelist="sources.list.d/twingate.list" -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"
$SUDO apt update
$SUDO apt-get update -o Dir::Etc::sourcelist="sources.list.d/twingate.list" -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"

# Download all packages to cache if caching is enabled
if [ "${{ steps.twingate-version-linux.outputs.version }}" != "unknown" ]; then
mkdir -p ~/.twingate-cache
# Download Twingate and all dependencies to cache directory
sudo apt-get install -yq --download-only -o Dir::Cache::Archives="$HOME/.twingate-cache" twingate
$SUDO apt-get install -yq --download-only -o Dir::Cache::Archives="$HOME/.twingate-cache" twingate
# Install to resolve any missing dependencies, then download them
sudo apt-get install -yq --download-only -o Dir::Cache::Archives="$HOME/.twingate-cache" -f
$SUDO apt-get install -yq --download-only -o Dir::Cache::Archives="$HOME/.twingate-cache" -f
# Fix permissions so cache action can save files
sudo chown -R "$(id -u)":"$(id -g)" "$HOME/.twingate-cache"
$SUDO chown -R "$(id -u)":"$(id -g)" "$HOME/.twingate-cache"
fi

sudo apt install -yq twingate
$SUDO apt install -yq twingate

- name: Download and cache Twingate MSI (Windows)
if: runner.os == 'Windows' && (inputs.cache != 'true' || steps.cache-twingate-windows.outputs.cache-hit != 'true' || steps.validate-cache-windows.outputs.valid != 'true' || steps.twingate-version-windows.outputs.version == 'unknown')
shell: powershell
run: |
$env:DEBUG_MODE = '${{ inputs.debug }}'
. ${{ github.action_path }}/scripts/windows-helpers.ps1
. "$env:GITHUB_ACTION_PATH/scripts/windows-helpers.ps1"

# Download MSI
$msiUrl = "https://api.twingate.com/download/windows?installer=msi"
Expand All @@ -200,9 +220,9 @@ runs:
shell: bash
run: |
export DEBUG_MODE='${{ inputs.debug }}'
source ${{ github.action_path }}/scripts/linux-helpers.sh
source "$GITHUB_ACTION_PATH/scripts/linux-helpers.sh"

echo '${{ inputs.service-key }}' | sudo twingate setup --headless -
echo '${{ inputs.service-key }}' | $SUDO twingate setup --headless -
MAX_RETRIES=5
WAIT_TIME=5
n=0
Expand Down Expand Up @@ -248,7 +268,7 @@ runs:
shell: powershell
run: |
$env:DEBUG_MODE = '${{ inputs.debug }}'
. ${{ github.action_path }}/scripts/windows-helpers.ps1
. "$env:GITHUB_ACTION_PATH/scripts/windows-helpers.ps1"

# Install Twingate client and start service
Set-Content .\key.json '${{ inputs.service-key }}'
Expand Down
3 changes: 3 additions & 0 deletions scripts/linux-helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
# Linux helper functions for logging, version detection, and cache validation
# Usage: source ./scripts/linux-helpers.sh

# Source SUDO detection
source "$(dirname "${BASH_SOURCE[0]}")/sudo-detect.sh"

log() {
local level=$1
shift
Expand Down
16 changes: 16 additions & 0 deletions scripts/sudo-detect.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash
# Detects and sets SUDO variable for privilege escalation
# Usage: source sudo-detect.sh

if [ "$(id -u)" -eq 0 ]; then
SUDO=""
else
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
else
echo "[ERROR] sudo is not available. Please run this script as root." >&2
exit 1
fi
fi

export SUDO
Loading