Skip to content

Commit 2ddbe90

Browse files
committed
stop tailscale service after CI workload finishes
Updates #205 Signed-off-by: Percy Wegmann <percy@tailscale.com>
1 parent aa60431 commit 2ddbe90

5 files changed

Lines changed: 93 additions & 35 deletions

File tree

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 runners that persist after CI jobs have finished,
136+
the GitHub Action leaves tailscale binaries installed but stops the tailscale background processes.

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: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25686,10 +25686,13 @@ 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";
25691+
const runnerMacOS = "macOS";
2568925692
async function logout() {
2569025693
try {
2569125694
const runnerOS = process.env.RUNNER_OS || "";
25692-
if (runnerOS === "macOS") {
25695+
if (runnerOS === runnerMacOS) {
2569325696
// The below is required to allow GitHub's post job cleanup to complete.
2569425697
core.info("Resetting DNS settings on macOS");
2569525698
await exec.exec("networksetup", ["-setdnsservers", "Ethernet", "Empty"]);
@@ -25703,29 +25706,50 @@ async function logout() {
2570325706
// Check if tailscale is available first
2570425707
try {
2570525708
await exec.exec("tailscale", ["--version"], { silent: true });
25709+
// Determine the correct command based on OS
25710+
let execArgs;
25711+
if (runnerOS === runnerWindows) {
25712+
execArgs = ["tailscale", "logout"];
25713+
}
25714+
else {
25715+
// Linux and macOS - use system-installed binary with sudo
25716+
execArgs = ["sudo", "-E", "tailscale", "logout"];
25717+
}
25718+
core.info(`Running: ${execArgs.join(" ")}`);
25719+
try {
25720+
await exec.exec(execArgs[0], execArgs.slice(1));
25721+
core.info("✅ Successfully logged out of Tailscale");
25722+
}
25723+
catch (error) {
25724+
// Don't fail the action if logout fails - it's just cleanup
25725+
core.warning(`Failed to logout from Tailscale: ${error}`);
25726+
core.info("Your ephemeral node will eventually be cleaned up by Tailscale");
25727+
}
2570625728
}
2570725729
catch (error) {
2570825730
core.info("Tailscale not found or not accessible, skipping logout");
2570925731
return;
2571025732
}
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(" ")}`);
25733+
core.info("Stopping tailscale");
2572125734
try {
25722-
await exec.exec(execArgs[0], execArgs.slice(1));
25723-
core.info("✅ Successfully logged out of Tailscale");
25735+
if (runnerOS === runnerWindows) {
25736+
await exec.exec("net", ["stop", "Tailscale"]);
25737+
await exec.exec("taskkill", ["/F", "/IM", "tailscale-ipn.exe"]);
25738+
}
25739+
else {
25740+
const pid = fs.readFileSync("tailscaled.pid").toString();
25741+
if (pid === "") {
25742+
throw new Error("pid file empty");
25743+
}
25744+
// The pid is actually the pid of the `sudo` parent of tailscaled, so use pkill -P to kill children of that parent
25745+
await exec.exec("sudo", ["pkill", "-P", pid]);
25746+
// Clean up DNS and routes.
25747+
await exec.exec("sudo", ["tailscaled", "--cleanup"]);
25748+
}
25749+
core.info("✅ Stopped tailscale");
2572425750
}
2572525751
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");
25752+
core.warning(`Failed to stop tailscale: ${error}`);
2572925753
}
2573025754
}
2573125755
catch (error) {

src/logout/logout.ts

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
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";
9+
const runnerMacOS = "macOS";
610

711
async function logout(): Promise<void> {
812
try {
913
const runnerOS = process.env.RUNNER_OS || "";
1014

11-
if (runnerOS === "macOS") {
15+
if (runnerOS === runnerMacOS) {
1216
// The below is required to allow GitHub's post job cleanup to complete.
1317
core.info("Resetting DNS settings on macOS");
1418
await exec.exec("networksetup", ["-setdnsservers", "Ethernet", "Empty"]);
@@ -24,31 +28,51 @@ async function logout(): Promise<void> {
2428
// Check if tailscale is available first
2529
try {
2630
await exec.exec("tailscale", ["--version"], { silent: true });
31+
32+
// Determine the correct command based on OS
33+
let execArgs: string[];
34+
if (runnerOS === runnerWindows) {
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+
);
52+
}
2753
} catch (error) {
2854
core.info("Tailscale not found or not accessible, skipping logout");
2955
return;
3056
}
3157

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-
58+
core.info("Stopping tailscale");
4359
try {
44-
await exec.exec(execArgs[0], execArgs.slice(1));
45-
core.info("✅ Successfully logged out of Tailscale");
60+
if (runnerOS === runnerWindows) {
61+
await exec.exec("net", ["stop", "Tailscale"]);
62+
await exec.exec("taskkill", ["/F", "/IM", "tailscale-ipn.exe"]);
63+
} else {
64+
const pid = fs.readFileSync("tailscaled.pid").toString();
65+
if (pid === "") {
66+
throw new Error("pid file empty");
67+
}
68+
// The pid is actually the pid of the `sudo` parent of tailscaled, so use pkill -P to kill children of that parent
69+
await exec.exec("sudo", ["pkill", "-P", pid]);
70+
// Clean up DNS and routes.
71+
await exec.exec("sudo", ["tailscaled", "--cleanup"]);
72+
}
73+
core.info("✅ Stopped tailscale");
4674
} 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-
);
75+
core.warning(`Failed to stop tailscale: ${error}`);
5276
}
5377
} catch (error) {
5478
// 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)