diff --git a/dist/index.js b/dist/index.js index 984f291..f5a0829 100644 --- a/dist/index.js +++ b/dist/index.js @@ -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}`); diff --git a/dist/logout/index.js b/dist/logout/index.js index 56289e6..c160f77 100644 --- a/dist/logout/index.js +++ b/dist/logout/index.js @@ -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}`); }); diff --git a/package-lock.json b/package-lock.json index c68038a..7f2034b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 97b9b25..3bd63e7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/logout/logout.ts b/src/logout/logout.ts index bc8f4bb..47195ac 100644 --- a/src/logout/logout.ts +++ b/src/logout/logout.ts @@ -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 { 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 { } // 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}`); }); diff --git a/src/main.ts b/src/main.ts index bbbb1f0..10e9519 100644 --- a/src/main.ts +++ b/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 { 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 { } }); }); - + // 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 { } }); }); - + // 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 { async function run(): Promise { 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 { 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 { 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 { } 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 { async function getInputs(): Promise { 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 { - 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 { } 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 { +async function installTailscale( + config: TailscaleConfig, + runnerOS: string +): Promise { 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 { 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 { +async function installTailscaleLinux( + config: TailscaleConfig, + toolPath: string +): Promise { // 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 { +async function installTailscaleWindows( + config: TailscaleConfig, + toolPath: string, + fromCache: boolean = false +): Promise { // 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 { - core.info('Building tailscale from src on macOS...'); - +async function installTailscaleMacOS( + config: TailscaleConfig, + toolPath: string +): Promise { + 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 { - 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 { 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 { 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 { 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 { +async function connectToTailscale( + config: TailscaleConfig, + runnerOS: string +): Promise { // 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((_, 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 { - 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 { - if (runnerOS === 'Linux' || runnerOS === 'macOS') { +async function installCachedBinaries( + toolPath: string, + runnerOS: string +): Promise { + 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(); \ No newline at end of file +run();