Skip to content

Commit d22af2d

Browse files
committed
on Linux and MacOS, stop tailscaled after CI workload finishes
Updates #205 Signed-off-by: Percy Wegmann <[email protected]>
1 parent aa60431 commit d22af2d

File tree

5 files changed

+83
-38
lines changed

5 files changed

+83
-38
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,8 @@ Although caching is generally recommended, you can disable it by passing `'false
129129
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
130130
use-cache: "false"
131131
```
132+
133+
## Usage on persistent self-hosted runners
134+
135+
When running on self-hosted Linux or MacOS runners that persist after CI jobs have finished,
136+
the GitHub Action leaves tailscale binaries installed but stops the tailscaled daemon.

dist/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41570,6 +41570,8 @@ async function startTailscaleDaemon(config) {
4157041570
fs.openSync(path.join(os.homedir(), "tailscaled.log"), "w"),
4157141571
],
4157241572
});
41573+
// Store PID for cleaning up daemon process in logout.ts.
41574+
fs.writeFileSync("tailscaled.pid", `${daemon.pid}`);
4157341575
daemon.unref(); // Ensure daemon doesn't keep Node.js process alive
4157441576
// Close stdin/stdout/stderr to fully detach
4157541577
if (daemon.stdin)

dist/logout/index.js

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25686,6 +25686,8 @@ var __importStar = (this && this.__importStar) || (function () {
2568625686
Object.defineProperty(exports, "__esModule", ({ value: true }));
2568725687
const core = __importStar(__nccwpck_require__(7484));
2568825688
const exec = __importStar(__nccwpck_require__(5236));
25689+
const fs = __importStar(__nccwpck_require__(9896));
25690+
const runnerWindows = "Windows";
2568925691
async function logout() {
2569025692
try {
2569125693
const runnerOS = process.env.RUNNER_OS || "";
@@ -25703,29 +25705,43 @@ async function logout() {
2570325705
// Check if tailscale is available first
2570425706
try {
2570525707
await exec.exec("tailscale", ["--version"], { silent: true });
25708+
// Determine the correct command based on OS
25709+
let execArgs;
25710+
if (runnerOS === runnerWindows) {
25711+
execArgs = ["tailscale", "logout"];
25712+
}
25713+
else {
25714+
// Linux and macOS - use system-installed binary with sudo
25715+
execArgs = ["sudo", "-E", "tailscale", "logout"];
25716+
}
25717+
core.info(`Running: ${execArgs.join(" ")}`);
25718+
try {
25719+
await exec.exec(execArgs[0], execArgs.slice(1));
25720+
core.info("✅ Successfully logged out of Tailscale");
25721+
}
25722+
catch (error) {
25723+
// Don't fail the action if logout fails - it's just cleanup
25724+
core.warning(`Failed to logout from Tailscale: ${error}`);
25725+
core.info("Your ephemeral node will eventually be cleaned up by Tailscale");
25726+
}
2570625727
}
2570725728
catch (error) {
2570825729
core.info("Tailscale not found or not accessible, skipping logout");
2570925730
return;
2571025731
}
25711-
// Determine the correct command based on OS
25712-
let execArgs;
25713-
if (runnerOS === "Windows") {
25714-
execArgs = ["tailscale", "logout"];
25715-
}
25716-
else {
25717-
// Linux and macOS - use system-installed binary with sudo
25718-
execArgs = ["sudo", "-E", "tailscale", "logout"];
25719-
}
25720-
core.info(`Running: ${execArgs.join(" ")}`);
25721-
try {
25722-
await exec.exec(execArgs[0], execArgs.slice(1));
25723-
core.info("✅ Successfully logged out of Tailscale");
25724-
}
25725-
catch (error) {
25726-
// Don't fail the action if logout fails - it's just cleanup
25727-
core.warning(`Failed to logout from Tailscale: ${error}`);
25728-
core.info("Your ephemeral node will eventually be cleaned up by Tailscale");
25732+
if (runnerOS !== runnerWindows) {
25733+
try {
25734+
core.info("Stopping tailscaled");
25735+
const pid = fs.readFileSync("tailscaled.pid").toString();
25736+
if (pid === "") {
25737+
throw new Error("pid file empty");
25738+
}
25739+
await exec.exec("sudo", ["kill", "-9", pid]);
25740+
core.info("✅ Stopped tailscaled");
25741+
}
25742+
catch (error) {
25743+
core.warning(`Failed to stop tailscaled: ${error}`);
25744+
}
2572925745
}
2573025746
}
2573125747
catch (error) {

src/logout/logout.ts

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
import * as core from "@actions/core";
55
import * as exec from "@actions/exec";
6+
import * as fs from "fs";
7+
8+
const runnerWindows = "Windows";
69

710
async function logout(): Promise<void> {
811
try {
@@ -24,31 +27,47 @@ async function logout(): Promise<void> {
2427
// Check if tailscale is available first
2528
try {
2629
await exec.exec("tailscale", ["--version"], { silent: true });
30+
31+
// Determine the correct command based on OS
32+
let execArgs: string[];
33+
if (runnerOS === runnerWindows) {
34+
execArgs = ["tailscale", "logout"];
35+
} else {
36+
// Linux and macOS - use system-installed binary with sudo
37+
execArgs = ["sudo", "-E", "tailscale", "logout"];
38+
}
39+
40+
core.info(`Running: ${execArgs.join(" ")}`);
41+
42+
try {
43+
await exec.exec(execArgs[0], execArgs.slice(1));
44+
core.info("✅ Successfully logged out of Tailscale");
45+
} catch (error) {
46+
// Don't fail the action if logout fails - it's just cleanup
47+
core.warning(`Failed to logout from Tailscale: ${error}`);
48+
core.info(
49+
"Your ephemeral node will eventually be cleaned up by Tailscale"
50+
);
51+
}
2752
} catch (error) {
2853
core.info("Tailscale not found or not accessible, skipping logout");
2954
return;
3055
}
3156

32-
// Determine the correct command based on OS
33-
let execArgs: string[];
34-
if (runnerOS === "Windows") {
35-
execArgs = ["tailscale", "logout"];
36-
} else {
37-
// Linux and macOS - use system-installed binary with sudo
38-
execArgs = ["sudo", "-E", "tailscale", "logout"];
39-
}
40-
41-
core.info(`Running: ${execArgs.join(" ")}`);
42-
43-
try {
44-
await exec.exec(execArgs[0], execArgs.slice(1));
45-
core.info("✅ Successfully logged out of Tailscale");
46-
} catch (error) {
47-
// Don't fail the action if logout fails - it's just cleanup
48-
core.warning(`Failed to logout from Tailscale: ${error}`);
49-
core.info(
50-
"Your ephemeral node will eventually be cleaned up by Tailscale"
51-
);
57+
if (runnerOS !== runnerWindows) {
58+
try {
59+
core.info("Stopping tailscaled");
60+
const pid = fs.readFileSync("tailscaled.pid").toString();
61+
if (pid === "") {
62+
throw new Error("pid file empty");
63+
}
64+
exec.exec("sudo", ["ps", "-ef"])
65+
await exec.exec("sudo", ["kill", "-9", pid]);
66+
core.info("✅ Stopped tailscaled");
67+
exec.exec("sudo", ["ps", "-ef"])
68+
} catch (error) {
69+
core.warning(`Failed to stop tailscaled: ${error}`);
70+
}
5271
}
5372
} catch (error) {
5473
// Don't fail the action for post-cleanup issues

src/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,9 @@ async function startTailscaleDaemon(config: TailscaleConfig): Promise<void> {
593593
],
594594
});
595595

596+
// Store PID for cleaning up daemon process in logout.ts.
597+
fs.writeFileSync("tailscaled.pid", `${daemon.pid}`);
598+
596599
daemon.unref(); // Ensure daemon doesn't keep Node.js process alive
597600

598601
// Close stdin/stdout/stderr to fully detach

0 commit comments

Comments
 (0)