mirror of
https://github.com/tailscale/github-action.git
synced 2026-08-20 23:19:24 +00:00
Merge pull request #304 from tailscale/log_groups
feat: implement log grouping for better output
This commit is contained in:
commit
508737e196
8 changed files with 928 additions and 536 deletions
23
README.md
23
README.md
|
|
@ -29,8 +29,8 @@ and log out immediately after finishing their CI run, at which point they are au
|
|||
by the coordination server. The nodes are also [marked Preapproved][kb-auth-keys]
|
||||
on tailnets which use [Device Approval][kb-device-approval]
|
||||
|
||||
|
||||
### Workload identity federation
|
||||
|
||||
[Workload identity federation][kb-workload-identity-federation] can also be used for authenticating nodes with your tailnet:
|
||||
|
||||
```yaml
|
||||
|
|
@ -92,6 +92,27 @@ tailscale ping my-target.my-tailnet.ts.net
|
|||
|
||||
The `ping` option will wait up to 3 minutes for a connection (direct or relayed).
|
||||
|
||||
## Log mode
|
||||
|
||||
By default, this action folds its major setup and cleanup phases into GitHub Actions log groups.
|
||||
You can change this with the `log-mode` input:
|
||||
|
||||
```yaml
|
||||
- name: Tailscale
|
||||
uses: tailscale/github-action@v4
|
||||
with:
|
||||
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
||||
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
|
||||
tags: tag:ci
|
||||
log-mode: normal
|
||||
```
|
||||
|
||||
Supported values are:
|
||||
|
||||
- `grouped`: fold major action phases in the log. This is the default.
|
||||
- `normal`: print routine action logs without grouping.
|
||||
- `quiet`: suppress routine informational output while preserving warnings and errors.
|
||||
|
||||
## Tailnet Lock
|
||||
|
||||
If you are using this Action in a [Tailnet Lock][kb-tailnet-lock] enabled network, you need to:
|
||||
|
|
|
|||
|
|
@ -64,7 +64,11 @@ inputs:
|
|||
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: ''
|
||||
|
||||
log-mode:
|
||||
description: 'Controls action log output mode. Use `grouped` to fold major setup and cleanup phases, `normal` for ungrouped logs, or `quiet` to suppress routine informational output.'
|
||||
required: false
|
||||
default: 'grouped'
|
||||
|
||||
runs:
|
||||
using: 'node24'
|
||||
main: 'dist/index.js'
|
||||
|
|
|
|||
443
dist/index.js
generated
vendored
443
dist/index.js
generated
vendored
|
|
@ -52031,6 +52031,119 @@ module.exports = {
|
|||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 91338:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
// Copyright (c) Lee Briggs, Tailscale Inc, & Contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.ExecError = void 0;
|
||||
exports.getLogMode = getLogMode;
|
||||
exports.logInfo = logInfo;
|
||||
exports.logDebug = logDebug;
|
||||
exports.withLogGroup = withLogGroup;
|
||||
exports.execCommand = execCommand;
|
||||
const core = __importStar(__nccwpck_require__(37484));
|
||||
const exec = __importStar(__nccwpck_require__(95236));
|
||||
const process = __importStar(__nccwpck_require__(932));
|
||||
class ExecError {
|
||||
constructor(msg, exitCode, stderr) {
|
||||
this.msg = msg;
|
||||
this.exitCode = exitCode;
|
||||
this.stderr = stderr;
|
||||
}
|
||||
toString() {
|
||||
return this.msg;
|
||||
}
|
||||
}
|
||||
exports.ExecError = ExecError;
|
||||
function getLogMode() {
|
||||
const logMode = core.getInput("log-mode") || "grouped";
|
||||
if (logMode !== "grouped" && logMode !== "normal" && logMode !== "quiet") {
|
||||
throw new Error(`Invalid log-mode "${logMode}". Expected "grouped", "normal", or "quiet".`);
|
||||
}
|
||||
return logMode;
|
||||
}
|
||||
function logInfo(logMode, message) {
|
||||
if (logMode !== "quiet") {
|
||||
core.info(message);
|
||||
}
|
||||
}
|
||||
function logDebug(logMode, message) {
|
||||
if (logMode !== "quiet") {
|
||||
core.debug(message);
|
||||
}
|
||||
}
|
||||
async function withLogGroup(logMode, name, fn) {
|
||||
if (logMode !== "grouped") {
|
||||
return fn();
|
||||
}
|
||||
core.startGroup(name);
|
||||
try {
|
||||
return await fn();
|
||||
}
|
||||
finally {
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
async function execCommand(commandLine, args, opts) {
|
||||
const { label, logMode = "normal", ...execOpts } = opts || {};
|
||||
if (label) {
|
||||
logInfo(logMode, `▶️ ${label}`);
|
||||
}
|
||||
const silent = execOpts.silent || logMode === "quiet" || !core.isDebug();
|
||||
const out = await exec.getExecOutput(commandLine, args, {
|
||||
...execOpts,
|
||||
silent,
|
||||
ignoreReturnCode: true,
|
||||
});
|
||||
if (out.exitCode !== 0) {
|
||||
if (silent) {
|
||||
process.stderr.write(out.stderr);
|
||||
}
|
||||
throw new ExecError(`${commandLine} failed with exit code ${out.exitCode}`, out.exitCode, out.stderr);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 41730:
|
||||
|
|
@ -52076,7 +52189,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const cache = __importStar(__nccwpck_require__(5116));
|
||||
const core = __importStar(__nccwpck_require__(37484));
|
||||
const exec = __importStar(__nccwpck_require__(95236));
|
||||
const tc = __importStar(__nccwpck_require__(33472));
|
||||
const child_process_1 = __nccwpck_require__(35317);
|
||||
const crypto = __importStar(__nccwpck_require__(76982));
|
||||
|
|
@ -52085,6 +52197,7 @@ const os = __importStar(__nccwpck_require__(70857));
|
|||
const path = __importStar(__nccwpck_require__(16928));
|
||||
const semver = __importStar(__nccwpck_require__(62088));
|
||||
const promises_1 = __nccwpck_require__(16460);
|
||||
const logging_1 = __nccwpck_require__(91338);
|
||||
const cmdTailscale = "tailscale";
|
||||
const cmdTailscaleFullPath = "/usr/local/bin/tailscale";
|
||||
const cmdTailscaled = "tailscaled";
|
||||
|
|
@ -52102,11 +52215,8 @@ function xdgRuntimeDir() {
|
|||
const versionLatest = "latest";
|
||||
const versionUnstable = "unstable";
|
||||
// Cross-platform Tailscale local API status check
|
||||
async function getTailscaleStatus() {
|
||||
const { stdout } = await execSilent("get tailscale status", cmdTailscale, [
|
||||
"status",
|
||||
"--json",
|
||||
]);
|
||||
async function getTailscaleStatus(logMode = "normal") {
|
||||
const { stdout } = await execSilent("get tailscale status", cmdTailscale, ["status", "--json"], { logMode });
|
||||
return JSON.parse(stdout);
|
||||
}
|
||||
async function run() {
|
||||
|
|
@ -52125,47 +52235,53 @@ async function run() {
|
|||
}
|
||||
// Validate authentication
|
||||
validateAuth(config);
|
||||
// Resolve version
|
||||
config.resolvedVersion = await resolveVersion(config.version, runnerOS);
|
||||
core.info(`Resolved Tailscale version: ${config.resolvedVersion}`);
|
||||
await (0, logging_1.withLogGroup)(config.logMode, "Resolving Tailscale version", async () => {
|
||||
config.resolvedVersion = await resolveVersion(config.version, runnerOS, config.logMode);
|
||||
(0, logging_1.logInfo)(config.logMode, `Resolved Tailscale version: ${config.resolvedVersion}`);
|
||||
});
|
||||
// Set architecture
|
||||
config.arch = getTailscaleArch(runnerOS);
|
||||
// Install Tailscale
|
||||
await installTailscale(config, runnerOS);
|
||||
// Start daemon (non-Windows only)
|
||||
await (0, logging_1.withLogGroup)(config.logMode, "Installing Tailscale", async () => {
|
||||
await installTailscale(config, runnerOS);
|
||||
});
|
||||
if (runnerOS !== runnerWindows) {
|
||||
await startTailscaleDaemon(config);
|
||||
await (0, logging_1.withLogGroup)(config.logMode, "Starting tailscaled", async () => {
|
||||
await startTailscaleDaemon(config);
|
||||
});
|
||||
}
|
||||
// Connect to Tailscale
|
||||
await connectToTailscale(config, runnerOS);
|
||||
// Check Tailscale status (cross-platform)
|
||||
try {
|
||||
const status = await getTailscaleStatus();
|
||||
if (status.BackendState === "Running") {
|
||||
core.info("✅ Tailscale is running and connected!");
|
||||
if (runnerOS === runnerMacOS) {
|
||||
await configureDNSOnMacOS(status);
|
||||
await (0, logging_1.withLogGroup)(config.logMode, "Connecting to Tailscale", async () => {
|
||||
await connectToTailscale(config, runnerOS);
|
||||
});
|
||||
let shouldPingHosts = false;
|
||||
await (0, logging_1.withLogGroup)(config.logMode, "Checking Tailscale status", async () => {
|
||||
try {
|
||||
const status = await getTailscaleStatus(config.logMode);
|
||||
if (status.BackendState === "Running") {
|
||||
(0, logging_1.logInfo)(config.logMode, "✅ Tailscale is running and connected!");
|
||||
if (runnerOS === runnerMacOS) {
|
||||
await configureDNSOnMacOS(status, config.logMode);
|
||||
}
|
||||
shouldPingHosts = true;
|
||||
}
|
||||
else {
|
||||
core.setFailed(`❌ Tailscale backend state: ${status.BackendState}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
await pingHostsIfNecessary(config);
|
||||
// Explicitly exit to prevent hanging
|
||||
process.exit(0);
|
||||
}
|
||||
else {
|
||||
core.setFailed(`❌ Tailscale backend state: ${status.BackendState}`);
|
||||
process.exit(1);
|
||||
catch (err) {
|
||||
core.warning(`Failed to get Tailscale status: ${err}`);
|
||||
if (runnerOS === runnerMacOS) {
|
||||
core.setFailed(`❌ Tailscale status is required in order to configure macOS`);
|
||||
process.exitCode = 2;
|
||||
return;
|
||||
}
|
||||
// Still exit successfully since the main connection worked
|
||||
(0, logging_1.logInfo)(config.logMode, "✅ Tailscale daemon is connected!");
|
||||
shouldPingHosts = true;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
core.warning(`Failed to get Tailscale status: ${err}`);
|
||||
if (runnerOS === runnerMacOS) {
|
||||
core.setFailed(`❌ Tailscale status is required in order to configure macOS`);
|
||||
process.exit(2);
|
||||
}
|
||||
// Still exit successfully since the main connection worked
|
||||
core.info("✅ Tailscale daemon is connected!");
|
||||
});
|
||||
if (shouldPingHosts) {
|
||||
await pingHostsIfNecessary(config);
|
||||
// Explicitly exit to prevent hanging
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
|
|
@ -52176,14 +52292,16 @@ async function pingHostsIfNecessary(config) {
|
|||
if (config.pingHosts.length == 0) {
|
||||
return;
|
||||
}
|
||||
core.info(`Will ping hosts ${config.pingHosts.join(",")} up to 3 minutes each (in parallel) in order to check connectivity`);
|
||||
let pings = config.pingHosts.map((host) => pingHost(host));
|
||||
for (const ping of pings) {
|
||||
await ping;
|
||||
}
|
||||
await (0, logging_1.withLogGroup)(config.logMode, "Pinging Tailscale hosts", async () => {
|
||||
(0, logging_1.logInfo)(config.logMode, `Will ping hosts ${config.pingHosts.join(",")} up to 3 minutes each (in parallel) in order to check connectivity`);
|
||||
let pings = config.pingHosts.map((host) => pingHost(host, config.logMode));
|
||||
for (const ping of pings) {
|
||||
await ping;
|
||||
}
|
||||
});
|
||||
}
|
||||
async function pingHost(host) {
|
||||
core.info(`Pinging host ${host}`);
|
||||
async function pingHost(host, logMode) {
|
||||
(0, logging_1.logInfo)(logMode, `Pinging host ${host}`);
|
||||
let start = new Date().getTime();
|
||||
var i = 0;
|
||||
// Try for up to 180 seconds (3 minutes).
|
||||
|
|
@ -52191,35 +52309,32 @@ async function pingHost(host) {
|
|||
if (i > 0) {
|
||||
// Exponential backoff on wait time, with maximum 5 second wait.
|
||||
let waitTime = Math.min(Math.pow(1.3, i), 5000);
|
||||
core.debug(`Waiting ${waitTime} milliseconds before pinging`);
|
||||
(0, logging_1.logDebug)(logMode, `Waiting ${waitTime} milliseconds before pinging`);
|
||||
await (0, promises_1.setTimeout)(waitTime);
|
||||
}
|
||||
try {
|
||||
let result = await execSilent("ping host", cmdTailscale, [
|
||||
"ping",
|
||||
"-c",
|
||||
"1",
|
||||
host,
|
||||
]);
|
||||
core.info(`✅ Ping host ${host} reachable via direct connection!`);
|
||||
await execSilent("ping host", cmdTailscale, ["ping", "-c", "1", host], {
|
||||
logMode,
|
||||
});
|
||||
(0, logging_1.logInfo)(logMode, `✅ Ping host ${host} reachable via direct connection!`);
|
||||
return;
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof execError &&
|
||||
if (err instanceof logging_1.ExecError &&
|
||||
err.stderr.includes("direct connection not established")) {
|
||||
// Relayed connectivity is good enough, we don't want to tie up a CI job waiting for a direct connection.
|
||||
core.info(`✅ Ping host ${host} reachable via DERP!`);
|
||||
(0, logging_1.logInfo)(logMode, `✅ Ping host ${host} reachable via DERP!`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
core.setFailed(`❌ Ping host ${host} did not respond`);
|
||||
process.exit(1);
|
||||
throw new Error(`❌ Ping host ${host} did not respond`);
|
||||
}
|
||||
async function getInputs() {
|
||||
let ping = core.getInput("ping");
|
||||
let pingHosts = ping?.length > 0 ? ping.split(",") : [];
|
||||
const logMode = (0, logging_1.getLogMode)();
|
||||
const authKey = core.getInput("authkey") || "";
|
||||
const oauthSecret = core.getInput("oauth-secret") || "";
|
||||
// Mask sensitive values in logs unless debug mode is enabled
|
||||
|
|
@ -52249,6 +52364,7 @@ async function getInputs() {
|
|||
useCache: core.getBooleanInput("use-cache"),
|
||||
sha256Sum: core.getInput("sha256sum") || "",
|
||||
pingHosts: pingHosts,
|
||||
logMode: logMode,
|
||||
};
|
||||
if (config.oauthSecret && !config.tags) {
|
||||
throw new Error("the tags parameter is required when using an OAuth client");
|
||||
|
|
@ -52267,19 +52383,14 @@ function validateAuth(config) {
|
|||
throw new Error("Workload identity federation requires using tailscale version 1.90.0 or later.");
|
||||
}
|
||||
}
|
||||
async function resolveVersion(version, runnerOS) {
|
||||
async function resolveVersion(version, runnerOS, logMode) {
|
||||
if (runnerOS === runnerMacOS && version === versionUnstable) {
|
||||
return "main";
|
||||
}
|
||||
if (version === versionLatest || version === versionUnstable) {
|
||||
let path = version === versionUnstable ? versionUnstable : "stable";
|
||||
let pkg = `https://pkgs.tailscale.com/${path}/?mode=json`;
|
||||
const { stdout } = await execSilent(`curl ${pkg}`, "curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-s",
|
||||
pkg,
|
||||
]);
|
||||
const { stdout } = await execSilent(`curl ${pkg}`, "curl", ["-H", "user-agent:action-setup-tailscale", "-s", pkg], { logMode });
|
||||
const response = JSON.parse(stdout);
|
||||
switch (runnerOS) {
|
||||
case runnerLinux:
|
||||
|
|
@ -52338,14 +52449,14 @@ async function installTailscale(config, runnerOS) {
|
|||
if (config.useCache && cacheKey) {
|
||||
const cacheHit = await cache.restoreCache([toolPath], cacheKey);
|
||||
if (cacheHit) {
|
||||
core.info(`Found Tailscale ${config.resolvedVersion} in cache: ${toolPath}`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Found Tailscale ${config.resolvedVersion} in cache: ${toolPath}`);
|
||||
// For Windows, install the cached MSI
|
||||
if (runnerOS === runnerWindows) {
|
||||
await installTailscaleWindows(config, toolPath, true);
|
||||
}
|
||||
else {
|
||||
// For Linux/macOS, copy binaries to /usr/local/bin
|
||||
await installCachedBinaries(toolPath, runnerOS);
|
||||
await installCachedBinaries(config, toolPath, runnerOS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -52364,7 +52475,7 @@ async function installTailscale(config, runnerOS) {
|
|||
if (config.useCache && cacheKey) {
|
||||
try {
|
||||
await cache.saveCache([toolPath], cacheKey);
|
||||
core.info(`Cached Tailscale ${config.resolvedVersion} at: ${toolPath}`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Cached Tailscale ${config.resolvedVersion} at: ${toolPath}`);
|
||||
}
|
||||
catch (error) {
|
||||
const typedError = error;
|
||||
|
|
@ -52372,7 +52483,7 @@ async function installTailscale(config, runnerOS) {
|
|||
throw error;
|
||||
}
|
||||
else if (typedError.name === cache.ReserveCacheError.name) {
|
||||
core.info(typedError.message);
|
||||
(0, logging_1.logInfo)(config.logMode, typedError.message);
|
||||
}
|
||||
else {
|
||||
core.warning(`Cache save failed: ${typedError.message}`);
|
||||
|
|
@ -52399,26 +52510,20 @@ async function installTailscaleLinux(config, toolPath) {
|
|||
// Get SHA256 if not provided
|
||||
if (!config.sha256Sum) {
|
||||
const shaUrl = `${baseUrl}/tailscale_${config.resolvedVersion}_${config.arch}.tgz.sha256`;
|
||||
const { stdout } = await execSilent(`curl ${shaUrl}`, "curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-L",
|
||||
shaUrl,
|
||||
"--fail",
|
||||
]);
|
||||
const { stdout } = await execSilent(`curl ${shaUrl}`, "curl", ["-H", "user-agent:action-setup-tailscale", "-L", shaUrl, "--fail"], { logMode: config.logMode });
|
||||
config.sha256Sum = stdout.trim();
|
||||
}
|
||||
// Download and extract
|
||||
const downloadUrl = `${baseUrl}/tailscale_${config.resolvedVersion}_${config.arch}.tgz`;
|
||||
core.info(`Downloading ${downloadUrl}`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Downloading ${downloadUrl}`);
|
||||
const tarDest = path.join(xdgCacheDir(), "tailscale.tgz");
|
||||
fs.mkdirSync(path.dirname(tarDest), { recursive: true });
|
||||
const tarPath = await tc.downloadTool(downloadUrl, tarDest);
|
||||
// Verify checksum
|
||||
const actualSha = await calculateFileSha256(tarPath);
|
||||
const expectedSha = config.sha256Sum.trim().toLowerCase();
|
||||
core.info(`Expected sha256: ${expectedSha}`);
|
||||
core.info(`Actual sha256: ${actualSha}`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Expected sha256: ${expectedSha}`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Actual sha256: ${actualSha}`);
|
||||
if (actualSha !== expectedSha) {
|
||||
throw new Error("SHA256 checksum mismatch");
|
||||
}
|
||||
|
|
@ -52435,18 +52540,10 @@ async function installTailscaleLinux(config, toolPath) {
|
|||
path.join(toolPath, cmdTailscale),
|
||||
path.join(toolPath, cmdTailscaled),
|
||||
"/usr/local/bin",
|
||||
]);
|
||||
], { logMode: config.logMode });
|
||||
// Make sure they're executable
|
||||
await execSilent("chmod tailscale binary", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaleFullPath,
|
||||
]);
|
||||
await execSilent("chmod tailscaled binary", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaledFullPath,
|
||||
]);
|
||||
await execSilent("chmod tailscale binary", "sudo", ["chmod", "+x", cmdTailscaleFullPath], { logMode: config.logMode });
|
||||
await execSilent("chmod tailscaled binary", "sudo", ["chmod", "+x", cmdTailscaledFullPath], { logMode: config.logMode });
|
||||
}
|
||||
async function installTailscaleWindows(config, toolPath, fromCache = false) {
|
||||
// Create tool directory
|
||||
|
|
@ -52457,7 +52554,7 @@ async function installTailscaleWindows(config, toolPath, fromCache = false) {
|
|||
if (!fs.existsSync(msiPath)) {
|
||||
throw new Error(`Cached MSI not found at ${msiPath}`);
|
||||
}
|
||||
core.info(`Installing cached MSI from ${msiPath}`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Installing cached MSI from ${msiPath}`);
|
||||
}
|
||||
else {
|
||||
// Fresh download
|
||||
|
|
@ -52470,13 +52567,7 @@ async function installTailscaleWindows(config, toolPath, fromCache = false) {
|
|||
// Get SHA256 if not provided
|
||||
if (!config.sha256Sum) {
|
||||
const shaUrl = `${baseUrl}/tailscale-setup-${config.resolvedVersion}-${config.arch}.msi.sha256`;
|
||||
const { stdout } = await execSilent(`curl ${shaUrl}`, "curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-L",
|
||||
shaUrl,
|
||||
"--fail",
|
||||
]);
|
||||
const { stdout } = await execSilent(`curl ${shaUrl}`, "curl", ["-H", "user-agent:action-setup-tailscale", "-L", shaUrl, "--fail"], { logMode: config.logMode });
|
||||
config.sha256Sum = stdout.trim();
|
||||
}
|
||||
// Download MSI
|
||||
|
|
@ -52487,21 +52578,21 @@ async function installTailscaleWindows(config, toolPath, fromCache = false) {
|
|||
if (fs.existsSync(msiPath)) {
|
||||
const existingSha = await calculateFileSha256(msiPath);
|
||||
if (existingSha === expectedSha) {
|
||||
core.info(`Using existing MSI at ${msiPath} (checksum verified)`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Using existing MSI at ${msiPath} (checksum verified)`);
|
||||
needsDownload = false;
|
||||
}
|
||||
else {
|
||||
core.info(`Existing MSI checksum mismatch, re-downloading`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Existing MSI checksum mismatch, re-downloading`);
|
||||
fs.unlinkSync(msiPath);
|
||||
}
|
||||
}
|
||||
if (needsDownload) {
|
||||
core.info(`Downloading ${downloadUrl}`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Downloading ${downloadUrl}`);
|
||||
const downloadedMsiPath = await tc.downloadTool(downloadUrl, msiPath);
|
||||
// Verify checksum
|
||||
const actualSha = await calculateFileSha256(downloadedMsiPath);
|
||||
core.info(`Expected sha256: ${expectedSha}`);
|
||||
core.info(`Actual sha256: ${actualSha}`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Expected sha256: ${expectedSha}`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Actual sha256: ${actualSha}`);
|
||||
if (actualSha !== expectedSha) {
|
||||
throw new Error("SHA256 checksum mismatch");
|
||||
}
|
||||
|
|
@ -52519,17 +52610,18 @@ async function installTailscaleWindows(config, toolPath, fromCache = false) {
|
|||
path.join(process.env.RUNNER_TEMP || "", "tailscale.log"),
|
||||
"/i",
|
||||
msiPath,
|
||||
]);
|
||||
], { logMode: config.logMode });
|
||||
// Add to PATH
|
||||
core.addPath("C:\\Program Files\\Tailscale\\");
|
||||
}
|
||||
async function installTailscaleMacOS(config, toolPath) {
|
||||
core.info("Building tailscale from src on macOS...");
|
||||
(0, logging_1.logInfo)(config.logMode, "Building tailscale from src on macOS...");
|
||||
// Clone the repo
|
||||
await execSilent("clone tailscale repo", "git clone https://github.com/tailscale/tailscale.git tailscale");
|
||||
await execSilent("clone tailscale repo", "git clone https://github.com/tailscale/tailscale.git tailscale", [], { logMode: config.logMode });
|
||||
// Checkout the resolved version
|
||||
await execSilent("checkout resolved version", `git checkout v${config.resolvedVersion}`, [], {
|
||||
cwd: cmdTailscale,
|
||||
logMode: config.logMode,
|
||||
});
|
||||
// Create tool directory and copy binaries there for caching
|
||||
fs.mkdirSync(toolPath, { recursive: true });
|
||||
|
|
@ -52541,6 +52633,7 @@ async function installTailscaleMacOS(config, toolPath) {
|
|||
...process.env,
|
||||
TS_USE_TOOLCHAIN: "1",
|
||||
},
|
||||
logMode: config.logMode,
|
||||
});
|
||||
}
|
||||
// Install binaries to /usr/local/bin
|
||||
|
|
@ -52549,19 +52642,11 @@ async function installTailscaleMacOS(config, toolPath) {
|
|||
path.join(toolPath, cmdTailscale),
|
||||
path.join(toolPath, cmdTailscaled),
|
||||
"/usr/local/bin",
|
||||
]);
|
||||
], { logMode: config.logMode });
|
||||
// Make sure they're executable
|
||||
await execSilent("chmod tailscale", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaleFullPath,
|
||||
]);
|
||||
await execSilent("chmod tailscaled", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaledFullPath,
|
||||
]);
|
||||
core.info("✅ Tailscale installed successfully on macOS from source");
|
||||
await execSilent("chmod tailscale", "sudo", ["chmod", "+x", cmdTailscaleFullPath], { logMode: config.logMode });
|
||||
await execSilent("chmod tailscaled", "sudo", ["chmod", "+x", cmdTailscaledFullPath], { logMode: config.logMode });
|
||||
(0, logging_1.logInfo)(config.logMode, "✅ Tailscale installed successfully on macOS from source");
|
||||
}
|
||||
async function startTailscaleDaemon(config) {
|
||||
const runnerOS = process.env.RUNNER_OS || "";
|
||||
|
|
@ -52576,7 +52661,7 @@ async function startTailscaleDaemon(config) {
|
|||
...stateArgs,
|
||||
...config.tailscaledArgs.split(" ").filter(Boolean),
|
||||
];
|
||||
core.info("Starting tailscaled daemon...");
|
||||
(0, logging_1.logInfo)(config.logMode, "Starting tailscaled daemon...");
|
||||
// Start daemon in background
|
||||
const daemon = (0, child_process_1.spawn)("sudo", ["-E", cmdTailscaled, ...args], {
|
||||
detached: true,
|
||||
|
|
@ -52599,28 +52684,28 @@ async function startTailscaleDaemon(config) {
|
|||
if (daemon.stderr)
|
||||
daemon.stderr.destroy();
|
||||
// Poll the local API until daemon is responsive
|
||||
await waitForDaemonReady();
|
||||
core.info("✅ tailscaled daemon is up and running!");
|
||||
await waitForDaemonReady(config.logMode);
|
||||
(0, logging_1.logInfo)(config.logMode, "✅ tailscaled daemon is up and running!");
|
||||
}
|
||||
async function waitForDaemonReady() {
|
||||
async function waitForDaemonReady(logMode) {
|
||||
const maxWaitMs = 15000; // 15 seconds
|
||||
const pollIntervalMs = 500;
|
||||
let waited = 0;
|
||||
core.info("Waiting for tailscaled daemon to become ready...");
|
||||
(0, logging_1.logInfo)(logMode, "Waiting for tailscaled daemon to become ready...");
|
||||
var lastErr;
|
||||
while (waited < maxWaitMs) {
|
||||
try {
|
||||
const status = await getTailscaleStatus();
|
||||
const status = await getTailscaleStatus(logMode);
|
||||
// If we get any valid response from the API, the daemon is ready
|
||||
if (status) {
|
||||
core.info(`Daemon ready! Initial state: ${status.BackendState || "Unknown"}`);
|
||||
(0, logging_1.logInfo)(logMode, `Daemon ready! Initial state: ${status.BackendState || "Unknown"}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// Daemon not ready yet, keep polling
|
||||
lastErr = err;
|
||||
core.debug(`Waiting for daemon... (${waited}ms elapsed)`);
|
||||
(0, logging_1.logDebug)(logMode, `Waiting for daemon... (${waited}ms elapsed)`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
waited += pollIntervalMs;
|
||||
|
|
@ -52635,7 +52720,9 @@ async function connectToTailscale(config, runnerOS) {
|
|||
hostname = `github-${process.env.COMPUTERNAME}`;
|
||||
}
|
||||
else {
|
||||
const { stdout } = await execSilent("hostname", "hostname");
|
||||
const { stdout } = await execSilent("hostname", "hostname", [], {
|
||||
logMode: config.logMode,
|
||||
});
|
||||
hostname = `github-${stdout.trim()}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -52683,7 +52770,7 @@ async function connectToTailscale(config, runnerOS) {
|
|||
// Retry logic
|
||||
for (let attempt = 1; attempt <= config.retry; attempt++) {
|
||||
try {
|
||||
core.info(`Attempt ${attempt} to bring up Tailscale...`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Attempt ${attempt} to bring up Tailscale...`);
|
||||
let execArgs;
|
||||
if (runnerOS === runnerWindows) {
|
||||
execArgs = [cmdTailscale, ...upArgs];
|
||||
|
|
@ -52693,12 +52780,24 @@ async function connectToTailscale(config, runnerOS) {
|
|||
execArgs = ["sudo", "-E", cmdTailscale, ...upArgs];
|
||||
}
|
||||
const timeoutMs = parseTimeout(config.timeout);
|
||||
await Promise.race([
|
||||
execSilent("tailscale up", execArgs[0], execArgs.slice(1)),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), timeoutMs)),
|
||||
]);
|
||||
let timeoutHandle;
|
||||
try {
|
||||
await Promise.race([
|
||||
execSilent("tailscale up", execArgs[0], execArgs.slice(1), {
|
||||
logMode: config.logMode,
|
||||
}),
|
||||
new Promise((_, reject) => {
|
||||
timeoutHandle = setTimeout(() => reject(new Error("Timeout")), timeoutMs);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
finally {
|
||||
if (timeoutHandle) {
|
||||
clearTimeout(timeoutHandle);
|
||||
}
|
||||
}
|
||||
// Success
|
||||
core.info(`✅ Tailscale up command completed successfully on attempt ${attempt}`);
|
||||
(0, logging_1.logInfo)(config.logMode, `✅ Tailscale up command completed successfully on attempt ${attempt}`);
|
||||
return;
|
||||
}
|
||||
catch (error) {
|
||||
|
|
@ -52707,7 +52806,7 @@ async function connectToTailscale(config, runnerOS) {
|
|||
throw error;
|
||||
}
|
||||
const sleepTime = attempt * 2; // Reduced from 5 to 2 seconds
|
||||
core.info(`Retrying in ${sleepTime} seconds...`);
|
||||
(0, logging_1.logInfo)(config.logMode, `Retrying in ${sleepTime} seconds...`);
|
||||
await sleep(sleepTime * 1000);
|
||||
}
|
||||
}
|
||||
|
|
@ -52745,55 +52844,31 @@ function getToolPath(config, runnerOS) {
|
|||
}
|
||||
return path.join(cacheDirectory, cmdTailscale, config.resolvedVersion, `${runnerOS}-${config.arch}`);
|
||||
}
|
||||
async function installCachedBinaries(toolPath, runnerOS) {
|
||||
async function installCachedBinaries(config, toolPath, runnerOS) {
|
||||
if (runnerOS === runnerLinux || runnerOS === runnerMacOS) {
|
||||
// Copy cached binaries to /usr/local/bin
|
||||
const tailscaleBin = path.join(toolPath, cmdTailscale);
|
||||
const tailscaledBin = path.join(toolPath, cmdTailscaled);
|
||||
if (fs.existsSync(tailscaleBin) && fs.existsSync(tailscaledBin)) {
|
||||
await execSilent("copy tailscale from cache", "sudo", [
|
||||
"cp",
|
||||
tailscaleBin,
|
||||
cmdTailscaleFullPath,
|
||||
]);
|
||||
await execSilent("copy tailscaled from cache", "sudo", [
|
||||
"cp",
|
||||
tailscaledBin,
|
||||
cmdTailscaledFullPath,
|
||||
]);
|
||||
await execSilent("chmod tailscale", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaleFullPath,
|
||||
]);
|
||||
await execSilent("chmod tailscaled", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaledFullPath,
|
||||
]);
|
||||
await execSilent("copy tailscale from cache", "sudo", ["cp", tailscaleBin, cmdTailscaleFullPath], { logMode: config.logMode });
|
||||
await execSilent("copy tailscaled from cache", "sudo", ["cp", tailscaledBin, cmdTailscaledFullPath], { logMode: config.logMode });
|
||||
await execSilent("chmod tailscale", "sudo", ["chmod", "+x", cmdTailscaleFullPath], { logMode: config.logMode });
|
||||
await execSilent("chmod tailscaled", "sudo", ["chmod", "+x", cmdTailscaledFullPath], { logMode: config.logMode });
|
||||
}
|
||||
else {
|
||||
throw new Error(`Cached binaries not found in ${toolPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
async function configureDNSOnMacOS(status) {
|
||||
async function configureDNSOnMacOS(status, logMode) {
|
||||
if (!status.CurrentTailnet.MagicDNSEnabled) {
|
||||
core.info("MagicDNS is disabled, not configuring DNS");
|
||||
(0, logging_1.logInfo)(logMode, "MagicDNS is disabled, not configuring DNS");
|
||||
return;
|
||||
}
|
||||
core.info(`Setting system DNS server to 100.100.100.100 and searchdomains to ${status.CurrentTailnet.MagicDNSSuffix}`);
|
||||
(0, logging_1.logInfo)(logMode, `Setting system DNS server to 100.100.100.100 and searchdomains to ${status.CurrentTailnet.MagicDNSSuffix}`);
|
||||
try {
|
||||
await execSilent("set dns servers", "networksetup", [
|
||||
"-setdnsservers",
|
||||
"Ethernet",
|
||||
"100.100.100.100",
|
||||
]);
|
||||
await execSilent("set search domains", "networksetup", [
|
||||
"-setsearchdomains",
|
||||
"Ethernet",
|
||||
status.CurrentTailnet.MagicDNSSuffix,
|
||||
]);
|
||||
await execSilent("set dns servers", "networksetup", ["-setdnsservers", "Ethernet", "100.100.100.100"], { logMode });
|
||||
await execSilent("set search domains", "networksetup", ["-setsearchdomains", "Ethernet", status.CurrentTailnet.MagicDNSSuffix], { logMode });
|
||||
}
|
||||
catch (e) {
|
||||
throw Error(`Failed to configure DNS on macOS: ${e}`);
|
||||
|
|
@ -52814,30 +52889,10 @@ run();
|
|||
* @throws execError if exec returned a non-zero status code
|
||||
*/
|
||||
async function execSilent(label, cmd, args, opts) {
|
||||
core.info(`▶️ ${label}`);
|
||||
const out = await exec.getExecOutput(cmd, args, {
|
||||
return (0, logging_1.execCommand)(cmd, args, {
|
||||
...opts,
|
||||
silent: !core.isDebug(),
|
||||
ignoreReturnCode: true,
|
||||
label,
|
||||
});
|
||||
if (out.exitCode !== 0) {
|
||||
if (!core.isDebug()) {
|
||||
// When debug logging is off, stderr won't have been written to console, write it now.
|
||||
process.stderr.write(out.stderr);
|
||||
}
|
||||
throw new execError(`${cmd} failed with exit code ${out.exitCode}`, out.exitCode, out.stderr);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
class execError {
|
||||
constructor(msg, exitCode, stderr) {
|
||||
this.msg = msg;
|
||||
this.exitCode = exitCode;
|
||||
this.stderr = stderr;
|
||||
}
|
||||
toString() {
|
||||
return this.msg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -53067,6 +53122,14 @@ module.exports = require("perf_hooks");
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ 932:
|
||||
/***/ ((module) => {
|
||||
|
||||
"use strict";
|
||||
module.exports = require("process");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 83480:
|
||||
/***/ ((module) => {
|
||||
|
||||
|
|
|
|||
241
dist/logout/index.js
generated
vendored
241
dist/logout/index.js
generated
vendored
|
|
@ -25893,6 +25893,119 @@ module.exports = {
|
|||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 1338:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
// Copyright (c) Lee Briggs, Tailscale Inc, & Contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.ExecError = void 0;
|
||||
exports.getLogMode = getLogMode;
|
||||
exports.logInfo = logInfo;
|
||||
exports.logDebug = logDebug;
|
||||
exports.withLogGroup = withLogGroup;
|
||||
exports.execCommand = execCommand;
|
||||
const core = __importStar(__nccwpck_require__(7484));
|
||||
const exec = __importStar(__nccwpck_require__(5236));
|
||||
const process = __importStar(__nccwpck_require__(932));
|
||||
class ExecError {
|
||||
constructor(msg, exitCode, stderr) {
|
||||
this.msg = msg;
|
||||
this.exitCode = exitCode;
|
||||
this.stderr = stderr;
|
||||
}
|
||||
toString() {
|
||||
return this.msg;
|
||||
}
|
||||
}
|
||||
exports.ExecError = ExecError;
|
||||
function getLogMode() {
|
||||
const logMode = core.getInput("log-mode") || "grouped";
|
||||
if (logMode !== "grouped" && logMode !== "normal" && logMode !== "quiet") {
|
||||
throw new Error(`Invalid log-mode "${logMode}". Expected "grouped", "normal", or "quiet".`);
|
||||
}
|
||||
return logMode;
|
||||
}
|
||||
function logInfo(logMode, message) {
|
||||
if (logMode !== "quiet") {
|
||||
core.info(message);
|
||||
}
|
||||
}
|
||||
function logDebug(logMode, message) {
|
||||
if (logMode !== "quiet") {
|
||||
core.debug(message);
|
||||
}
|
||||
}
|
||||
async function withLogGroup(logMode, name, fn) {
|
||||
if (logMode !== "grouped") {
|
||||
return fn();
|
||||
}
|
||||
core.startGroup(name);
|
||||
try {
|
||||
return await fn();
|
||||
}
|
||||
finally {
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
async function execCommand(commandLine, args, opts) {
|
||||
const { label, logMode = "normal", ...execOpts } = opts || {};
|
||||
if (label) {
|
||||
logInfo(logMode, `▶️ ${label}`);
|
||||
}
|
||||
const silent = execOpts.silent || logMode === "quiet" || !core.isDebug();
|
||||
const out = await exec.getExecOutput(commandLine, args, {
|
||||
...execOpts,
|
||||
silent,
|
||||
ignoreReturnCode: true,
|
||||
});
|
||||
if (out.exitCode !== 0) {
|
||||
if (silent) {
|
||||
process.stderr.write(out.stderr);
|
||||
}
|
||||
throw new ExecError(`${commandLine} failed with exit code ${out.exitCode}`, out.exitCode, out.stderr);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 7254:
|
||||
|
|
@ -25937,79 +26050,83 @@ var __importStar = (this && this.__importStar) || (function () {
|
|||
})();
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const core = __importStar(__nccwpck_require__(7484));
|
||||
const exec = __importStar(__nccwpck_require__(5236));
|
||||
const fs = __importStar(__nccwpck_require__(9896));
|
||||
const os = __importStar(__nccwpck_require__(857));
|
||||
const path = __importStar(__nccwpck_require__(6928));
|
||||
const logging_1 = __nccwpck_require__(1338);
|
||||
const runnerWindows = "Windows";
|
||||
const runnerMacOS = "macOS";
|
||||
async function logout() {
|
||||
try {
|
||||
const runnerOS = process.env.RUNNER_OS || "";
|
||||
if (runnerOS === runnerMacOS) {
|
||||
// The below is required to allow GitHub's post job cleanup to complete.
|
||||
core.info("Resetting DNS settings on macOS");
|
||||
await exec.exec("networksetup", ["-setdnsservers", "Ethernet", "Empty"]);
|
||||
await exec.exec("networksetup", [
|
||||
"-setsearchdomains",
|
||||
"Ethernet",
|
||||
"Empty",
|
||||
]);
|
||||
}
|
||||
core.info("🔄 Logging out of Tailscale...");
|
||||
// Check if tailscale is available first
|
||||
try {
|
||||
await exec.exec("tailscale", ["--version"], { silent: true });
|
||||
// Determine the correct command based on OS
|
||||
let execArgs;
|
||||
if (runnerOS === runnerWindows) {
|
||||
execArgs = ["tailscale", "logout"];
|
||||
const logMode = (0, logging_1.getLogMode)();
|
||||
await (0, logging_1.withLogGroup)(logMode, "Cleaning up Tailscale", async () => {
|
||||
if (runnerOS === runnerMacOS) {
|
||||
// The below is required to allow GitHub's post job cleanup to complete.
|
||||
(0, logging_1.logInfo)(logMode, "Resetting DNS settings on macOS");
|
||||
await (0, logging_1.execCommand)("networksetup", ["-setdnsservers", "Ethernet", "Empty"], { logMode });
|
||||
await (0, logging_1.execCommand)("networksetup", ["-setsearchdomains", "Ethernet", "Empty"], { logMode });
|
||||
}
|
||||
else {
|
||||
// Linux and macOS - use system-installed binary with sudo
|
||||
execArgs = ["sudo", "-E", "tailscale", "logout"];
|
||||
}
|
||||
core.info(`Running: ${execArgs.join(" ")}`);
|
||||
(0, logging_1.logInfo)(logMode, "🔄 Logging out of Tailscale...");
|
||||
// Check if tailscale is available first
|
||||
try {
|
||||
await exec.exec(execArgs[0], execArgs.slice(1));
|
||||
core.info("✅ Successfully logged out of Tailscale");
|
||||
await (0, logging_1.execCommand)("tailscale", ["--version"], {
|
||||
logMode,
|
||||
silent: true,
|
||||
});
|
||||
// Determine the correct command based on OS
|
||||
let execArgs;
|
||||
if (runnerOS === runnerWindows) {
|
||||
execArgs = ["tailscale", "logout"];
|
||||
}
|
||||
else {
|
||||
// Linux and macOS - use system-installed binary with sudo
|
||||
execArgs = ["sudo", "-E", "tailscale", "logout"];
|
||||
}
|
||||
(0, logging_1.logInfo)(logMode, `Running: ${execArgs.join(" ")}`);
|
||||
try {
|
||||
await (0, logging_1.execCommand)(execArgs[0], execArgs.slice(1), { logMode });
|
||||
(0, logging_1.logInfo)(logMode, "✅ Successfully logged out of Tailscale");
|
||||
}
|
||||
catch (error) {
|
||||
// Don't fail the action if logout fails - it's just cleanup
|
||||
core.warning(`Failed to logout from Tailscale: ${error}`);
|
||||
(0, logging_1.logInfo)(logMode, "Your ephemeral node will eventually be cleaned up by Tailscale");
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// Don't fail the action if logout fails - it's just cleanup
|
||||
core.warning(`Failed to logout from Tailscale: ${error}`);
|
||||
core.info("Your ephemeral node will eventually be cleaned up by Tailscale");
|
||||
(0, logging_1.logInfo)(logMode, "Tailscale not found or not accessible, skipping logout");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
core.info("Tailscale not found or not accessible, skipping logout");
|
||||
return;
|
||||
}
|
||||
core.info("Stopping tailscale");
|
||||
try {
|
||||
if (runnerOS === runnerWindows) {
|
||||
await exec.exec("net", ["stop", "Tailscale"]);
|
||||
await exec.exec("taskkill", ["/F", "/IM", "tailscale-ipn.exe"]);
|
||||
}
|
||||
else {
|
||||
const xdgRuntimeDir = process.env.XDG_RUNTIME_DIR ||
|
||||
process.env.XDG_CACHE_HOME ||
|
||||
path.join(os.homedir(), ".cache");
|
||||
const pid = fs
|
||||
.readFileSync(path.join(xdgRuntimeDir, "tailscaled.pid"))
|
||||
.toString();
|
||||
if (pid === "") {
|
||||
throw new Error("pid file empty");
|
||||
(0, logging_1.logInfo)(logMode, "Stopping tailscale");
|
||||
try {
|
||||
if (runnerOS === runnerWindows) {
|
||||
await (0, logging_1.execCommand)("net", ["stop", "Tailscale"], { logMode });
|
||||
await (0, logging_1.execCommand)("taskkill", ["/F", "/IM", "tailscale-ipn.exe"], {
|
||||
logMode,
|
||||
});
|
||||
}
|
||||
// The pid is actually the pid of the `sudo` parent of tailscaled, so use pkill -P to kill children of that parent
|
||||
await exec.exec("sudo", ["pkill", "-P", pid]);
|
||||
// Clean up DNS and routes.
|
||||
await exec.exec("sudo", ["tailscaled", "--cleanup"]);
|
||||
else {
|
||||
const xdgRuntimeDir = process.env.XDG_RUNTIME_DIR ||
|
||||
process.env.XDG_CACHE_HOME ||
|
||||
path.join(os.homedir(), ".cache");
|
||||
const pid = fs
|
||||
.readFileSync(path.join(xdgRuntimeDir, "tailscaled.pid"))
|
||||
.toString();
|
||||
if (pid === "") {
|
||||
throw new Error("pid file empty");
|
||||
}
|
||||
// The pid is actually the pid of the `sudo` parent of tailscaled, so use pkill -P to kill children of that parent
|
||||
await (0, logging_1.execCommand)("sudo", ["pkill", "-P", pid], { logMode });
|
||||
// Clean up DNS and routes.
|
||||
await (0, logging_1.execCommand)("sudo", ["tailscaled", "--cleanup"], { logMode });
|
||||
}
|
||||
(0, logging_1.logInfo)(logMode, "✅ Stopped tailscale");
|
||||
}
|
||||
core.info("✅ Stopped tailscale");
|
||||
}
|
||||
catch (error) {
|
||||
core.warning(`Failed to stop tailscale: ${error}`);
|
||||
}
|
||||
catch (error) {
|
||||
core.warning(`Failed to stop tailscale: ${error}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
// Don't fail the action for post-cleanup issues
|
||||
|
|
@ -26185,6 +26302,14 @@ module.exports = require("perf_hooks");
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ 932:
|
||||
/***/ ((module) => {
|
||||
|
||||
"use strict";
|
||||
module.exports = require("process");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 3480:
|
||||
/***/ ((module) => {
|
||||
|
||||
|
|
|
|||
92
src/logging.ts
Normal file
92
src/logging.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) Lee Briggs, Tailscale Inc, & Contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as exec from "@actions/exec";
|
||||
import * as process from "process";
|
||||
|
||||
export type LogMode = "grouped" | "normal" | "quiet";
|
||||
|
||||
export class ExecError {
|
||||
msg: string;
|
||||
exitCode: number;
|
||||
stderr: string;
|
||||
|
||||
public constructor(msg: string, exitCode: number, stderr: string) {
|
||||
this.msg = msg;
|
||||
this.exitCode = exitCode;
|
||||
this.stderr = stderr;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.msg;
|
||||
}
|
||||
}
|
||||
|
||||
export function getLogMode(): LogMode {
|
||||
const logMode = core.getInput("log-mode") || "grouped";
|
||||
if (logMode !== "grouped" && logMode !== "normal" && logMode !== "quiet") {
|
||||
throw new Error(
|
||||
`Invalid log-mode "${logMode}". Expected "grouped", "normal", or "quiet".`,
|
||||
);
|
||||
}
|
||||
return logMode;
|
||||
}
|
||||
|
||||
export function logInfo(logMode: LogMode, message: string): void {
|
||||
if (logMode !== "quiet") {
|
||||
core.info(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function logDebug(logMode: LogMode, message: string): void {
|
||||
if (logMode !== "quiet") {
|
||||
core.debug(message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function withLogGroup<T>(
|
||||
logMode: LogMode,
|
||||
name: string,
|
||||
fn: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
if (logMode !== "grouped") {
|
||||
return fn();
|
||||
}
|
||||
|
||||
core.startGroup(name);
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
export async function execCommand(
|
||||
commandLine: string,
|
||||
args?: string[],
|
||||
opts?: exec.ExecOptions & { label?: string; logMode?: LogMode },
|
||||
): Promise<exec.ExecOutput> {
|
||||
const { label, logMode = "normal", ...execOpts } = opts || {};
|
||||
if (label) {
|
||||
logInfo(logMode, `▶️ ${label}`);
|
||||
}
|
||||
|
||||
const silent = execOpts.silent || logMode === "quiet" || !core.isDebug();
|
||||
const out = await exec.getExecOutput(commandLine, args, {
|
||||
...execOpts,
|
||||
silent,
|
||||
ignoreReturnCode: true,
|
||||
});
|
||||
if (out.exitCode !== 0) {
|
||||
if (silent) {
|
||||
process.stderr.write(out.stderr);
|
||||
}
|
||||
throw new ExecError(
|
||||
`${commandLine} failed with exit code ${out.exitCode}`,
|
||||
out.exitCode,
|
||||
out.stderr,
|
||||
);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as exec from "@actions/exec";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { execCommand, getLogMode, logInfo, withLogGroup } from "../logging";
|
||||
|
||||
const runnerWindows = "Windows";
|
||||
const runnerMacOS = "macOS";
|
||||
|
|
@ -13,75 +13,91 @@ const runnerMacOS = "macOS";
|
|||
async function logout(): Promise<void> {
|
||||
try {
|
||||
const runnerOS = process.env.RUNNER_OS || "";
|
||||
const logMode = getLogMode();
|
||||
|
||||
if (runnerOS === runnerMacOS) {
|
||||
// The below is required to allow GitHub's post job cleanup to complete.
|
||||
core.info("Resetting DNS settings on macOS");
|
||||
await exec.exec("networksetup", ["-setdnsservers", "Ethernet", "Empty"]);
|
||||
await exec.exec("networksetup", [
|
||||
"-setsearchdomains",
|
||||
"Ethernet",
|
||||
"Empty",
|
||||
]);
|
||||
}
|
||||
|
||||
core.info("🔄 Logging out of Tailscale...");
|
||||
|
||||
// Check if tailscale is available first
|
||||
try {
|
||||
await exec.exec("tailscale", ["--version"], { silent: true });
|
||||
|
||||
// Determine the correct command based on OS
|
||||
let execArgs: string[];
|
||||
if (runnerOS === runnerWindows) {
|
||||
execArgs = ["tailscale", "logout"];
|
||||
} else {
|
||||
// Linux and macOS - use system-installed binary with sudo
|
||||
execArgs = ["sudo", "-E", "tailscale", "logout"];
|
||||
}
|
||||
|
||||
core.info(`Running: ${execArgs.join(" ")}`);
|
||||
|
||||
try {
|
||||
await exec.exec(execArgs[0], execArgs.slice(1));
|
||||
core.info("✅ Successfully logged out of Tailscale");
|
||||
} catch (error) {
|
||||
// Don't fail the action if logout fails - it's just cleanup
|
||||
core.warning(`Failed to logout from Tailscale: ${error}`);
|
||||
core.info(
|
||||
"Your ephemeral node will eventually be cleaned up by Tailscale",
|
||||
await withLogGroup(logMode, "Cleaning up Tailscale", async () => {
|
||||
if (runnerOS === runnerMacOS) {
|
||||
// The below is required to allow GitHub's post job cleanup to complete.
|
||||
logInfo(logMode, "Resetting DNS settings on macOS");
|
||||
await execCommand(
|
||||
"networksetup",
|
||||
["-setdnsservers", "Ethernet", "Empty"],
|
||||
{ logMode },
|
||||
);
|
||||
await execCommand(
|
||||
"networksetup",
|
||||
["-setsearchdomains", "Ethernet", "Empty"],
|
||||
{ logMode },
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
core.info("Tailscale not found or not accessible, skipping logout");
|
||||
return;
|
||||
}
|
||||
|
||||
core.info("Stopping tailscale");
|
||||
try {
|
||||
if (runnerOS === runnerWindows) {
|
||||
await exec.exec("net", ["stop", "Tailscale"]);
|
||||
await exec.exec("taskkill", ["/F", "/IM", "tailscale-ipn.exe"]);
|
||||
} else {
|
||||
const xdgRuntimeDir =
|
||||
process.env.XDG_RUNTIME_DIR ||
|
||||
process.env.XDG_CACHE_HOME ||
|
||||
path.join(os.homedir(), ".cache");
|
||||
const pid = fs
|
||||
.readFileSync(path.join(xdgRuntimeDir, "tailscaled.pid"))
|
||||
.toString();
|
||||
if (pid === "") {
|
||||
throw new Error("pid file empty");
|
||||
logInfo(logMode, "🔄 Logging out of Tailscale...");
|
||||
|
||||
// Check if tailscale is available first
|
||||
try {
|
||||
await execCommand("tailscale", ["--version"], {
|
||||
logMode,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
// Determine the correct command based on OS
|
||||
let execArgs: string[];
|
||||
if (runnerOS === runnerWindows) {
|
||||
execArgs = ["tailscale", "logout"];
|
||||
} else {
|
||||
// Linux and macOS - use system-installed binary with sudo
|
||||
execArgs = ["sudo", "-E", "tailscale", "logout"];
|
||||
}
|
||||
// The pid is actually the pid of the `sudo` parent of tailscaled, so use pkill -P to kill children of that parent
|
||||
await exec.exec("sudo", ["pkill", "-P", pid]);
|
||||
// Clean up DNS and routes.
|
||||
await exec.exec("sudo", ["tailscaled", "--cleanup"]);
|
||||
|
||||
logInfo(logMode, `Running: ${execArgs.join(" ")}`);
|
||||
|
||||
try {
|
||||
await execCommand(execArgs[0], execArgs.slice(1), { logMode });
|
||||
logInfo(logMode, "✅ Successfully logged out of Tailscale");
|
||||
} catch (error) {
|
||||
// Don't fail the action if logout fails - it's just cleanup
|
||||
core.warning(`Failed to logout from Tailscale: ${error}`);
|
||||
logInfo(
|
||||
logMode,
|
||||
"Your ephemeral node will eventually be cleaned up by Tailscale",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logInfo(
|
||||
logMode,
|
||||
"Tailscale not found or not accessible, skipping logout",
|
||||
);
|
||||
return;
|
||||
}
|
||||
core.info("✅ Stopped tailscale");
|
||||
} catch (error) {
|
||||
core.warning(`Failed to stop tailscale: ${error}`);
|
||||
}
|
||||
|
||||
logInfo(logMode, "Stopping tailscale");
|
||||
try {
|
||||
if (runnerOS === runnerWindows) {
|
||||
await execCommand("net", ["stop", "Tailscale"], { logMode });
|
||||
await execCommand("taskkill", ["/F", "/IM", "tailscale-ipn.exe"], {
|
||||
logMode,
|
||||
});
|
||||
} else {
|
||||
const xdgRuntimeDir =
|
||||
process.env.XDG_RUNTIME_DIR ||
|
||||
process.env.XDG_CACHE_HOME ||
|
||||
path.join(os.homedir(), ".cache");
|
||||
const pid = fs
|
||||
.readFileSync(path.join(xdgRuntimeDir, "tailscaled.pid"))
|
||||
.toString();
|
||||
if (pid === "") {
|
||||
throw new Error("pid file empty");
|
||||
}
|
||||
// The pid is actually the pid of the `sudo` parent of tailscaled, so use pkill -P to kill children of that parent
|
||||
await execCommand("sudo", ["pkill", "-P", pid], { logMode });
|
||||
// Clean up DNS and routes.
|
||||
await execCommand("sudo", ["tailscaled", "--cleanup"], { logMode });
|
||||
}
|
||||
logInfo(logMode, "✅ Stopped tailscale");
|
||||
} catch (error) {
|
||||
core.warning(`Failed to stop tailscale: ${error}`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// Don't fail the action for post-cleanup issues
|
||||
core.warning(`Post-action cleanup error: ${error}`);
|
||||
|
|
|
|||
513
src/main.ts
513
src/main.ts
|
|
@ -12,6 +12,15 @@ import * as os from "os";
|
|||
import * as path from "path";
|
||||
import * as semver from "semver";
|
||||
import { setTimeout as wait } from "timers/promises";
|
||||
import {
|
||||
ExecError,
|
||||
execCommand,
|
||||
getLogMode,
|
||||
logDebug,
|
||||
logInfo,
|
||||
withLogGroup,
|
||||
} from "./logging";
|
||||
import type { LogMode } from "./logging";
|
||||
|
||||
const cmdTailscale = "tailscale";
|
||||
const cmdTailscaleFullPath = "/usr/local/bin/tailscale";
|
||||
|
|
@ -52,6 +61,7 @@ interface TailscaleConfig {
|
|||
useCache: boolean;
|
||||
sha256Sum: string;
|
||||
pingHosts: string[];
|
||||
logMode: LogMode;
|
||||
}
|
||||
|
||||
type tailnetInfo = {
|
||||
|
|
@ -65,11 +75,15 @@ type tailscaleStatus = {
|
|||
};
|
||||
|
||||
// Cross-platform Tailscale local API status check
|
||||
async function getTailscaleStatus(): Promise<tailscaleStatus> {
|
||||
const { stdout } = await execSilent("get tailscale status", cmdTailscale, [
|
||||
"status",
|
||||
"--json",
|
||||
]);
|
||||
async function getTailscaleStatus(
|
||||
logMode: LogMode = "normal",
|
||||
): Promise<tailscaleStatus> {
|
||||
const { stdout } = await execSilent(
|
||||
"get tailscale status",
|
||||
cmdTailscale,
|
||||
["status", "--json"],
|
||||
{ logMode },
|
||||
);
|
||||
return JSON.parse(stdout);
|
||||
}
|
||||
|
||||
|
|
@ -97,52 +111,76 @@ async function run(): Promise<void> {
|
|||
// Validate authentication
|
||||
validateAuth(config);
|
||||
|
||||
// Resolve version
|
||||
config.resolvedVersion = await resolveVersion(config.version, runnerOS);
|
||||
core.info(`Resolved Tailscale version: ${config.resolvedVersion}`);
|
||||
await withLogGroup(
|
||||
config.logMode,
|
||||
"Resolving Tailscale version",
|
||||
async () => {
|
||||
config.resolvedVersion = await resolveVersion(
|
||||
config.version,
|
||||
runnerOS,
|
||||
config.logMode,
|
||||
);
|
||||
logInfo(
|
||||
config.logMode,
|
||||
`Resolved Tailscale version: ${config.resolvedVersion}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Set architecture
|
||||
config.arch = getTailscaleArch(runnerOS);
|
||||
|
||||
// Install Tailscale
|
||||
await installTailscale(config, runnerOS);
|
||||
await withLogGroup(config.logMode, "Installing Tailscale", async () => {
|
||||
await installTailscale(config, runnerOS);
|
||||
});
|
||||
|
||||
// Start daemon (non-Windows only)
|
||||
if (runnerOS !== runnerWindows) {
|
||||
await startTailscaleDaemon(config);
|
||||
await withLogGroup(config.logMode, "Starting tailscaled", async () => {
|
||||
await startTailscaleDaemon(config);
|
||||
});
|
||||
}
|
||||
|
||||
// Connect to Tailscale
|
||||
await connectToTailscale(config, runnerOS);
|
||||
await withLogGroup(config.logMode, "Connecting to Tailscale", async () => {
|
||||
await connectToTailscale(config, runnerOS);
|
||||
});
|
||||
|
||||
// Check Tailscale status (cross-platform)
|
||||
try {
|
||||
const status = await getTailscaleStatus();
|
||||
if (status.BackendState === "Running") {
|
||||
core.info("✅ Tailscale is running and connected!");
|
||||
if (runnerOS === runnerMacOS) {
|
||||
await configureDNSOnMacOS(status);
|
||||
let shouldPingHosts = false;
|
||||
await withLogGroup(
|
||||
config.logMode,
|
||||
"Checking Tailscale status",
|
||||
async () => {
|
||||
try {
|
||||
const status = await getTailscaleStatus(config.logMode);
|
||||
if (status.BackendState === "Running") {
|
||||
logInfo(config.logMode, "✅ Tailscale is running and connected!");
|
||||
if (runnerOS === runnerMacOS) {
|
||||
await configureDNSOnMacOS(status, config.logMode);
|
||||
}
|
||||
shouldPingHosts = true;
|
||||
} else {
|
||||
core.setFailed(
|
||||
`❌ Tailscale backend state: ${status.BackendState}`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
core.warning(`Failed to get Tailscale status: ${err}`);
|
||||
if (runnerOS === runnerMacOS) {
|
||||
core.setFailed(
|
||||
`❌ Tailscale status is required in order to configure macOS`,
|
||||
);
|
||||
process.exitCode = 2;
|
||||
return;
|
||||
}
|
||||
// Still exit successfully since the main connection worked
|
||||
logInfo(config.logMode, "✅ Tailscale daemon is connected!");
|
||||
shouldPingHosts = true;
|
||||
}
|
||||
await pingHostsIfNecessary(config);
|
||||
// Explicitly exit to prevent hanging
|
||||
process.exit(0);
|
||||
} else {
|
||||
core.setFailed(`❌ Tailscale backend state: ${status.BackendState}`);
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
core.warning(`Failed to get Tailscale status: ${err}`);
|
||||
if (runnerOS === runnerMacOS) {
|
||||
core.setFailed(
|
||||
`❌ Tailscale status is required in order to configure macOS`,
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
// Still exit successfully since the main connection worked
|
||||
core.info("✅ Tailscale daemon is connected!");
|
||||
},
|
||||
);
|
||||
|
||||
if (shouldPingHosts) {
|
||||
await pingHostsIfNecessary(config);
|
||||
// Explicitly exit to prevent hanging
|
||||
process.exit(0);
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed(error instanceof Error ? error.message : String(error));
|
||||
|
|
@ -154,19 +192,22 @@ async function pingHostsIfNecessary(config: TailscaleConfig): Promise<void> {
|
|||
return;
|
||||
}
|
||||
|
||||
core.info(
|
||||
`Will ping hosts ${config.pingHosts.join(
|
||||
",",
|
||||
)} up to 3 minutes each (in parallel) in order to check connectivity`,
|
||||
);
|
||||
let pings = config.pingHosts.map((host) => pingHost(host));
|
||||
for (const ping of pings) {
|
||||
await ping;
|
||||
}
|
||||
await withLogGroup(config.logMode, "Pinging Tailscale hosts", async () => {
|
||||
logInfo(
|
||||
config.logMode,
|
||||
`Will ping hosts ${config.pingHosts.join(
|
||||
",",
|
||||
)} up to 3 minutes each (in parallel) in order to check connectivity`,
|
||||
);
|
||||
let pings = config.pingHosts.map((host) => pingHost(host, config.logMode));
|
||||
for (const ping of pings) {
|
||||
await ping;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function pingHost(host: string): Promise<void> {
|
||||
core.info(`Pinging host ${host}`);
|
||||
async function pingHost(host: string, logMode: LogMode): Promise<void> {
|
||||
logInfo(logMode, `Pinging host ${host}`);
|
||||
let start = new Date().getTime();
|
||||
var i = 0;
|
||||
// Try for up to 180 seconds (3 minutes).
|
||||
|
|
@ -174,37 +215,34 @@ async function pingHost(host: string): Promise<void> {
|
|||
if (i > 0) {
|
||||
// Exponential backoff on wait time, with maximum 5 second wait.
|
||||
let waitTime = Math.min(Math.pow(1.3, i), 5000);
|
||||
core.debug(`Waiting ${waitTime} milliseconds before pinging`);
|
||||
logDebug(logMode, `Waiting ${waitTime} milliseconds before pinging`);
|
||||
await wait(waitTime);
|
||||
}
|
||||
try {
|
||||
let result = await execSilent("ping host", cmdTailscale, [
|
||||
"ping",
|
||||
"-c",
|
||||
"1",
|
||||
host,
|
||||
]);
|
||||
core.info(`✅ Ping host ${host} reachable via direct connection!`);
|
||||
await execSilent("ping host", cmdTailscale, ["ping", "-c", "1", host], {
|
||||
logMode,
|
||||
});
|
||||
logInfo(logMode, `✅ Ping host ${host} reachable via direct connection!`);
|
||||
return;
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof execError &&
|
||||
err instanceof ExecError &&
|
||||
err.stderr.includes("direct connection not established")
|
||||
) {
|
||||
// Relayed connectivity is good enough, we don't want to tie up a CI job waiting for a direct connection.
|
||||
core.info(`✅ Ping host ${host} reachable via DERP!`);
|
||||
logInfo(logMode, `✅ Ping host ${host} reachable via DERP!`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
core.setFailed(`❌ Ping host ${host} did not respond`);
|
||||
process.exit(1);
|
||||
throw new Error(`❌ Ping host ${host} did not respond`);
|
||||
}
|
||||
|
||||
async function getInputs(): Promise<TailscaleConfig> {
|
||||
let ping = core.getInput("ping");
|
||||
let pingHosts = ping?.length > 0 ? ping.split(",") : [];
|
||||
const logMode = getLogMode();
|
||||
|
||||
const authKey = core.getInput("authkey") || "";
|
||||
const oauthSecret = core.getInput("oauth-secret") || "";
|
||||
|
|
@ -237,6 +275,7 @@ async function getInputs(): Promise<TailscaleConfig> {
|
|||
useCache: core.getBooleanInput("use-cache"),
|
||||
sha256Sum: core.getInput("sha256sum") || "",
|
||||
pingHosts: pingHosts,
|
||||
logMode: logMode,
|
||||
};
|
||||
|
||||
if (config.oauthSecret && !config.tags) {
|
||||
|
|
@ -273,6 +312,7 @@ function validateAuth(config: TailscaleConfig): void {
|
|||
async function resolveVersion(
|
||||
version: string,
|
||||
runnerOS: string,
|
||||
logMode: LogMode,
|
||||
): Promise<string> {
|
||||
if (runnerOS === runnerMacOS && version === versionUnstable) {
|
||||
return "main";
|
||||
|
|
@ -281,12 +321,12 @@ async function resolveVersion(
|
|||
if (version === versionLatest || version === versionUnstable) {
|
||||
let path = version === versionUnstable ? versionUnstable : "stable";
|
||||
let pkg = `https://pkgs.tailscale.com/${path}/?mode=json`;
|
||||
const { stdout } = await execSilent(`curl ${pkg}`, "curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-s",
|
||||
pkg,
|
||||
]);
|
||||
const { stdout } = await execSilent(
|
||||
`curl ${pkg}`,
|
||||
"curl",
|
||||
["-H", "user-agent:action-setup-tailscale", "-s", pkg],
|
||||
{ logMode },
|
||||
);
|
||||
const response = JSON.parse(stdout);
|
||||
switch (runnerOS) {
|
||||
case runnerLinux:
|
||||
|
|
@ -351,7 +391,8 @@ async function installTailscale(
|
|||
if (config.useCache && cacheKey) {
|
||||
const cacheHit = await cache.restoreCache([toolPath], cacheKey);
|
||||
if (cacheHit) {
|
||||
core.info(
|
||||
logInfo(
|
||||
config.logMode,
|
||||
`Found Tailscale ${config.resolvedVersion} in cache: ${toolPath}`,
|
||||
);
|
||||
|
||||
|
|
@ -360,7 +401,7 @@ async function installTailscale(
|
|||
await installTailscaleWindows(config, toolPath, true);
|
||||
} else {
|
||||
// For Linux/macOS, copy binaries to /usr/local/bin
|
||||
await installCachedBinaries(toolPath, runnerOS);
|
||||
await installCachedBinaries(config, toolPath, runnerOS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -379,13 +420,16 @@ async function installTailscale(
|
|||
if (config.useCache && cacheKey) {
|
||||
try {
|
||||
await cache.saveCache([toolPath], cacheKey);
|
||||
core.info(`Cached Tailscale ${config.resolvedVersion} at: ${toolPath}`);
|
||||
logInfo(
|
||||
config.logMode,
|
||||
`Cached Tailscale ${config.resolvedVersion} at: ${toolPath}`,
|
||||
);
|
||||
} catch (error) {
|
||||
const typedError = error as Error;
|
||||
if (typedError.name === cache.ValidationError.name) {
|
||||
throw error;
|
||||
} else if (typedError.name === cache.ReserveCacheError.name) {
|
||||
core.info(typedError.message);
|
||||
logInfo(config.logMode, typedError.message);
|
||||
} else {
|
||||
core.warning(`Cache save failed: ${typedError.message}`);
|
||||
}
|
||||
|
|
@ -417,19 +461,18 @@ async function installTailscaleLinux(
|
|||
// Get SHA256 if not provided
|
||||
if (!config.sha256Sum) {
|
||||
const shaUrl = `${baseUrl}/tailscale_${config.resolvedVersion}_${config.arch}.tgz.sha256`;
|
||||
const { stdout } = await execSilent(`curl ${shaUrl}`, "curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-L",
|
||||
shaUrl,
|
||||
"--fail",
|
||||
]);
|
||||
const { stdout } = await execSilent(
|
||||
`curl ${shaUrl}`,
|
||||
"curl",
|
||||
["-H", "user-agent:action-setup-tailscale", "-L", shaUrl, "--fail"],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
config.sha256Sum = stdout.trim();
|
||||
}
|
||||
|
||||
// Download and extract
|
||||
const downloadUrl = `${baseUrl}/tailscale_${config.resolvedVersion}_${config.arch}.tgz`;
|
||||
core.info(`Downloading ${downloadUrl}`);
|
||||
logInfo(config.logMode, `Downloading ${downloadUrl}`);
|
||||
|
||||
const tarDest = path.join(xdgCacheDir(), "tailscale.tgz");
|
||||
fs.mkdirSync(path.dirname(tarDest), { recursive: true });
|
||||
|
|
@ -438,8 +481,8 @@ async function installTailscaleLinux(
|
|||
// Verify checksum
|
||||
const actualSha = await calculateFileSha256(tarPath);
|
||||
const expectedSha = config.sha256Sum.trim().toLowerCase();
|
||||
core.info(`Expected sha256: ${expectedSha}`);
|
||||
core.info(`Actual sha256: ${actualSha}`);
|
||||
logInfo(config.logMode, `Expected sha256: ${expectedSha}`);
|
||||
logInfo(config.logMode, `Actual sha256: ${actualSha}`);
|
||||
if (actualSha !== expectedSha) {
|
||||
throw new Error("SHA256 checksum mismatch");
|
||||
}
|
||||
|
|
@ -463,24 +506,31 @@ async function installTailscaleLinux(
|
|||
);
|
||||
|
||||
// Install binaries to /usr/local/bin
|
||||
await execSilent("copy tailscale binaries to /usr/local/bin", "sudo", [
|
||||
"cp",
|
||||
path.join(toolPath, cmdTailscale),
|
||||
path.join(toolPath, cmdTailscaled),
|
||||
"/usr/local/bin",
|
||||
]);
|
||||
await execSilent(
|
||||
"copy tailscale binaries to /usr/local/bin",
|
||||
"sudo",
|
||||
[
|
||||
"cp",
|
||||
path.join(toolPath, cmdTailscale),
|
||||
path.join(toolPath, cmdTailscaled),
|
||||
"/usr/local/bin",
|
||||
],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
|
||||
// Make sure they're executable
|
||||
await execSilent("chmod tailscale binary", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaleFullPath,
|
||||
]);
|
||||
await execSilent("chmod tailscaled binary", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaledFullPath,
|
||||
]);
|
||||
await execSilent(
|
||||
"chmod tailscale binary",
|
||||
"sudo",
|
||||
["chmod", "+x", cmdTailscaleFullPath],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
await execSilent(
|
||||
"chmod tailscaled binary",
|
||||
"sudo",
|
||||
["chmod", "+x", cmdTailscaledFullPath],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
}
|
||||
|
||||
async function installTailscaleWindows(
|
||||
|
|
@ -497,7 +547,7 @@ async function installTailscaleWindows(
|
|||
if (!fs.existsSync(msiPath)) {
|
||||
throw new Error(`Cached MSI not found at ${msiPath}`);
|
||||
}
|
||||
core.info(`Installing cached MSI from ${msiPath}`);
|
||||
logInfo(config.logMode, `Installing cached MSI from ${msiPath}`);
|
||||
} else {
|
||||
// Fresh download
|
||||
// Determine if stable or unstable
|
||||
|
|
@ -510,13 +560,12 @@ async function installTailscaleWindows(
|
|||
// Get SHA256 if not provided
|
||||
if (!config.sha256Sum) {
|
||||
const shaUrl = `${baseUrl}/tailscale-setup-${config.resolvedVersion}-${config.arch}.msi.sha256`;
|
||||
const { stdout } = await execSilent(`curl ${shaUrl}`, "curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-L",
|
||||
shaUrl,
|
||||
"--fail",
|
||||
]);
|
||||
const { stdout } = await execSilent(
|
||||
`curl ${shaUrl}`,
|
||||
"curl",
|
||||
["-H", "user-agent:action-setup-tailscale", "-L", shaUrl, "--fail"],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
config.sha256Sum = stdout.trim();
|
||||
}
|
||||
|
||||
|
|
@ -529,22 +578,28 @@ async function installTailscaleWindows(
|
|||
if (fs.existsSync(msiPath)) {
|
||||
const existingSha = await calculateFileSha256(msiPath);
|
||||
if (existingSha === expectedSha) {
|
||||
core.info(`Using existing MSI at ${msiPath} (checksum verified)`);
|
||||
logInfo(
|
||||
config.logMode,
|
||||
`Using existing MSI at ${msiPath} (checksum verified)`,
|
||||
);
|
||||
needsDownload = false;
|
||||
} else {
|
||||
core.info(`Existing MSI checksum mismatch, re-downloading`);
|
||||
logInfo(
|
||||
config.logMode,
|
||||
`Existing MSI checksum mismatch, re-downloading`,
|
||||
);
|
||||
fs.unlinkSync(msiPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (needsDownload) {
|
||||
core.info(`Downloading ${downloadUrl}`);
|
||||
logInfo(config.logMode, `Downloading ${downloadUrl}`);
|
||||
const downloadedMsiPath = await tc.downloadTool(downloadUrl, msiPath);
|
||||
|
||||
// Verify checksum
|
||||
const actualSha = await calculateFileSha256(downloadedMsiPath);
|
||||
core.info(`Expected sha256: ${expectedSha}`);
|
||||
core.info(`Actual sha256: ${actualSha}`);
|
||||
logInfo(config.logMode, `Expected sha256: ${expectedSha}`);
|
||||
logInfo(config.logMode, `Actual sha256: ${actualSha}`);
|
||||
if (actualSha !== expectedSha) {
|
||||
throw new Error("SHA256 checksum mismatch");
|
||||
}
|
||||
|
|
@ -558,13 +613,18 @@ async function installTailscaleWindows(
|
|||
}
|
||||
|
||||
// Install MSI (same for both fresh and cached)
|
||||
await execSilent("install msi", "msiexec.exe", [
|
||||
"/quiet",
|
||||
`/l*v`,
|
||||
path.join(process.env.RUNNER_TEMP || "", "tailscale.log"),
|
||||
"/i",
|
||||
msiPath,
|
||||
]);
|
||||
await execSilent(
|
||||
"install msi",
|
||||
"msiexec.exe",
|
||||
[
|
||||
"/quiet",
|
||||
`/l*v`,
|
||||
path.join(process.env.RUNNER_TEMP || "", "tailscale.log"),
|
||||
"/i",
|
||||
msiPath,
|
||||
],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
|
||||
// Add to PATH
|
||||
core.addPath("C:\\Program Files\\Tailscale\\");
|
||||
|
|
@ -574,12 +634,14 @@ async function installTailscaleMacOS(
|
|||
config: TailscaleConfig,
|
||||
toolPath: string,
|
||||
): Promise<void> {
|
||||
core.info("Building tailscale from src on macOS...");
|
||||
logInfo(config.logMode, "Building tailscale from src on macOS...");
|
||||
|
||||
// Clone the repo
|
||||
await execSilent(
|
||||
"clone tailscale repo",
|
||||
"git clone https://github.com/tailscale/tailscale.git tailscale",
|
||||
[],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
|
||||
// Checkout the resolved version
|
||||
|
|
@ -589,6 +651,7 @@ async function installTailscaleMacOS(
|
|||
[],
|
||||
{
|
||||
cwd: cmdTailscale,
|
||||
logMode: config.logMode,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -607,31 +670,42 @@ async function installTailscaleMacOS(
|
|||
...process.env,
|
||||
TS_USE_TOOLCHAIN: "1",
|
||||
},
|
||||
logMode: config.logMode,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Install binaries to /usr/local/bin
|
||||
await execSilent("copy binaries to /usr/local/bin", "sudo", [
|
||||
"cp",
|
||||
path.join(toolPath, cmdTailscale),
|
||||
path.join(toolPath, cmdTailscaled),
|
||||
"/usr/local/bin",
|
||||
]);
|
||||
await execSilent(
|
||||
"copy binaries to /usr/local/bin",
|
||||
"sudo",
|
||||
[
|
||||
"cp",
|
||||
path.join(toolPath, cmdTailscale),
|
||||
path.join(toolPath, cmdTailscaled),
|
||||
"/usr/local/bin",
|
||||
],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
|
||||
// Make sure they're executable
|
||||
await execSilent("chmod tailscale", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaleFullPath,
|
||||
]);
|
||||
await execSilent("chmod tailscaled", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaledFullPath,
|
||||
]);
|
||||
await execSilent(
|
||||
"chmod tailscale",
|
||||
"sudo",
|
||||
["chmod", "+x", cmdTailscaleFullPath],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
await execSilent(
|
||||
"chmod tailscaled",
|
||||
"sudo",
|
||||
["chmod", "+x", cmdTailscaledFullPath],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
|
||||
core.info("✅ Tailscale installed successfully on macOS from source");
|
||||
logInfo(
|
||||
config.logMode,
|
||||
"✅ Tailscale installed successfully on macOS from source",
|
||||
);
|
||||
}
|
||||
|
||||
async function startTailscaleDaemon(config: TailscaleConfig): Promise<void> {
|
||||
|
|
@ -651,7 +725,7 @@ async function startTailscaleDaemon(config: TailscaleConfig): Promise<void> {
|
|||
...config.tailscaledArgs.split(" ").filter(Boolean),
|
||||
];
|
||||
|
||||
core.info("Starting tailscaled daemon...");
|
||||
logInfo(config.logMode, "Starting tailscaled daemon...");
|
||||
|
||||
// Start daemon in background
|
||||
const daemon = spawn("sudo", ["-E", cmdTailscaled, ...args], {
|
||||
|
|
@ -676,25 +750,26 @@ async function startTailscaleDaemon(config: TailscaleConfig): Promise<void> {
|
|||
if (daemon.stderr) daemon.stderr.destroy();
|
||||
|
||||
// Poll the local API until daemon is responsive
|
||||
await waitForDaemonReady();
|
||||
await waitForDaemonReady(config.logMode);
|
||||
|
||||
core.info("✅ tailscaled daemon is up and running!");
|
||||
logInfo(config.logMode, "✅ tailscaled daemon is up and running!");
|
||||
}
|
||||
|
||||
async function waitForDaemonReady(): Promise<void> {
|
||||
async function waitForDaemonReady(logMode: LogMode): Promise<void> {
|
||||
const maxWaitMs = 15000; // 15 seconds
|
||||
const pollIntervalMs = 500;
|
||||
let waited = 0;
|
||||
|
||||
core.info("Waiting for tailscaled daemon to become ready...");
|
||||
logInfo(logMode, "Waiting for tailscaled daemon to become ready...");
|
||||
|
||||
var lastErr: any;
|
||||
while (waited < maxWaitMs) {
|
||||
try {
|
||||
const status = await getTailscaleStatus();
|
||||
const status = await getTailscaleStatus(logMode);
|
||||
// If we get any valid response from the API, the daemon is ready
|
||||
if (status) {
|
||||
core.info(
|
||||
logInfo(
|
||||
logMode,
|
||||
`Daemon ready! Initial state: ${status.BackendState || "Unknown"}`,
|
||||
);
|
||||
return;
|
||||
|
|
@ -702,7 +777,7 @@ async function waitForDaemonReady(): Promise<void> {
|
|||
} catch (err) {
|
||||
// Daemon not ready yet, keep polling
|
||||
lastErr = err;
|
||||
core.debug(`Waiting for daemon... (${waited}ms elapsed)`);
|
||||
logDebug(logMode, `Waiting for daemon... (${waited}ms elapsed)`);
|
||||
}
|
||||
await sleep(pollIntervalMs);
|
||||
waited += pollIntervalMs;
|
||||
|
|
@ -723,7 +798,9 @@ async function connectToTailscale(
|
|||
if (runnerOS === runnerWindows) {
|
||||
hostname = `github-${process.env.COMPUTERNAME}`;
|
||||
} else {
|
||||
const { stdout } = await execSilent("hostname", "hostname");
|
||||
const { stdout } = await execSilent("hostname", "hostname", [], {
|
||||
logMode: config.logMode,
|
||||
});
|
||||
hostname = `github-${stdout.trim()}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -777,7 +854,7 @@ async function connectToTailscale(
|
|||
// Retry logic
|
||||
for (let attempt = 1; attempt <= config.retry; attempt++) {
|
||||
try {
|
||||
core.info(`Attempt ${attempt} to bring up Tailscale...`);
|
||||
logInfo(config.logMode, `Attempt ${attempt} to bring up Tailscale...`);
|
||||
|
||||
let execArgs: string[];
|
||||
if (runnerOS === runnerWindows) {
|
||||
|
|
@ -788,15 +865,28 @@ async function connectToTailscale(
|
|||
}
|
||||
|
||||
const timeoutMs = parseTimeout(config.timeout);
|
||||
await Promise.race([
|
||||
execSilent("tailscale up", execArgs[0], execArgs.slice(1)),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error("Timeout")), timeoutMs),
|
||||
),
|
||||
]);
|
||||
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
await Promise.race([
|
||||
execSilent("tailscale up", execArgs[0], execArgs.slice(1), {
|
||||
logMode: config.logMode,
|
||||
}),
|
||||
new Promise<never>((_, reject) => {
|
||||
timeoutHandle = setTimeout(
|
||||
() => reject(new Error("Timeout")),
|
||||
timeoutMs,
|
||||
);
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
if (timeoutHandle) {
|
||||
clearTimeout(timeoutHandle);
|
||||
}
|
||||
}
|
||||
|
||||
// Success
|
||||
core.info(
|
||||
logInfo(
|
||||
config.logMode,
|
||||
`✅ Tailscale up command completed successfully on attempt ${attempt}`,
|
||||
);
|
||||
return;
|
||||
|
|
@ -807,7 +897,7 @@ async function connectToTailscale(
|
|||
}
|
||||
|
||||
const sleepTime = attempt * 2; // Reduced from 5 to 2 seconds
|
||||
core.info(`Retrying in ${sleepTime} seconds...`);
|
||||
logInfo(config.logMode, `Retrying in ${sleepTime} seconds...`);
|
||||
await sleep(sleepTime * 1000);
|
||||
}
|
||||
}
|
||||
|
|
@ -862,6 +952,7 @@ function getToolPath(config: TailscaleConfig, runnerOS: string): string {
|
|||
}
|
||||
|
||||
async function installCachedBinaries(
|
||||
config: TailscaleConfig,
|
||||
toolPath: string,
|
||||
runnerOS: string,
|
||||
): Promise<void> {
|
||||
|
|
@ -871,52 +962,62 @@ async function installCachedBinaries(
|
|||
const tailscaledBin = path.join(toolPath, cmdTailscaled);
|
||||
|
||||
if (fs.existsSync(tailscaleBin) && fs.existsSync(tailscaledBin)) {
|
||||
await execSilent("copy tailscale from cache", "sudo", [
|
||||
"cp",
|
||||
tailscaleBin,
|
||||
cmdTailscaleFullPath,
|
||||
]);
|
||||
await execSilent("copy tailscaled from cache", "sudo", [
|
||||
"cp",
|
||||
tailscaledBin,
|
||||
cmdTailscaledFullPath,
|
||||
]);
|
||||
await execSilent("chmod tailscale", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaleFullPath,
|
||||
]);
|
||||
await execSilent("chmod tailscaled", "sudo", [
|
||||
"chmod",
|
||||
"+x",
|
||||
cmdTailscaledFullPath,
|
||||
]);
|
||||
await execSilent(
|
||||
"copy tailscale from cache",
|
||||
"sudo",
|
||||
["cp", tailscaleBin, cmdTailscaleFullPath],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
await execSilent(
|
||||
"copy tailscaled from cache",
|
||||
"sudo",
|
||||
["cp", tailscaledBin, cmdTailscaledFullPath],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
await execSilent(
|
||||
"chmod tailscale",
|
||||
"sudo",
|
||||
["chmod", "+x", cmdTailscaleFullPath],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
await execSilent(
|
||||
"chmod tailscaled",
|
||||
"sudo",
|
||||
["chmod", "+x", cmdTailscaledFullPath],
|
||||
{ logMode: config.logMode },
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Cached binaries not found in ${toolPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function configureDNSOnMacOS(status: tailscaleStatus): Promise<void> {
|
||||
async function configureDNSOnMacOS(
|
||||
status: tailscaleStatus,
|
||||
logMode: LogMode,
|
||||
): Promise<void> {
|
||||
if (!status.CurrentTailnet.MagicDNSEnabled) {
|
||||
core.info("MagicDNS is disabled, not configuring DNS");
|
||||
logInfo(logMode, "MagicDNS is disabled, not configuring DNS");
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(
|
||||
logInfo(
|
||||
logMode,
|
||||
`Setting system DNS server to 100.100.100.100 and searchdomains to ${status.CurrentTailnet.MagicDNSSuffix}`,
|
||||
);
|
||||
try {
|
||||
await execSilent("set dns servers", "networksetup", [
|
||||
"-setdnsservers",
|
||||
"Ethernet",
|
||||
"100.100.100.100",
|
||||
]);
|
||||
await execSilent("set search domains", "networksetup", [
|
||||
"-setsearchdomains",
|
||||
"Ethernet",
|
||||
status.CurrentTailnet.MagicDNSSuffix,
|
||||
]);
|
||||
await execSilent(
|
||||
"set dns servers",
|
||||
"networksetup",
|
||||
["-setdnsservers", "Ethernet", "100.100.100.100"],
|
||||
{ logMode },
|
||||
);
|
||||
await execSilent(
|
||||
"set search domains",
|
||||
"networksetup",
|
||||
["-setsearchdomains", "Ethernet", status.CurrentTailnet.MagicDNSSuffix],
|
||||
{ logMode },
|
||||
);
|
||||
} catch (e) {
|
||||
throw Error(`Failed to configure DNS on macOS: ${e}`);
|
||||
}
|
||||
|
|
@ -941,40 +1042,10 @@ async function execSilent(
|
|||
label: string,
|
||||
cmd: string,
|
||||
args?: string[],
|
||||
opts?: {},
|
||||
opts?: exec.ExecOptions & { logMode?: LogMode },
|
||||
): Promise<exec.ExecOutput> {
|
||||
core.info(`▶️ ${label}`);
|
||||
const out = await exec.getExecOutput(cmd, args, {
|
||||
return execCommand(cmd, args, {
|
||||
...opts,
|
||||
silent: !core.isDebug(),
|
||||
ignoreReturnCode: true,
|
||||
label,
|
||||
});
|
||||
if (out.exitCode !== 0) {
|
||||
if (!core.isDebug()) {
|
||||
// When debug logging is off, stderr won't have been written to console, write it now.
|
||||
process.stderr.write(out.stderr);
|
||||
}
|
||||
throw new execError(
|
||||
`${cmd} failed with exit code ${out.exitCode}`,
|
||||
out.exitCode,
|
||||
out.stderr,
|
||||
);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
class execError {
|
||||
msg: string;
|
||||
exitCode: number;
|
||||
stderr: string;
|
||||
|
||||
public constructor(msg: string, exitCode: number, stderr: string) {
|
||||
this.msg = msg;
|
||||
this.exitCode = exitCode;
|
||||
this.stderr = stderr;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.msg;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
"types": ["node"], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue