mirror of
https://github.com/tailscale/github-action.git
synced 2026-08-20 06:09:23 +00:00
add dependency on prettier and apply autoformatting
Updates tailscale/corp#30483 Signed-off-by: Percy Wegmann <percy@tailscale.com>
This commit is contained in:
parent
391d5e0792
commit
39ac0a5882
6 changed files with 546 additions and 385 deletions
349
dist/index.js
vendored
349
dist/index.js
vendored
|
|
@ -41113,37 +41113,40 @@ 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 cache = __importStar(__nccwpck_require__(5116));
|
||||
const fs = __importStar(__nccwpck_require__(79896));
|
||||
const path = __importStar(__nccwpck_require__(16928));
|
||||
const os = __importStar(__nccwpck_require__(70857));
|
||||
const child_process_1 = __nccwpck_require__(35317);
|
||||
const crypto = __importStar(__nccwpck_require__(76982));
|
||||
const fs = __importStar(__nccwpck_require__(79896));
|
||||
const http = __importStar(__nccwpck_require__(58611));
|
||||
const os = __importStar(__nccwpck_require__(70857));
|
||||
const path = __importStar(__nccwpck_require__(16928));
|
||||
// Cross-platform Tailscale local API status check
|
||||
async function getTailscaleStatus() {
|
||||
const platform = os.platform();
|
||||
if (platform === 'win32') {
|
||||
if (platform === "win32") {
|
||||
// Windows: use tailscale status command
|
||||
const { stdout } = await exec.getExecOutput('tailscale', ['status', '--json']);
|
||||
const { stdout } = await exec.getExecOutput("tailscale", [
|
||||
"status",
|
||||
"--json",
|
||||
]);
|
||||
return JSON.parse(stdout);
|
||||
}
|
||||
else if (platform === 'darwin') {
|
||||
else if (platform === "darwin") {
|
||||
// macOS: use /var/run/tailscaled.socket
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
socketPath: '/var/run/tailscaled.socket',
|
||||
path: '/localapi/v0/status',
|
||||
method: 'GET',
|
||||
headers: { Host: 'local-tailscaled.sock' },
|
||||
socketPath: "/var/run/tailscaled.socket",
|
||||
path: "/localapi/v0/status",
|
||||
method: "GET",
|
||||
headers: { Host: "local-tailscaled.sock" },
|
||||
};
|
||||
const req = http.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => (data += chunk));
|
||||
res.on('end', () => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => (data += chunk));
|
||||
res.on("end", () => {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
}
|
||||
|
|
@ -41155,9 +41158,9 @@ async function getTailscaleStatus() {
|
|||
// Set timeout to prevent hanging
|
||||
req.setTimeout(5000, () => {
|
||||
req.destroy();
|
||||
reject(new Error('Request timeout'));
|
||||
reject(new Error("Request timeout"));
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
|
@ -41165,15 +41168,15 @@ async function getTailscaleStatus() {
|
|||
// Linux: use Unix socket
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
socketPath: '/run/tailscale/tailscaled.sock',
|
||||
path: '/localapi/v0/status',
|
||||
method: 'GET',
|
||||
headers: { Host: 'local-tailscaled.sock' },
|
||||
socketPath: "/run/tailscale/tailscaled.sock",
|
||||
path: "/localapi/v0/status",
|
||||
method: "GET",
|
||||
headers: { Host: "local-tailscaled.sock" },
|
||||
};
|
||||
const req = http.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => (data += chunk));
|
||||
res.on('end', () => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => (data += chunk));
|
||||
res.on("end", () => {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
}
|
||||
|
|
@ -41185,9 +41188,9 @@ async function getTailscaleStatus() {
|
|||
// Set timeout to prevent hanging
|
||||
req.setTimeout(5000, () => {
|
||||
req.destroy();
|
||||
reject(new Error('Request timeout'));
|
||||
reject(new Error("Request timeout"));
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
|
@ -41195,9 +41198,9 @@ async function getTailscaleStatus() {
|
|||
async function run() {
|
||||
try {
|
||||
// Validate runner OS
|
||||
const runnerOS = process.env.RUNNER_OS || '';
|
||||
if (!['Linux', 'Windows', 'macOS'].includes(runnerOS)) {
|
||||
throw new Error('Support Linux, Windows, and macOS Only');
|
||||
const runnerOS = process.env.RUNNER_OS || "";
|
||||
if (!["Linux", "Windows", "macOS"].includes(runnerOS)) {
|
||||
throw new Error("Support Linux, Windows, and macOS Only");
|
||||
}
|
||||
// Get and validate inputs
|
||||
const config = await getInputs();
|
||||
|
|
@ -41211,7 +41214,7 @@ async function run() {
|
|||
// Install Tailscale
|
||||
await installTailscale(config, runnerOS);
|
||||
// Start daemon (non-Windows only)
|
||||
if (runnerOS !== 'Windows') {
|
||||
if (runnerOS !== "Windows") {
|
||||
await startTailscaleDaemon(config);
|
||||
}
|
||||
// Connect to Tailscale
|
||||
|
|
@ -41220,8 +41223,8 @@ async function run() {
|
|||
try {
|
||||
const status = await getTailscaleStatus();
|
||||
core.debug(`Tailscale status: ${JSON.stringify(status)}`);
|
||||
if (status.BackendState === 'Running') {
|
||||
core.info('✅ Tailscale is running and connected!');
|
||||
if (status.BackendState === "Running") {
|
||||
core.info("✅ Tailscale is running and connected!");
|
||||
// Explicitly exit to prevent hanging
|
||||
process.exit(0);
|
||||
}
|
||||
|
|
@ -41233,7 +41236,7 @@ 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 connection completed successfully!");
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -41243,34 +41246,35 @@ async function run() {
|
|||
}
|
||||
async function getInputs() {
|
||||
return {
|
||||
version: core.getInput('version') || '1.82.0',
|
||||
resolvedVersion: '',
|
||||
arch: '',
|
||||
authKey: core.getInput('authkey') || '',
|
||||
oauthClientId: core.getInput('oauth-client-id') || '',
|
||||
oauthSecret: core.getInput('oauth-client-secret') || '',
|
||||
tags: core.getInput('tags') || '',
|
||||
hostname: core.getInput('hostname') || '',
|
||||
args: core.getInput('args') || '',
|
||||
tailscaledArgs: core.getInput('tailscaled-args') || '',
|
||||
stateDir: core.getInput('statedir') || '',
|
||||
timeout: core.getInput('timeout') || '60s', // Reduced from 2m to 60s
|
||||
retry: parseInt(core.getInput('retry') || '5'),
|
||||
useCache: core.getBooleanInput('use-cache'),
|
||||
sha256Sum: core.getInput('sha256sum') || ''
|
||||
version: core.getInput("version") || "1.82.0",
|
||||
resolvedVersion: "",
|
||||
arch: "",
|
||||
authKey: core.getInput("authkey") || "",
|
||||
oauthClientId: core.getInput("oauth-client-id") || "",
|
||||
oauthSecret: core.getInput("oauth-client-secret") || "",
|
||||
tags: core.getInput("tags") || "",
|
||||
hostname: core.getInput("hostname") || "",
|
||||
args: core.getInput("args") || "",
|
||||
tailscaledArgs: core.getInput("tailscaled-args") || "",
|
||||
stateDir: core.getInput("statedir") || "",
|
||||
timeout: core.getInput("timeout") || "60s", // Reduced from 2m to 60s
|
||||
retry: parseInt(core.getInput("retry") || "5"),
|
||||
useCache: core.getBooleanInput("use-cache"),
|
||||
sha256Sum: core.getInput("sha256sum") || "",
|
||||
};
|
||||
}
|
||||
function validateAuth(config) {
|
||||
if (!config.authKey && (!config.oauthSecret || !config.tags)) {
|
||||
throw new Error('OAuth identity empty, please provide either an auth key or OAuth secret and tags.');
|
||||
throw new Error("OAuth identity empty, please provide either an auth key or OAuth secret and tags.");
|
||||
}
|
||||
}
|
||||
async function resolveVersion(version) {
|
||||
if (version === 'latest') {
|
||||
const { stdout } = await exec.getExecOutput('curl', [
|
||||
'-H', 'user-agent:action-setup-tailscale',
|
||||
'-s',
|
||||
'https://pkgs.tailscale.com/stable/?mode=json'
|
||||
if (version === "latest") {
|
||||
const { stdout } = await exec.getExecOutput("curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-s",
|
||||
"https://pkgs.tailscale.com/stable/?mode=json",
|
||||
]);
|
||||
const response = JSON.parse(stdout);
|
||||
return response.Version;
|
||||
|
|
@ -41278,23 +41282,30 @@ async function resolveVersion(version) {
|
|||
return version;
|
||||
}
|
||||
function getTailscaleArch(runnerOS) {
|
||||
const runnerArch = process.env.RUNNER_ARCH || '';
|
||||
if (runnerOS === 'Linux') {
|
||||
const runnerArch = process.env.RUNNER_ARCH || "";
|
||||
if (runnerOS === "Linux") {
|
||||
switch (runnerArch) {
|
||||
case 'ARM64': return 'arm64';
|
||||
case 'ARM': return 'arm';
|
||||
case 'X86': return '386';
|
||||
default: return 'amd64';
|
||||
case "ARM64":
|
||||
return "arm64";
|
||||
case "ARM":
|
||||
return "arm";
|
||||
case "X86":
|
||||
return "386";
|
||||
default:
|
||||
return "amd64";
|
||||
}
|
||||
}
|
||||
else if (runnerOS === 'Windows') {
|
||||
else if (runnerOS === "Windows") {
|
||||
switch (runnerArch) {
|
||||
case 'ARM64': return 'arm64';
|
||||
case 'X86': return 'x86';
|
||||
default: return 'amd64';
|
||||
case "ARM64":
|
||||
return "arm64";
|
||||
case "X86":
|
||||
return "x86";
|
||||
default:
|
||||
return "amd64";
|
||||
}
|
||||
}
|
||||
return 'amd64';
|
||||
return "amd64";
|
||||
}
|
||||
async function installTailscale(config, runnerOS) {
|
||||
const cacheKey = generateCacheKey(config, runnerOS);
|
||||
|
|
@ -41305,7 +41316,7 @@ async function installTailscale(config, runnerOS) {
|
|||
if (cacheHit) {
|
||||
core.info(`Found Tailscale ${config.resolvedVersion} in cache: ${toolPath}`);
|
||||
// For Windows, install the cached MSI
|
||||
if (runnerOS === 'Windows') {
|
||||
if (runnerOS === "Windows") {
|
||||
await installTailscaleWindows(config, toolPath, true);
|
||||
}
|
||||
else {
|
||||
|
|
@ -41316,13 +41327,13 @@ async function installTailscale(config, runnerOS) {
|
|||
}
|
||||
}
|
||||
// Install fresh if not cached
|
||||
if (runnerOS === 'Linux') {
|
||||
if (runnerOS === "Linux") {
|
||||
await installTailscaleLinux(config, toolPath);
|
||||
}
|
||||
else if (runnerOS === 'Windows') {
|
||||
else if (runnerOS === "Windows") {
|
||||
await installTailscaleWindows(config, toolPath);
|
||||
}
|
||||
else if (runnerOS === 'macOS') {
|
||||
else if (runnerOS === "macOS") {
|
||||
await installTailscaleMacOS(config, toolPath);
|
||||
}
|
||||
// Save to cache after installation
|
||||
|
|
@ -41347,60 +41358,66 @@ async function installTailscale(config, runnerOS) {
|
|||
}
|
||||
async function calculateFileSha256(filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash('sha256');
|
||||
const hash = crypto.createHash("sha256");
|
||||
const stream = fs.createReadStream(filePath);
|
||||
stream.on('error', err => reject(err));
|
||||
stream.on('data', chunk => hash.update(chunk));
|
||||
stream.on('end', () => resolve(hash.digest('hex').toLowerCase()));
|
||||
stream.on("error", (err) => reject(err));
|
||||
stream.on("data", (chunk) => hash.update(chunk));
|
||||
stream.on("end", () => resolve(hash.digest("hex").toLowerCase()));
|
||||
});
|
||||
}
|
||||
async function installTailscaleLinux(config, toolPath) {
|
||||
// Determine if stable or unstable
|
||||
const minor = parseInt(config.resolvedVersion.split('.')[1]);
|
||||
const minor = parseInt(config.resolvedVersion.split(".")[1]);
|
||||
const isStable = minor % 2 === 0;
|
||||
const baseUrl = isStable ? 'https://pkgs.tailscale.com/stable' : 'https://pkgs.tailscale.com/unstable';
|
||||
const baseUrl = isStable
|
||||
? "https://pkgs.tailscale.com/stable"
|
||||
: "https://pkgs.tailscale.com/unstable";
|
||||
// Get SHA256 if not provided
|
||||
if (!config.sha256Sum) {
|
||||
const shaUrl = `${baseUrl}/tailscale_${config.resolvedVersion}_${config.arch}.tgz.sha256`;
|
||||
const { stdout } = await exec.getExecOutput('curl', [
|
||||
'-H', 'user-agent:action-setup-tailscale',
|
||||
'-L', shaUrl, '--fail'
|
||||
const { stdout } = await exec.getExecOutput("curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-L",
|
||||
shaUrl,
|
||||
"--fail",
|
||||
]);
|
||||
config.sha256Sum = stdout.trim();
|
||||
}
|
||||
// Download and extract
|
||||
const downloadUrl = `${baseUrl}/tailscale_${config.resolvedVersion}_${config.arch}.tgz`;
|
||||
core.info(`Downloading ${downloadUrl}`);
|
||||
const tarPath = await tc.downloadTool(downloadUrl, 'tailscale.tgz');
|
||||
const tarPath = await tc.downloadTool(downloadUrl, "tailscale.tgz");
|
||||
// Verify checksum
|
||||
const actualSha = await calculateFileSha256(tarPath);
|
||||
const expectedSha = config.sha256Sum.trim().toLowerCase();
|
||||
core.info(`Expected sha256: ${expectedSha}`);
|
||||
core.info(`Actual sha256: ${actualSha}`);
|
||||
if (actualSha !== expectedSha) {
|
||||
throw new Error('SHA256 checksum mismatch');
|
||||
throw new Error("SHA256 checksum mismatch");
|
||||
}
|
||||
// Extract to tool path
|
||||
const extractedPath = await tc.extractTar(tarPath, undefined, 'xz');
|
||||
const extractedPath = await tc.extractTar(tarPath, undefined, "xz");
|
||||
const extractedDir = path.join(extractedPath, `tailscale_${config.resolvedVersion}_${config.arch}`);
|
||||
// Create tool directory and copy binaries there for caching
|
||||
fs.mkdirSync(toolPath, { recursive: true });
|
||||
fs.copyFileSync(path.join(extractedDir, 'tailscale'), path.join(toolPath, 'tailscale'));
|
||||
fs.copyFileSync(path.join(extractedDir, 'tailscaled'), path.join(toolPath, 'tailscaled'));
|
||||
fs.copyFileSync(path.join(extractedDir, "tailscale"), path.join(toolPath, "tailscale"));
|
||||
fs.copyFileSync(path.join(extractedDir, "tailscaled"), path.join(toolPath, "tailscaled"));
|
||||
// Install binaries to /usr/local/bin
|
||||
await exec.exec('sudo', ['cp',
|
||||
path.join(toolPath, 'tailscale'),
|
||||
path.join(toolPath, 'tailscaled'),
|
||||
'/usr/local/bin'
|
||||
await exec.exec("sudo", [
|
||||
"cp",
|
||||
path.join(toolPath, "tailscale"),
|
||||
path.join(toolPath, "tailscaled"),
|
||||
"/usr/local/bin",
|
||||
]);
|
||||
// Make sure they're executable
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscale']);
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscaled']);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscale"]);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscaled"]);
|
||||
}
|
||||
async function installTailscaleWindows(config, toolPath, fromCache = false) {
|
||||
// Create tool directory
|
||||
fs.mkdirSync(toolPath, { recursive: true });
|
||||
const msiPath = path.join(toolPath, 'tailscale.msi');
|
||||
const msiPath = path.join(toolPath, "tailscale.msi");
|
||||
if (fromCache) {
|
||||
// Installing from cached MSI
|
||||
if (!fs.existsSync(msiPath)) {
|
||||
|
|
@ -41411,15 +41428,20 @@ async function installTailscaleWindows(config, toolPath, fromCache = false) {
|
|||
else {
|
||||
// Fresh download
|
||||
// Determine if stable or unstable
|
||||
const minor = parseInt(config.resolvedVersion.split('.')[1]);
|
||||
const minor = parseInt(config.resolvedVersion.split(".")[1]);
|
||||
const isStable = minor % 2 === 0;
|
||||
const baseUrl = isStable ? 'https://pkgs.tailscale.com/stable' : 'https://pkgs.tailscale.com/unstable';
|
||||
const baseUrl = isStable
|
||||
? "https://pkgs.tailscale.com/stable"
|
||||
: "https://pkgs.tailscale.com/unstable";
|
||||
// Get SHA256 if not provided
|
||||
if (!config.sha256Sum) {
|
||||
const shaUrl = `${baseUrl}/tailscale-setup-${config.resolvedVersion}-${config.arch}.msi.sha256`;
|
||||
const { stdout } = await exec.getExecOutput('curl', [
|
||||
'-H', 'user-agent:action-setup-tailscale',
|
||||
'-L', shaUrl, '--fail'
|
||||
const { stdout } = await exec.getExecOutput("curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-L",
|
||||
shaUrl,
|
||||
"--fail",
|
||||
]);
|
||||
config.sha256Sum = stdout.trim();
|
||||
}
|
||||
|
|
@ -41433,7 +41455,7 @@ async function installTailscaleWindows(config, toolPath, fromCache = false) {
|
|||
core.info(`Expected sha256: ${expectedSha}`);
|
||||
core.info(`Actual sha256: ${actualSha}`);
|
||||
if (actualSha !== expectedSha) {
|
||||
throw new Error('SHA256 checksum mismatch');
|
||||
throw new Error("SHA256 checksum mismatch");
|
||||
}
|
||||
// Keep the MSI file in toolPath for caching (don't delete it)
|
||||
// The downloadedMsiPath is in temp, but we want to keep it in toolPath
|
||||
|
|
@ -41442,63 +41464,70 @@ async function installTailscaleWindows(config, toolPath, fromCache = false) {
|
|||
}
|
||||
}
|
||||
// Install MSI (same for both fresh and cached)
|
||||
await exec.exec('msiexec.exe', [
|
||||
'/quiet',
|
||||
`/l*v`, path.join(process.env.RUNNER_TEMP || '', 'tailscale.log'),
|
||||
'/i', msiPath
|
||||
await exec.exec("msiexec.exe", [
|
||||
"/quiet",
|
||||
`/l*v`,
|
||||
path.join(process.env.RUNNER_TEMP || "", "tailscale.log"),
|
||||
"/i",
|
||||
msiPath,
|
||||
]);
|
||||
// Add to PATH
|
||||
core.addPath('C:\\Program Files\\Tailscale\\');
|
||||
core.addPath("C:\\Program Files\\Tailscale\\");
|
||||
}
|
||||
async function installTailscaleMacOS(config, toolPath) {
|
||||
core.info('Building tailscale from src on macOS...');
|
||||
core.info("Building tailscale from src on macOS...");
|
||||
// Clone the repo
|
||||
await exec.exec('git clone https://github.com/tailscale/tailscale.git tailscale');
|
||||
await exec.exec("git clone https://github.com/tailscale/tailscale.git tailscale");
|
||||
// Checkout the resolved version
|
||||
await exec.exec(`git checkout v${config.resolvedVersion}`, [], {
|
||||
cwd: 'tailscale',
|
||||
cwd: "tailscale",
|
||||
});
|
||||
// Create tool directory and copy binaries there for caching
|
||||
fs.mkdirSync(toolPath, { recursive: true });
|
||||
// Build tailscale and tailscaled into tool directory
|
||||
for (const binary of ['tailscale', 'tailscaled']) {
|
||||
for (const binary of ["tailscale", "tailscaled"]) {
|
||||
await exec.exec(`./build_dist.sh -o ${path.join(toolPath, binary)} ./cmd/${binary}`, [], {
|
||||
cwd: 'tailscale',
|
||||
cwd: "tailscale",
|
||||
env: {
|
||||
...process.env,
|
||||
'TS_USE_TOOLCHAIN': '1',
|
||||
}
|
||||
TS_USE_TOOLCHAIN: "1",
|
||||
},
|
||||
});
|
||||
}
|
||||
// Install binaries to /usr/local/bin
|
||||
await exec.exec('sudo', ['cp',
|
||||
path.join(toolPath, 'tailscale'),
|
||||
path.join(toolPath, 'tailscaled'),
|
||||
'/usr/local/bin'
|
||||
await exec.exec("sudo", [
|
||||
"cp",
|
||||
path.join(toolPath, "tailscale"),
|
||||
path.join(toolPath, "tailscaled"),
|
||||
"/usr/local/bin",
|
||||
]);
|
||||
// Make sure they're executable
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscale']);
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscaled']);
|
||||
core.info('✅ Tailscale installed successfully on macOS from source');
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscale"]);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscaled"]);
|
||||
core.info("✅ Tailscale installed successfully on macOS from source");
|
||||
}
|
||||
async function startTailscaleDaemon(config) {
|
||||
const runnerOS = process.env.RUNNER_OS || '';
|
||||
const runnerOS = process.env.RUNNER_OS || "";
|
||||
// Manual daemon start
|
||||
const stateArgs = config.stateDir ?
|
||||
[`--statedir=${config.stateDir}`] :
|
||||
['--state=mem:'];
|
||||
const stateArgs = config.stateDir
|
||||
? [`--statedir=${config.stateDir}`]
|
||||
: ["--state=mem:"];
|
||||
if (config.stateDir) {
|
||||
fs.mkdirSync(config.stateDir, { recursive: true });
|
||||
}
|
||||
const args = [
|
||||
...stateArgs,
|
||||
...config.tailscaledArgs.split(' ').filter(Boolean)
|
||||
...config.tailscaledArgs.split(" ").filter(Boolean),
|
||||
];
|
||||
core.info('Starting tailscaled daemon...');
|
||||
core.info("Starting tailscaled daemon...");
|
||||
// Start daemon in background
|
||||
const daemon = (0, child_process_1.spawn)('sudo', ['-E', 'tailscaled', ...args], {
|
||||
const daemon = (0, child_process_1.spawn)("sudo", ["-E", "tailscaled", ...args], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', fs.openSync(path.join(os.homedir(), 'tailscaled.log'), 'w')]
|
||||
stdio: [
|
||||
"ignore",
|
||||
"ignore",
|
||||
fs.openSync(path.join(os.homedir(), "tailscaled.log"), "w"),
|
||||
],
|
||||
});
|
||||
daemon.unref(); // Ensure daemon doesn't keep Node.js process alive
|
||||
// Close stdin/stdout/stderr to fully detach
|
||||
|
|
@ -41510,19 +41539,19 @@ async function startTailscaleDaemon(config) {
|
|||
daemon.stderr.destroy();
|
||||
// Poll the local API until daemon is responsive
|
||||
await waitForDaemonReady();
|
||||
core.info('✅ tailscaled daemon is up and running!');
|
||||
core.info("✅ tailscaled daemon is up and running!");
|
||||
}
|
||||
async function waitForDaemonReady() {
|
||||
const maxWaitMs = 15000; // 15 seconds
|
||||
const pollIntervalMs = 500;
|
||||
let waited = 0;
|
||||
core.info('Waiting for tailscaled daemon to become ready...');
|
||||
core.info("Waiting for tailscaled daemon to become ready...");
|
||||
while (waited < maxWaitMs) {
|
||||
try {
|
||||
const status = await getTailscaleStatus();
|
||||
// If we get any valid response from the API, the daemon is ready
|
||||
if (status) {
|
||||
core.info(`Daemon ready! Initial state: ${status.BackendState || 'Unknown'}`);
|
||||
core.info(`Daemon ready! Initial state: ${status.BackendState || "Unknown"}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -41533,17 +41562,17 @@ async function waitForDaemonReady() {
|
|||
await sleep(pollIntervalMs);
|
||||
waited += pollIntervalMs;
|
||||
}
|
||||
throw new Error('tailscaled daemon did not become ready within timeout');
|
||||
throw new Error("tailscaled daemon did not become ready within timeout");
|
||||
}
|
||||
async function connectToTailscale(config, runnerOS) {
|
||||
// Determine hostname
|
||||
let hostname = config.hostname;
|
||||
if (!hostname) {
|
||||
if (runnerOS === 'Windows') {
|
||||
if (runnerOS === "Windows") {
|
||||
hostname = `github-${process.env.COMPUTERNAME}`;
|
||||
}
|
||||
else {
|
||||
const { stdout } = await exec.getExecOutput('hostname');
|
||||
const { stdout } = await exec.getExecOutput("hostname");
|
||||
hostname = `github-${stdout.trim()}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -41560,36 +41589,36 @@ async function connectToTailscale(config, runnerOS) {
|
|||
}
|
||||
// Platform-specific args
|
||||
const platformArgs = [];
|
||||
if (runnerOS === 'Windows') {
|
||||
platformArgs.push('--unattended');
|
||||
if (runnerOS === "Windows") {
|
||||
platformArgs.push("--unattended");
|
||||
}
|
||||
// Build command
|
||||
const upArgs = [
|
||||
'up',
|
||||
"up",
|
||||
...tagsArg,
|
||||
`--authkey=${finalAuthKey}`,
|
||||
`--hostname=${hostname}`,
|
||||
'--accept-routes',
|
||||
"--accept-routes",
|
||||
...platformArgs,
|
||||
...config.args.split(' ').filter(Boolean)
|
||||
...config.args.split(" ").filter(Boolean),
|
||||
];
|
||||
// Retry logic
|
||||
for (let attempt = 1; attempt <= config.retry; attempt++) {
|
||||
try {
|
||||
core.info(`Attempt ${attempt} to bring up Tailscale...`);
|
||||
let execArgs;
|
||||
if (runnerOS === 'Windows') {
|
||||
execArgs = ['tailscale', ...upArgs];
|
||||
if (runnerOS === "Windows") {
|
||||
execArgs = ["tailscale", ...upArgs];
|
||||
}
|
||||
else {
|
||||
// Linux and macOS - use system-installed binary with sudo
|
||||
execArgs = ['sudo', '-E', 'tailscale', ...upArgs];
|
||||
execArgs = ["sudo", "-E", "tailscale", ...upArgs];
|
||||
}
|
||||
const timeoutMs = parseTimeout(config.timeout);
|
||||
core.info(`Running: ${execArgs.join(' ')} (timeout: ${timeoutMs}ms)`);
|
||||
core.info(`Running: ${execArgs.join(" ")} (timeout: ${timeoutMs}ms)`);
|
||||
await Promise.race([
|
||||
exec.exec(execArgs[0], execArgs.slice(1)),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeoutMs))
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), timeoutMs)),
|
||||
]);
|
||||
// Success
|
||||
core.info(`✅ Tailscale up command completed successfully on attempt ${attempt}`);
|
||||
|
|
@ -41611,41 +41640,49 @@ function parseTimeout(timeout) {
|
|||
if (!match)
|
||||
return 120000; // default 2 minutes
|
||||
const value = parseInt(match[1]);
|
||||
const unit = match[2] || 's';
|
||||
const unit = match[2] || "s";
|
||||
switch (unit) {
|
||||
case 's': return value * 1000;
|
||||
case 'm': return value * 60 * 1000;
|
||||
case 'h': return value * 60 * 60 * 1000;
|
||||
default: return value * 1000;
|
||||
case "s":
|
||||
return value * 1000;
|
||||
case "m":
|
||||
return value * 60 * 1000;
|
||||
case "h":
|
||||
return value * 60 * 60 * 1000;
|
||||
default:
|
||||
return value * 1000;
|
||||
}
|
||||
}
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
function generateCacheKey(config, runnerOS) {
|
||||
// Don't cache if version is latest or if caching is disabled
|
||||
if (config.resolvedVersion === 'latest' || !config.useCache) {
|
||||
if (config.resolvedVersion === "latest" || !config.useCache) {
|
||||
return undefined;
|
||||
}
|
||||
return `action-setup-tailscale/${config.resolvedVersion}/${runnerOS}-${config.arch}`;
|
||||
}
|
||||
function getToolPath(config, runnerOS) {
|
||||
const cacheDirectory = process.env.RUNNER_TOOL_CACHE || '';
|
||||
if (cacheDirectory === '') {
|
||||
core.warning('Expected RUNNER_TOOL_CACHE to be defined');
|
||||
const cacheDirectory = process.env.RUNNER_TOOL_CACHE || "";
|
||||
if (cacheDirectory === "") {
|
||||
core.warning("Expected RUNNER_TOOL_CACHE to be defined");
|
||||
}
|
||||
return path.join(cacheDirectory, 'tailscale', config.resolvedVersion, `${runnerOS}-${config.arch}`);
|
||||
return path.join(cacheDirectory, "tailscale", config.resolvedVersion, `${runnerOS}-${config.arch}`);
|
||||
}
|
||||
async function installCachedBinaries(toolPath, runnerOS) {
|
||||
if (runnerOS === 'Linux' || runnerOS === 'macOS') {
|
||||
if (runnerOS === "Linux" || runnerOS === "macOS") {
|
||||
// Copy cached binaries to /usr/local/bin
|
||||
const tailscaleBin = path.join(toolPath, 'tailscale');
|
||||
const tailscaledBin = path.join(toolPath, 'tailscaled');
|
||||
const tailscaleBin = path.join(toolPath, "tailscale");
|
||||
const tailscaledBin = path.join(toolPath, "tailscaled");
|
||||
if (fs.existsSync(tailscaleBin) && fs.existsSync(tailscaledBin)) {
|
||||
await exec.exec('sudo', ['cp', tailscaleBin, '/usr/local/bin/tailscale']);
|
||||
await exec.exec('sudo', ['cp', tailscaledBin, '/usr/local/bin/tailscaled']);
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscale']);
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscaled']);
|
||||
await exec.exec("sudo", ["cp", tailscaleBin, "/usr/local/bin/tailscale"]);
|
||||
await exec.exec("sudo", [
|
||||
"cp",
|
||||
tailscaledBin,
|
||||
"/usr/local/bin/tailscaled",
|
||||
]);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscale"]);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscaled"]);
|
||||
}
|
||||
else {
|
||||
throw new Error(`Cached binaries not found in ${toolPath}`);
|
||||
|
|
|
|||
22
dist/logout/index.js
vendored
22
dist/logout/index.js
vendored
|
|
@ -25686,34 +25686,34 @@ const core = __importStar(__nccwpck_require__(7484));
|
|||
const exec = __importStar(__nccwpck_require__(5236));
|
||||
async function logout() {
|
||||
try {
|
||||
const runnerOS = process.env.RUNNER_OS || '';
|
||||
core.info('🔄 Logging out of Tailscale...');
|
||||
const runnerOS = process.env.RUNNER_OS || "";
|
||||
core.info("🔄 Logging out of Tailscale...");
|
||||
// Check if tailscale is available first
|
||||
try {
|
||||
await exec.exec('tailscale', ['--version'], { silent: true });
|
||||
await exec.exec("tailscale", ["--version"], { silent: true });
|
||||
}
|
||||
catch (error) {
|
||||
core.info('Tailscale not found or not accessible, skipping logout');
|
||||
core.info("Tailscale not found or not accessible, skipping logout");
|
||||
return;
|
||||
}
|
||||
// Determine the correct command based on OS
|
||||
let execArgs;
|
||||
if (runnerOS === 'Windows') {
|
||||
execArgs = ['tailscale', 'logout'];
|
||||
if (runnerOS === "Windows") {
|
||||
execArgs = ["tailscale", "logout"];
|
||||
}
|
||||
else {
|
||||
// Linux and macOS - use system-installed binary with sudo
|
||||
execArgs = ['sudo', '-E', 'tailscale', 'logout'];
|
||||
execArgs = ["sudo", "-E", "tailscale", "logout"];
|
||||
}
|
||||
core.info(`Running: ${execArgs.join(' ')}`);
|
||||
core.info(`Running: ${execArgs.join(" ")}`);
|
||||
try {
|
||||
await exec.exec(execArgs[0], execArgs.slice(1));
|
||||
core.info('✅ Successfully logged out of Tailscale');
|
||||
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');
|
||||
core.info("Your ephemeral node will eventually be cleaned up by Tailscale");
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
|
|
@ -25722,7 +25722,7 @@ async function logout() {
|
|||
}
|
||||
}
|
||||
// Run the logout function
|
||||
logout().catch(error => {
|
||||
logout().catch((error) => {
|
||||
// Even if logout fails, don't fail the action
|
||||
core.warning(`Logout process failed: ${error}`);
|
||||
});
|
||||
|
|
|
|||
39
package-lock.json
generated
39
package-lock.json
generated
|
|
@ -18,6 +18,8 @@
|
|||
"devDependencies": {
|
||||
"@types/node": "^20.17.6",
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-organize-imports": "^3.2.2",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
|
|
@ -1070,6 +1072,43 @@
|
|||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-organize-imports": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz",
|
||||
"integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@volar/vue-language-plugin-pug": "^1.0.4",
|
||||
"@volar/vue-typescript": "^1.0.4",
|
||||
"prettier": ">=2.0",
|
||||
"typescript": ">=2.9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@volar/vue-language-plugin-pug": {
|
||||
"optional": true
|
||||
},
|
||||
"@volar/vue-typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@
|
|||
"devDependencies": {
|
||||
"@types/node": "^20.17.6",
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.8.3",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-organize-imports": "^3.2.2"
|
||||
},
|
||||
"overrides": {
|
||||
"glob": "^10.3.10",
|
||||
|
|
|
|||
|
|
@ -1,39 +1,40 @@
|
|||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as os from 'os';
|
||||
import * as core from "@actions/core";
|
||||
import * as exec from "@actions/exec";
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
try {
|
||||
const runnerOS = process.env.RUNNER_OS || '';
|
||||
|
||||
core.info('🔄 Logging out of Tailscale...');
|
||||
|
||||
const runnerOS = process.env.RUNNER_OS || "";
|
||||
|
||||
core.info("🔄 Logging out of Tailscale...");
|
||||
|
||||
// Check if tailscale is available first
|
||||
try {
|
||||
await exec.exec('tailscale', ['--version'], { silent: true });
|
||||
await exec.exec("tailscale", ["--version"], { silent: true });
|
||||
} catch (error) {
|
||||
core.info('Tailscale not found or not accessible, skipping logout');
|
||||
core.info("Tailscale not found or not accessible, skipping logout");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Determine the correct command based on OS
|
||||
let execArgs: string[];
|
||||
if (runnerOS === 'Windows') {
|
||||
execArgs = ['tailscale', 'logout'];
|
||||
if (runnerOS === "Windows") {
|
||||
execArgs = ["tailscale", "logout"];
|
||||
} else {
|
||||
// Linux and macOS - use system-installed binary with sudo
|
||||
execArgs = ['sudo', '-E', 'tailscale', 'logout'];
|
||||
execArgs = ["sudo", "-E", "tailscale", "logout"];
|
||||
}
|
||||
|
||||
core.info(`Running: ${execArgs.join(' ')}`);
|
||||
|
||||
|
||||
core.info(`Running: ${execArgs.join(" ")}`);
|
||||
|
||||
try {
|
||||
await exec.exec(execArgs[0], execArgs.slice(1));
|
||||
core.info('✅ Successfully logged out of Tailscale');
|
||||
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');
|
||||
core.info(
|
||||
"Your ephemeral node will eventually be cleaned up by Tailscale"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// Don't fail the action for post-cleanup issues
|
||||
|
|
@ -42,7 +43,7 @@ async function logout(): Promise<void> {
|
|||
}
|
||||
|
||||
// Run the logout function
|
||||
logout().catch(error => {
|
||||
logout().catch((error) => {
|
||||
// Even if logout fails, don't fail the action
|
||||
core.warning(`Logout process failed: ${error}`);
|
||||
});
|
||||
|
|
|
|||
478
src/main.ts
478
src/main.ts
|
|
@ -1,13 +1,13 @@
|
|||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as cache from '@actions/cache';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { spawn } from 'child_process';
|
||||
import * as crypto from 'crypto';
|
||||
import * as http from 'http';
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
import * as exec from "@actions/exec";
|
||||
import * as tc from "@actions/tool-cache";
|
||||
import { spawn } from "child_process";
|
||||
import * as crypto from "crypto";
|
||||
import * as fs from "fs";
|
||||
import * as http from "http";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
|
||||
interface TailscaleConfig {
|
||||
version: string;
|
||||
|
|
@ -30,25 +30,28 @@ interface TailscaleConfig {
|
|||
// Cross-platform Tailscale local API status check
|
||||
async function getTailscaleStatus(): Promise<any> {
|
||||
const platform = os.platform();
|
||||
|
||||
if (platform === 'win32') {
|
||||
|
||||
if (platform === "win32") {
|
||||
// Windows: use tailscale status command
|
||||
const { stdout } = await exec.getExecOutput('tailscale', ['status', '--json']);
|
||||
const { stdout } = await exec.getExecOutput("tailscale", [
|
||||
"status",
|
||||
"--json",
|
||||
]);
|
||||
return JSON.parse(stdout);
|
||||
} else if (platform === 'darwin') {
|
||||
} else if (platform === "darwin") {
|
||||
// macOS: use /var/run/tailscaled.socket
|
||||
return new Promise((resolve, reject) => {
|
||||
const options: http.RequestOptions = {
|
||||
socketPath: '/var/run/tailscaled.socket',
|
||||
path: '/localapi/v0/status',
|
||||
method: 'GET',
|
||||
headers: { Host: 'local-tailscaled.sock' },
|
||||
socketPath: "/var/run/tailscaled.socket",
|
||||
path: "/localapi/v0/status",
|
||||
method: "GET",
|
||||
headers: { Host: "local-tailscaled.sock" },
|
||||
};
|
||||
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => (data += chunk));
|
||||
res.on('end', () => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => (data += chunk));
|
||||
res.on("end", () => {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (e) {
|
||||
|
|
@ -56,30 +59,30 @@ async function getTailscaleStatus(): Promise<any> {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Set timeout to prevent hanging
|
||||
req.setTimeout(5000, () => {
|
||||
req.destroy();
|
||||
reject(new Error('Request timeout'));
|
||||
reject(new Error("Request timeout"));
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
});
|
||||
} else {
|
||||
// Linux: use Unix socket
|
||||
return new Promise((resolve, reject) => {
|
||||
const options: http.RequestOptions = {
|
||||
socketPath: '/run/tailscale/tailscaled.sock',
|
||||
path: '/localapi/v0/status',
|
||||
method: 'GET',
|
||||
headers: { Host: 'local-tailscaled.sock' },
|
||||
socketPath: "/run/tailscale/tailscaled.sock",
|
||||
path: "/localapi/v0/status",
|
||||
method: "GET",
|
||||
headers: { Host: "local-tailscaled.sock" },
|
||||
};
|
||||
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => (data += chunk));
|
||||
res.on('end', () => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => (data += chunk));
|
||||
res.on("end", () => {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (e) {
|
||||
|
|
@ -87,14 +90,14 @@ async function getTailscaleStatus(): Promise<any> {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Set timeout to prevent hanging
|
||||
req.setTimeout(5000, () => {
|
||||
req.destroy();
|
||||
reject(new Error('Request timeout'));
|
||||
reject(new Error("Request timeout"));
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
|
@ -103,9 +106,9 @@ async function getTailscaleStatus(): Promise<any> {
|
|||
async function run(): Promise<void> {
|
||||
try {
|
||||
// Validate runner OS
|
||||
const runnerOS = process.env.RUNNER_OS || '';
|
||||
if (!['Linux', 'Windows', 'macOS'].includes(runnerOS)) {
|
||||
throw new Error('Support Linux, Windows, and macOS Only');
|
||||
const runnerOS = process.env.RUNNER_OS || "";
|
||||
if (!["Linux", "Windows", "macOS"].includes(runnerOS)) {
|
||||
throw new Error("Support Linux, Windows, and macOS Only");
|
||||
}
|
||||
|
||||
// Get and validate inputs
|
||||
|
|
@ -125,7 +128,7 @@ async function run(): Promise<void> {
|
|||
await installTailscale(config, runnerOS);
|
||||
|
||||
// Start daemon (non-Windows only)
|
||||
if (runnerOS !== 'Windows') {
|
||||
if (runnerOS !== "Windows") {
|
||||
await startTailscaleDaemon(config);
|
||||
}
|
||||
|
||||
|
|
@ -136,8 +139,8 @@ async function run(): Promise<void> {
|
|||
try {
|
||||
const status = await getTailscaleStatus();
|
||||
core.debug(`Tailscale status: ${JSON.stringify(status)}`);
|
||||
if (status.BackendState === 'Running') {
|
||||
core.info('✅ Tailscale is running and connected!');
|
||||
if (status.BackendState === "Running") {
|
||||
core.info("✅ Tailscale is running and connected!");
|
||||
// Explicitly exit to prevent hanging
|
||||
process.exit(0);
|
||||
} else {
|
||||
|
|
@ -147,7 +150,7 @@ 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 connection completed successfully!");
|
||||
process.exit(0);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -157,36 +160,39 @@ async function run(): Promise<void> {
|
|||
|
||||
async function getInputs(): Promise<TailscaleConfig> {
|
||||
return {
|
||||
version: core.getInput('version') || '1.82.0',
|
||||
resolvedVersion: '',
|
||||
arch: '',
|
||||
authKey: core.getInput('authkey') || '',
|
||||
oauthClientId: core.getInput('oauth-client-id') || '',
|
||||
oauthSecret: core.getInput('oauth-client-secret') || '',
|
||||
tags: core.getInput('tags') || '',
|
||||
hostname: core.getInput('hostname') || '',
|
||||
args: core.getInput('args') || '',
|
||||
tailscaledArgs: core.getInput('tailscaled-args') || '',
|
||||
stateDir: core.getInput('statedir') || '',
|
||||
timeout: core.getInput('timeout') || '60s', // Reduced from 2m to 60s
|
||||
retry: parseInt(core.getInput('retry') || '5'),
|
||||
useCache: core.getBooleanInput('use-cache'),
|
||||
sha256Sum: core.getInput('sha256sum') || ''
|
||||
version: core.getInput("version") || "1.82.0",
|
||||
resolvedVersion: "",
|
||||
arch: "",
|
||||
authKey: core.getInput("authkey") || "",
|
||||
oauthClientId: core.getInput("oauth-client-id") || "",
|
||||
oauthSecret: core.getInput("oauth-client-secret") || "",
|
||||
tags: core.getInput("tags") || "",
|
||||
hostname: core.getInput("hostname") || "",
|
||||
args: core.getInput("args") || "",
|
||||
tailscaledArgs: core.getInput("tailscaled-args") || "",
|
||||
stateDir: core.getInput("statedir") || "",
|
||||
timeout: core.getInput("timeout") || "60s", // Reduced from 2m to 60s
|
||||
retry: parseInt(core.getInput("retry") || "5"),
|
||||
useCache: core.getBooleanInput("use-cache"),
|
||||
sha256Sum: core.getInput("sha256sum") || "",
|
||||
};
|
||||
}
|
||||
|
||||
function validateAuth(config: TailscaleConfig): void {
|
||||
if (!config.authKey && (!config.oauthSecret || !config.tags)) {
|
||||
throw new Error('OAuth identity empty, please provide either an auth key or OAuth secret and tags.');
|
||||
throw new Error(
|
||||
"OAuth identity empty, please provide either an auth key or OAuth secret and tags."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveVersion(version: string): Promise<string> {
|
||||
if (version === 'latest') {
|
||||
const { stdout } = await exec.getExecOutput('curl', [
|
||||
'-H', 'user-agent:action-setup-tailscale',
|
||||
'-s',
|
||||
'https://pkgs.tailscale.com/stable/?mode=json'
|
||||
if (version === "latest") {
|
||||
const { stdout } = await exec.getExecOutput("curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-s",
|
||||
"https://pkgs.tailscale.com/stable/?mode=json",
|
||||
]);
|
||||
const response = JSON.parse(stdout);
|
||||
return response.Version;
|
||||
|
|
@ -195,26 +201,36 @@ async function resolveVersion(version: string): Promise<string> {
|
|||
}
|
||||
|
||||
function getTailscaleArch(runnerOS: string): string {
|
||||
const runnerArch = process.env.RUNNER_ARCH || '';
|
||||
const runnerArch = process.env.RUNNER_ARCH || "";
|
||||
|
||||
if (runnerOS === 'Linux') {
|
||||
if (runnerOS === "Linux") {
|
||||
switch (runnerArch) {
|
||||
case 'ARM64': return 'arm64';
|
||||
case 'ARM': return 'arm';
|
||||
case 'X86': return '386';
|
||||
default: return 'amd64';
|
||||
case "ARM64":
|
||||
return "arm64";
|
||||
case "ARM":
|
||||
return "arm";
|
||||
case "X86":
|
||||
return "386";
|
||||
default:
|
||||
return "amd64";
|
||||
}
|
||||
} else if (runnerOS === 'Windows') {
|
||||
} else if (runnerOS === "Windows") {
|
||||
switch (runnerArch) {
|
||||
case 'ARM64': return 'arm64';
|
||||
case 'X86': return 'x86';
|
||||
default: return 'amd64';
|
||||
case "ARM64":
|
||||
return "arm64";
|
||||
case "X86":
|
||||
return "x86";
|
||||
default:
|
||||
return "amd64";
|
||||
}
|
||||
}
|
||||
return 'amd64';
|
||||
return "amd64";
|
||||
}
|
||||
|
||||
async function installTailscale(config: TailscaleConfig, runnerOS: string): Promise<void> {
|
||||
async function installTailscale(
|
||||
config: TailscaleConfig,
|
||||
runnerOS: string
|
||||
): Promise<void> {
|
||||
const cacheKey = generateCacheKey(config, runnerOS);
|
||||
const toolPath = getToolPath(config, runnerOS);
|
||||
|
||||
|
|
@ -222,10 +238,12 @@ async function installTailscale(config: TailscaleConfig, runnerOS: string): Prom
|
|||
if (config.useCache && cacheKey) {
|
||||
const cacheHit = await cache.restoreCache([toolPath], cacheKey);
|
||||
if (cacheHit) {
|
||||
core.info(`Found Tailscale ${config.resolvedVersion} in cache: ${toolPath}`);
|
||||
core.info(
|
||||
`Found Tailscale ${config.resolvedVersion} in cache: ${toolPath}`
|
||||
);
|
||||
|
||||
// For Windows, install the cached MSI
|
||||
if (runnerOS === 'Windows') {
|
||||
if (runnerOS === "Windows") {
|
||||
await installTailscaleWindows(config, toolPath, true);
|
||||
} else {
|
||||
// For Linux/macOS, copy binaries to /usr/local/bin
|
||||
|
|
@ -236,11 +254,11 @@ async function installTailscale(config: TailscaleConfig, runnerOS: string): Prom
|
|||
}
|
||||
|
||||
// Install fresh if not cached
|
||||
if (runnerOS === 'Linux') {
|
||||
if (runnerOS === "Linux") {
|
||||
await installTailscaleLinux(config, toolPath);
|
||||
} else if (runnerOS === 'Windows') {
|
||||
} else if (runnerOS === "Windows") {
|
||||
await installTailscaleWindows(config, toolPath);
|
||||
} else if (runnerOS === 'macOS') {
|
||||
} else if (runnerOS === "macOS") {
|
||||
await installTailscaleMacOS(config, toolPath);
|
||||
}
|
||||
|
||||
|
|
@ -264,26 +282,34 @@ async function installTailscale(config: TailscaleConfig, runnerOS: string): Prom
|
|||
|
||||
async function calculateFileSha256(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash('sha256');
|
||||
const hash = crypto.createHash("sha256");
|
||||
const stream = fs.createReadStream(filePath);
|
||||
stream.on('error', err => reject(err));
|
||||
stream.on('data', chunk => hash.update(chunk));
|
||||
stream.on('end', () => resolve(hash.digest('hex').toLowerCase()));
|
||||
stream.on("error", (err) => reject(err));
|
||||
stream.on("data", (chunk) => hash.update(chunk));
|
||||
stream.on("end", () => resolve(hash.digest("hex").toLowerCase()));
|
||||
});
|
||||
}
|
||||
|
||||
async function installTailscaleLinux(config: TailscaleConfig, toolPath: string): Promise<void> {
|
||||
async function installTailscaleLinux(
|
||||
config: TailscaleConfig,
|
||||
toolPath: string
|
||||
): Promise<void> {
|
||||
// Determine if stable or unstable
|
||||
const minor = parseInt(config.resolvedVersion.split('.')[1]);
|
||||
const minor = parseInt(config.resolvedVersion.split(".")[1]);
|
||||
const isStable = minor % 2 === 0;
|
||||
const baseUrl = isStable ? 'https://pkgs.tailscale.com/stable' : 'https://pkgs.tailscale.com/unstable';
|
||||
const baseUrl = isStable
|
||||
? "https://pkgs.tailscale.com/stable"
|
||||
: "https://pkgs.tailscale.com/unstable";
|
||||
|
||||
// Get SHA256 if not provided
|
||||
if (!config.sha256Sum) {
|
||||
const shaUrl = `${baseUrl}/tailscale_${config.resolvedVersion}_${config.arch}.tgz.sha256`;
|
||||
const { stdout } = await exec.getExecOutput('curl', [
|
||||
'-H', 'user-agent:action-setup-tailscale',
|
||||
'-L', shaUrl, '--fail'
|
||||
const { stdout } = await exec.getExecOutput("curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-L",
|
||||
shaUrl,
|
||||
"--fail",
|
||||
]);
|
||||
config.sha256Sum = stdout.trim();
|
||||
}
|
||||
|
|
@ -292,7 +318,7 @@ async function installTailscaleLinux(config: TailscaleConfig, toolPath: string):
|
|||
const downloadUrl = `${baseUrl}/tailscale_${config.resolvedVersion}_${config.arch}.tgz`;
|
||||
core.info(`Downloading ${downloadUrl}`);
|
||||
|
||||
const tarPath = await tc.downloadTool(downloadUrl, 'tailscale.tgz');
|
||||
const tarPath = await tc.downloadTool(downloadUrl, "tailscale.tgz");
|
||||
|
||||
// Verify checksum
|
||||
const actualSha = await calculateFileSha256(tarPath);
|
||||
|
|
@ -300,34 +326,48 @@ async function installTailscaleLinux(config: TailscaleConfig, toolPath: string):
|
|||
core.info(`Expected sha256: ${expectedSha}`);
|
||||
core.info(`Actual sha256: ${actualSha}`);
|
||||
if (actualSha !== expectedSha) {
|
||||
throw new Error('SHA256 checksum mismatch');
|
||||
throw new Error("SHA256 checksum mismatch");
|
||||
}
|
||||
|
||||
// Extract to tool path
|
||||
const extractedPath = await tc.extractTar(tarPath, undefined, 'xz');
|
||||
const extractedDir = path.join(extractedPath, `tailscale_${config.resolvedVersion}_${config.arch}`);
|
||||
const extractedPath = await tc.extractTar(tarPath, undefined, "xz");
|
||||
const extractedDir = path.join(
|
||||
extractedPath,
|
||||
`tailscale_${config.resolvedVersion}_${config.arch}`
|
||||
);
|
||||
|
||||
// Create tool directory and copy binaries there for caching
|
||||
fs.mkdirSync(toolPath, { recursive: true });
|
||||
fs.copyFileSync(path.join(extractedDir, 'tailscale'), path.join(toolPath, 'tailscale'));
|
||||
fs.copyFileSync(path.join(extractedDir, 'tailscaled'), path.join(toolPath, 'tailscaled'));
|
||||
fs.copyFileSync(
|
||||
path.join(extractedDir, "tailscale"),
|
||||
path.join(toolPath, "tailscale")
|
||||
);
|
||||
fs.copyFileSync(
|
||||
path.join(extractedDir, "tailscaled"),
|
||||
path.join(toolPath, "tailscaled")
|
||||
);
|
||||
|
||||
// Install binaries to /usr/local/bin
|
||||
await exec.exec('sudo', ['cp',
|
||||
path.join(toolPath, 'tailscale'),
|
||||
path.join(toolPath, 'tailscaled'),
|
||||
'/usr/local/bin'
|
||||
await exec.exec("sudo", [
|
||||
"cp",
|
||||
path.join(toolPath, "tailscale"),
|
||||
path.join(toolPath, "tailscaled"),
|
||||
"/usr/local/bin",
|
||||
]);
|
||||
|
||||
// Make sure they're executable
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscale']);
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscaled']);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscale"]);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscaled"]);
|
||||
}
|
||||
|
||||
async function installTailscaleWindows(config: TailscaleConfig, toolPath: string, fromCache: boolean = false): Promise<void> {
|
||||
async function installTailscaleWindows(
|
||||
config: TailscaleConfig,
|
||||
toolPath: string,
|
||||
fromCache: boolean = false
|
||||
): Promise<void> {
|
||||
// Create tool directory
|
||||
fs.mkdirSync(toolPath, { recursive: true });
|
||||
const msiPath = path.join(toolPath, 'tailscale.msi');
|
||||
const msiPath = path.join(toolPath, "tailscale.msi");
|
||||
|
||||
if (fromCache) {
|
||||
// Installing from cached MSI
|
||||
|
|
@ -338,16 +378,21 @@ async function installTailscaleWindows(config: TailscaleConfig, toolPath: string
|
|||
} else {
|
||||
// Fresh download
|
||||
// Determine if stable or unstable
|
||||
const minor = parseInt(config.resolvedVersion.split('.')[1]);
|
||||
const minor = parseInt(config.resolvedVersion.split(".")[1]);
|
||||
const isStable = minor % 2 === 0;
|
||||
const baseUrl = isStable ? 'https://pkgs.tailscale.com/stable' : 'https://pkgs.tailscale.com/unstable';
|
||||
const baseUrl = isStable
|
||||
? "https://pkgs.tailscale.com/stable"
|
||||
: "https://pkgs.tailscale.com/unstable";
|
||||
|
||||
// Get SHA256 if not provided
|
||||
if (!config.sha256Sum) {
|
||||
const shaUrl = `${baseUrl}/tailscale-setup-${config.resolvedVersion}-${config.arch}.msi.sha256`;
|
||||
const { stdout } = await exec.getExecOutput('curl', [
|
||||
'-H', 'user-agent:action-setup-tailscale',
|
||||
'-L', shaUrl, '--fail'
|
||||
const { stdout } = await exec.getExecOutput("curl", [
|
||||
"-H",
|
||||
"user-agent:action-setup-tailscale",
|
||||
"-L",
|
||||
shaUrl,
|
||||
"--fail",
|
||||
]);
|
||||
config.sha256Sum = stdout.trim();
|
||||
}
|
||||
|
|
@ -364,7 +409,7 @@ async function installTailscaleWindows(config: TailscaleConfig, toolPath: string
|
|||
core.info(`Expected sha256: ${expectedSha}`);
|
||||
core.info(`Actual sha256: ${actualSha}`);
|
||||
if (actualSha !== expectedSha) {
|
||||
throw new Error('SHA256 checksum mismatch');
|
||||
throw new Error("SHA256 checksum mismatch");
|
||||
}
|
||||
|
||||
// Keep the MSI file in toolPath for caching (don't delete it)
|
||||
|
|
@ -375,62 +420,74 @@ async function installTailscaleWindows(config: TailscaleConfig, toolPath: string
|
|||
}
|
||||
|
||||
// Install MSI (same for both fresh and cached)
|
||||
await exec.exec('msiexec.exe', [
|
||||
'/quiet',
|
||||
`/l*v`, path.join(process.env.RUNNER_TEMP || '', 'tailscale.log'),
|
||||
'/i', msiPath
|
||||
await exec.exec("msiexec.exe", [
|
||||
"/quiet",
|
||||
`/l*v`,
|
||||
path.join(process.env.RUNNER_TEMP || "", "tailscale.log"),
|
||||
"/i",
|
||||
msiPath,
|
||||
]);
|
||||
|
||||
// Add to PATH
|
||||
core.addPath('C:\\Program Files\\Tailscale\\');
|
||||
core.addPath("C:\\Program Files\\Tailscale\\");
|
||||
}
|
||||
|
||||
async function installTailscaleMacOS(config: TailscaleConfig, toolPath: string): Promise<void> {
|
||||
core.info('Building tailscale from src on macOS...');
|
||||
|
||||
async function installTailscaleMacOS(
|
||||
config: TailscaleConfig,
|
||||
toolPath: string
|
||||
): Promise<void> {
|
||||
core.info("Building tailscale from src on macOS...");
|
||||
|
||||
// Clone the repo
|
||||
await exec.exec('git clone https://github.com/tailscale/tailscale.git tailscale');
|
||||
|
||||
await exec.exec(
|
||||
"git clone https://github.com/tailscale/tailscale.git tailscale"
|
||||
);
|
||||
|
||||
// Checkout the resolved version
|
||||
await exec.exec(`git checkout v${config.resolvedVersion}`, [], {
|
||||
cwd: 'tailscale',
|
||||
})
|
||||
cwd: "tailscale",
|
||||
});
|
||||
|
||||
// Create tool directory and copy binaries there for caching
|
||||
fs.mkdirSync(toolPath, { recursive: true });
|
||||
|
||||
|
||||
// Build tailscale and tailscaled into tool directory
|
||||
for (const binary of ['tailscale', 'tailscaled']) {
|
||||
await exec.exec(`./build_dist.sh -o ${path.join(toolPath, binary)} ./cmd/${binary}`, [], {
|
||||
cwd: 'tailscale',
|
||||
env: {
|
||||
...process.env,
|
||||
'TS_USE_TOOLCHAIN': '1',
|
||||
for (const binary of ["tailscale", "tailscaled"]) {
|
||||
await exec.exec(
|
||||
`./build_dist.sh -o ${path.join(toolPath, binary)} ./cmd/${binary}`,
|
||||
[],
|
||||
{
|
||||
cwd: "tailscale",
|
||||
env: {
|
||||
...process.env,
|
||||
TS_USE_TOOLCHAIN: "1",
|
||||
},
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Install binaries to /usr/local/bin
|
||||
await exec.exec('sudo', ['cp',
|
||||
path.join(toolPath, 'tailscale'),
|
||||
path.join(toolPath, 'tailscaled'),
|
||||
'/usr/local/bin'
|
||||
await exec.exec("sudo", [
|
||||
"cp",
|
||||
path.join(toolPath, "tailscale"),
|
||||
path.join(toolPath, "tailscaled"),
|
||||
"/usr/local/bin",
|
||||
]);
|
||||
|
||||
// Make sure they're executable
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscale']);
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscaled']);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscale"]);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscaled"]);
|
||||
|
||||
core.info('✅ Tailscale installed successfully on macOS from source');
|
||||
core.info("✅ Tailscale installed successfully on macOS from source");
|
||||
}
|
||||
|
||||
async function startTailscaleDaemon(config: TailscaleConfig): Promise<void> {
|
||||
const runnerOS = process.env.RUNNER_OS || '';
|
||||
|
||||
const runnerOS = process.env.RUNNER_OS || "";
|
||||
|
||||
// Manual daemon start
|
||||
const stateArgs = config.stateDir ?
|
||||
[`--statedir=${config.stateDir}`] :
|
||||
['--state=mem:'];
|
||||
const stateArgs = config.stateDir
|
||||
? [`--statedir=${config.stateDir}`]
|
||||
: ["--state=mem:"];
|
||||
|
||||
if (config.stateDir) {
|
||||
fs.mkdirSync(config.stateDir, { recursive: true });
|
||||
|
|
@ -438,43 +495,49 @@ async function startTailscaleDaemon(config: TailscaleConfig): Promise<void> {
|
|||
|
||||
const args = [
|
||||
...stateArgs,
|
||||
...config.tailscaledArgs.split(' ').filter(Boolean)
|
||||
...config.tailscaledArgs.split(" ").filter(Boolean),
|
||||
];
|
||||
|
||||
core.info('Starting tailscaled daemon...');
|
||||
|
||||
core.info("Starting tailscaled daemon...");
|
||||
|
||||
// Start daemon in background
|
||||
const daemon = spawn('sudo', ['-E', 'tailscaled', ...args], {
|
||||
const daemon = spawn("sudo", ["-E", "tailscaled", ...args], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', fs.openSync(path.join(os.homedir(), 'tailscaled.log'), 'w')]
|
||||
stdio: [
|
||||
"ignore",
|
||||
"ignore",
|
||||
fs.openSync(path.join(os.homedir(), "tailscaled.log"), "w"),
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
daemon.unref(); // Ensure daemon doesn't keep Node.js process alive
|
||||
|
||||
|
||||
// Close stdin/stdout/stderr to fully detach
|
||||
if (daemon.stdin) daemon.stdin.end();
|
||||
if (daemon.stdout) daemon.stdout.destroy();
|
||||
if (daemon.stderr) daemon.stderr.destroy();
|
||||
|
||||
|
||||
// Poll the local API until daemon is responsive
|
||||
await waitForDaemonReady();
|
||||
|
||||
core.info('✅ tailscaled daemon is up and running!');
|
||||
|
||||
core.info("✅ tailscaled daemon is up and running!");
|
||||
}
|
||||
|
||||
async function waitForDaemonReady(): Promise<void> {
|
||||
const maxWaitMs = 15000; // 15 seconds
|
||||
const pollIntervalMs = 500;
|
||||
let waited = 0;
|
||||
|
||||
core.info('Waiting for tailscaled daemon to become ready...');
|
||||
|
||||
|
||||
core.info("Waiting for tailscaled daemon to become ready...");
|
||||
|
||||
while (waited < maxWaitMs) {
|
||||
try {
|
||||
const status = await getTailscaleStatus();
|
||||
// If we get any valid response from the API, the daemon is ready
|
||||
if (status) {
|
||||
core.info(`Daemon ready! Initial state: ${status.BackendState || 'Unknown'}`);
|
||||
core.info(
|
||||
`Daemon ready! Initial state: ${status.BackendState || "Unknown"}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -484,18 +547,21 @@ async function waitForDaemonReady(): Promise<void> {
|
|||
await sleep(pollIntervalMs);
|
||||
waited += pollIntervalMs;
|
||||
}
|
||||
|
||||
throw new Error('tailscaled daemon did not become ready within timeout');
|
||||
|
||||
throw new Error("tailscaled daemon did not become ready within timeout");
|
||||
}
|
||||
|
||||
async function connectToTailscale(config: TailscaleConfig, runnerOS: string): Promise<void> {
|
||||
async function connectToTailscale(
|
||||
config: TailscaleConfig,
|
||||
runnerOS: string
|
||||
): Promise<void> {
|
||||
// Determine hostname
|
||||
let hostname = config.hostname;
|
||||
if (!hostname) {
|
||||
if (runnerOS === 'Windows') {
|
||||
if (runnerOS === "Windows") {
|
||||
hostname = `github-${process.env.COMPUTERNAME}`;
|
||||
} else {
|
||||
const { stdout } = await exec.getExecOutput('hostname');
|
||||
const { stdout } = await exec.getExecOutput("hostname");
|
||||
hostname = `github-${stdout.trim()}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -516,19 +582,19 @@ async function connectToTailscale(config: TailscaleConfig, runnerOS: string): Pr
|
|||
|
||||
// Platform-specific args
|
||||
const platformArgs: string[] = [];
|
||||
if (runnerOS === 'Windows') {
|
||||
platformArgs.push('--unattended');
|
||||
if (runnerOS === "Windows") {
|
||||
platformArgs.push("--unattended");
|
||||
}
|
||||
|
||||
// Build command
|
||||
const upArgs = [
|
||||
'up',
|
||||
"up",
|
||||
...tagsArg,
|
||||
`--authkey=${finalAuthKey}`,
|
||||
`--hostname=${hostname}`,
|
||||
'--accept-routes',
|
||||
"--accept-routes",
|
||||
...platformArgs,
|
||||
...config.args.split(' ').filter(Boolean)
|
||||
...config.args.split(" ").filter(Boolean),
|
||||
];
|
||||
|
||||
// Retry logic
|
||||
|
|
@ -537,25 +603,27 @@ async function connectToTailscale(config: TailscaleConfig, runnerOS: string): Pr
|
|||
core.info(`Attempt ${attempt} to bring up Tailscale...`);
|
||||
|
||||
let execArgs: string[];
|
||||
if (runnerOS === 'Windows') {
|
||||
execArgs = ['tailscale', ...upArgs];
|
||||
if (runnerOS === "Windows") {
|
||||
execArgs = ["tailscale", ...upArgs];
|
||||
} else {
|
||||
// Linux and macOS - use system-installed binary with sudo
|
||||
execArgs = ['sudo', '-E', 'tailscale', ...upArgs];
|
||||
execArgs = ["sudo", "-E", "tailscale", ...upArgs];
|
||||
}
|
||||
|
||||
const timeoutMs = parseTimeout(config.timeout);
|
||||
core.info(`Running: ${execArgs.join(' ')} (timeout: ${timeoutMs}ms)`);
|
||||
core.info(`Running: ${execArgs.join(" ")} (timeout: ${timeoutMs}ms)`);
|
||||
|
||||
await Promise.race([
|
||||
exec.exec(execArgs[0], execArgs.slice(1)),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), timeoutMs)
|
||||
)
|
||||
setTimeout(() => reject(new Error("Timeout")), timeoutMs)
|
||||
),
|
||||
]);
|
||||
|
||||
// Success
|
||||
core.info(`✅ Tailscale up command completed successfully on attempt ${attempt}`);
|
||||
core.info(
|
||||
`✅ Tailscale up command completed successfully on attempt ${attempt}`
|
||||
);
|
||||
return;
|
||||
} catch (error) {
|
||||
core.warning(`Tailscale up attempt ${attempt} failed: ${error}`);
|
||||
|
|
@ -575,23 +643,30 @@ function parseTimeout(timeout: string): number {
|
|||
if (!match) return 120000; // default 2 minutes
|
||||
|
||||
const value = parseInt(match[1]);
|
||||
const unit = match[2] || 's';
|
||||
const unit = match[2] || "s";
|
||||
|
||||
switch (unit) {
|
||||
case 's': return value * 1000;
|
||||
case 'm': return value * 60 * 1000;
|
||||
case 'h': return value * 60 * 60 * 1000;
|
||||
default: return value * 1000;
|
||||
case "s":
|
||||
return value * 1000;
|
||||
case "m":
|
||||
return value * 60 * 1000;
|
||||
case "h":
|
||||
return value * 60 * 60 * 1000;
|
||||
default:
|
||||
return value * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function generateCacheKey(config: TailscaleConfig, runnerOS: string): string | undefined {
|
||||
function generateCacheKey(
|
||||
config: TailscaleConfig,
|
||||
runnerOS: string
|
||||
): string | undefined {
|
||||
// Don't cache if version is latest or if caching is disabled
|
||||
if (config.resolvedVersion === 'latest' || !config.useCache) {
|
||||
if (config.resolvedVersion === "latest" || !config.useCache) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
@ -599,34 +674,41 @@ function generateCacheKey(config: TailscaleConfig, runnerOS: string): string | u
|
|||
}
|
||||
|
||||
function getToolPath(config: TailscaleConfig, runnerOS: string): string {
|
||||
const cacheDirectory = process.env.RUNNER_TOOL_CACHE || '';
|
||||
if (cacheDirectory === '') {
|
||||
core.warning('Expected RUNNER_TOOL_CACHE to be defined');
|
||||
const cacheDirectory = process.env.RUNNER_TOOL_CACHE || "";
|
||||
if (cacheDirectory === "") {
|
||||
core.warning("Expected RUNNER_TOOL_CACHE to be defined");
|
||||
}
|
||||
|
||||
return path.join(
|
||||
cacheDirectory,
|
||||
'tailscale',
|
||||
"tailscale",
|
||||
config.resolvedVersion,
|
||||
`${runnerOS}-${config.arch}`
|
||||
);
|
||||
}
|
||||
|
||||
async function installCachedBinaries(toolPath: string, runnerOS: string): Promise<void> {
|
||||
if (runnerOS === 'Linux' || runnerOS === 'macOS') {
|
||||
async function installCachedBinaries(
|
||||
toolPath: string,
|
||||
runnerOS: string
|
||||
): Promise<void> {
|
||||
if (runnerOS === "Linux" || runnerOS === "macOS") {
|
||||
// Copy cached binaries to /usr/local/bin
|
||||
const tailscaleBin = path.join(toolPath, 'tailscale');
|
||||
const tailscaledBin = path.join(toolPath, 'tailscaled');
|
||||
const tailscaleBin = path.join(toolPath, "tailscale");
|
||||
const tailscaledBin = path.join(toolPath, "tailscaled");
|
||||
|
||||
if (fs.existsSync(tailscaleBin) && fs.existsSync(tailscaledBin)) {
|
||||
await exec.exec('sudo', ['cp', tailscaleBin, '/usr/local/bin/tailscale']);
|
||||
await exec.exec('sudo', ['cp', tailscaledBin, '/usr/local/bin/tailscaled']);
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscale']);
|
||||
await exec.exec('sudo', ['chmod', '+x', '/usr/local/bin/tailscaled']);
|
||||
await exec.exec("sudo", ["cp", tailscaleBin, "/usr/local/bin/tailscale"]);
|
||||
await exec.exec("sudo", [
|
||||
"cp",
|
||||
tailscaledBin,
|
||||
"/usr/local/bin/tailscaled",
|
||||
]);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscale"]);
|
||||
await exec.exec("sudo", ["chmod", "+x", "/usr/local/bin/tailscaled"]);
|
||||
} else {
|
||||
throw new Error(`Cached binaries not found in ${toolPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
run();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue