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
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
runner-os: Linux
arch: amd64
version: latest
ping: 100.99.0.2,lax-pve.pineapplefish.ts.net,lax-pve

# Try unstable too
- os: ubuntu-latest
Expand All @@ -45,6 +46,7 @@ jobs:
runner-os: Windows
arch: amd64
version: latest
ping: 100.99.0.2,lax-pve.pineapplefish.ts.net,lax-pve

- os: windows-latest
runner-os: Windows
Expand All @@ -62,6 +64,7 @@ jobs:
runner-os: macOS
arch: amd64
version: latest
ping: 100.99.0.2 # hostnames aren't resolving on MacOS, just ping IP lax-pve.pineapplefish.ts.net,lax-pve

# macOS ARM
- os: macos-14
Expand Down Expand Up @@ -99,6 +102,7 @@ jobs:
use-cache: false
timeout: "5m"
retry: 3
ping: "${{ matrix.ping }}"

# Test Tailscale status command
- name: Check Tailscale Status
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ This action is written in Typescript using official GitHub SDKs. It provides som
| `tailscaled-args` | Additional `tailscaled` arguments | false | |
| `statedir` | State directory (if empty, uses memory) | false | |
| `sha256sum` | Expected SHA256 checksum | false | |
| `ping` | Comma separated list of hosts (Tailscale IP addresses or machine names if MagicDNS is enabled on the tailnet) to `tailscale ping` for connectivity verification after `tailscale up` completes | false | |

## Authentication

Expand Down Expand Up @@ -209,4 +210,6 @@ For security issues, please see our [security policy](SECURITY.md).

## CI Notes

CI tests run against the tailscalegithubactionbot.github tailnet. Check our usual credential store for credentials.
CI tests run against the pineapplefish-tailnet.org.github tailnet. Check our usual credential store for credentials.

`tag:ci` must have access to the `lax-pve` server.
6 changes: 5 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ inputs:
description: 'Expected SHA256 checksum of the Tailscale package. If not provided, it will be fetched automatically.'
required: false
default: ''

ping:
Comment thread
oxtoacart marked this conversation as resolved.
description: 'Comma separated list of hosts (Tailscale IP addresses or machine names if MagicDNS is enabled on the tailnet) to `tailscale ping` for connectivity verification after `tailscale up` completes.'
required: false
default: ''

runs:
using: 'node20'
main: 'dist/index.js'
Expand Down
35 changes: 34 additions & 1 deletion dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41241,6 +41241,7 @@ async function run() {
core.debug(`Tailscale status: ${JSON.stringify(status)}`);
if (status.BackendState === "Running") {
core.info("✅ Tailscale is running and connected!");
pingHostsIfNecessary(config);
// Explicitly exit to prevent hanging
process.exit(0);
}
Expand All @@ -41252,15 +41253,46 @@ async function run() {
catch (err) {
core.warning(`Failed to get Tailscale status: ${err}`);
// Still exit successfully since the main connection worked
core.info("✅ Tailscale connection completed successfully!");
core.info("✅ Tailscale daemon is connected!");
Comment thread
oxtoacart marked this conversation as resolved.
pingHostsIfNecessary(config);
// Explicitly exit to prevent hanging
process.exit(0);
}
}
catch (error) {
core.setFailed(error instanceof Error ? error.message : String(error));
}
}
async function pingHostsIfNecessary(config) {
const directConnectionWarning = "direct connection not established";
if (config.pingHosts.length == 0) {
return;
}
core.info(`Will ping hosts ${config.pingHosts.join(",")} up to 3 minutes in order to check connectivity`);
for (const host of config.pingHosts) {
core.info(`Pinging host ${host}`);
let result = await exec.getExecOutput(cmdTailscale, [
"ping",
"-c",
"36",
host,
]);
if (result.exitCode === 0) {
core.info(`✅ Ping host ${host} responded!`);
}
else if (result.stderr.includes(directConnectionWarning) ||
result.stdout.includes(directConnectionWarning)) {
core.warning(`⚠️ Ping host ${host} reachable only via DERP, not direct connection.`);
}
else {
core.setFailed(`❌ Ping host ${host} did not respond`);
process.exit(1);
}
}
}
async function getInputs() {
let ping = core.getInput("ping");
let pingHosts = ping?.length > 0 ? ping.split(",") : [];
return {
version: core.getInput("version") || "1.82.0",
resolvedVersion: "",
Expand All @@ -41277,6 +41309,7 @@ async function getInputs() {
retry: parseInt(core.getInput("retry") || "5"),
useCache: core.getBooleanInput("use-cache"),
sha256Sum: core.getInput("sha256sum") || "",
pingHosts: pingHosts,
};
}
function validateAuth(config) {
Expand Down
46 changes: 45 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface TailscaleConfig {
retry: number;
useCache: boolean;
sha256Sum: string;
pingHosts: string[];
}

// Cross-platform Tailscale local API status check
Expand Down Expand Up @@ -166,6 +167,7 @@ async function run(): Promise<void> {
core.debug(`Tailscale status: ${JSON.stringify(status)}`);
if (status.BackendState === "Running") {
core.info("✅ Tailscale is running and connected!");
pingHostsIfNecessary(config);
// Explicitly exit to prevent hanging
process.exit(0);
} else {
Expand All @@ -175,15 +177,56 @@ async function run(): Promise<void> {
} catch (err) {
core.warning(`Failed to get Tailscale status: ${err}`);
// Still exit successfully since the main connection worked
core.info("✅ Tailscale connection completed successfully!");
core.info("✅ Tailscale daemon is connected!");
pingHostsIfNecessary(config);
// Explicitly exit to prevent hanging
process.exit(0);
}
} catch (error) {
core.setFailed(error instanceof Error ? error.message : String(error));
}
}

async function pingHostsIfNecessary(config: TailscaleConfig): Promise<void> {
Comment thread
oxtoacart marked this conversation as resolved.
const directConnectionWarning = "direct connection not established";

if (config.pingHosts.length == 0) {
return;
}

core.info(
`Will ping hosts ${config.pingHosts.join(
","
)} up to 3 minutes in order to check connectivity`
);
for (const host of config.pingHosts) {
core.info(`Pinging host ${host}`);
let result = await exec.getExecOutput(cmdTailscale, [
"ping",
"-c",
"36",
host,
]);
if (result.exitCode === 0) {
core.info(`✅ Ping host ${host} responded!`);
} else if (
result.stderr.includes(directConnectionWarning) ||
result.stdout.includes(directConnectionWarning)
) {
core.warning(
`⚠️ Ping host ${host} reachable only via DERP, not direct connection.`
);
} else {
core.setFailed(`❌ Ping host ${host} did not respond`);
process.exit(1);
}
}
}

async function getInputs(): Promise<TailscaleConfig> {
let ping = core.getInput("ping");
let pingHosts = ping?.length > 0 ? ping.split(",") : [];

return {
version: core.getInput("version") || "1.82.0",
resolvedVersion: "",
Expand All @@ -200,6 +243,7 @@ async function getInputs(): Promise<TailscaleConfig> {
retry: parseInt(core.getInput("retry") || "5"),
useCache: core.getBooleanInput("use-cache"),
sha256Sum: core.getInput("sha256sum") || "",
pingHosts: pingHosts,
};
}

Expand Down
Loading