@swarp/cli 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/deploy/wireguard.mjs +54 -0
- package/src/deploy/wireguard.test.mjs +91 -0
- package/src/init/index.mjs +1 -5
- package/src/init/index.test.mjs +23 -1
package/package.json
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
export function createWireGuardPeer(org, region, agentName) {
|
|
5
|
+
const confPath = `/tmp/${agentName}.conf`;
|
|
6
|
+
execFileSync("flyctl", ["wireguard", "create", org, region, agentName, confPath]);
|
|
7
|
+
return confPath;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function extractDnsIP(confPath) {
|
|
11
|
+
const contents = readFileSync(confPath, "utf8");
|
|
12
|
+
for (const line of contents.split("\n")) {
|
|
13
|
+
const trimmed = line.trim();
|
|
14
|
+
if (trimmed.startsWith("DNS")) {
|
|
15
|
+
const [, value] = trimmed.split("=");
|
|
16
|
+
return value.trim();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function addKeepalive(confPath, interval = 25) {
|
|
23
|
+
const contents = readFileSync(confPath, "utf8");
|
|
24
|
+
if (contents.includes("PersistentKeepalive")) {
|
|
25
|
+
return confPath;
|
|
26
|
+
}
|
|
27
|
+
const updated = contents.replace(
|
|
28
|
+
/(\[Peer\])/,
|
|
29
|
+
`$1\nPersistentKeepalive = ${interval}`
|
|
30
|
+
);
|
|
31
|
+
writeFileSync(confPath, updated, "utf8");
|
|
32
|
+
return confPath;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function pushWireGuardToSprite(agentName, confPath) {
|
|
36
|
+
const conf = readFileSync(confPath, "utf8");
|
|
37
|
+
execFileSync(
|
|
38
|
+
"sprite",
|
|
39
|
+
[
|
|
40
|
+
"exec",
|
|
41
|
+
"-s",
|
|
42
|
+
agentName,
|
|
43
|
+
"--",
|
|
44
|
+
"bash",
|
|
45
|
+
"-c",
|
|
46
|
+
"mkdir -p /etc/wireguard && cat > /etc/wireguard/wg0.conf && wg-quick up wg0",
|
|
47
|
+
],
|
|
48
|
+
{ input: conf }
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function removeWireGuardPeer(org, agentName) {
|
|
53
|
+
execFileSync("flyctl", ["wireguard", "remove", org, agentName]);
|
|
54
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { writeFileSync, readFileSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
vi.mock("node:child_process", () => ({ execFileSync: vi.fn() }));
|
|
7
|
+
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
9
|
+
import {
|
|
10
|
+
createWireGuardPeer,
|
|
11
|
+
extractDnsIP,
|
|
12
|
+
addKeepalive,
|
|
13
|
+
pushWireGuardToSprite,
|
|
14
|
+
removeWireGuardPeer,
|
|
15
|
+
} from "./wireguard.mjs";
|
|
16
|
+
|
|
17
|
+
const SAMPLE_CONF = `[Interface]
|
|
18
|
+
PrivateKey = abc123
|
|
19
|
+
Address = fdaa:0:5e4f:a7b:23c:0:a:2/120
|
|
20
|
+
DNS = fdaa:0:5e4f::3
|
|
21
|
+
|
|
22
|
+
[Peer]
|
|
23
|
+
PublicKey = xyz789
|
|
24
|
+
AllowedIPs = fdaa:0:5e4f::/48
|
|
25
|
+
Endpoint = sea1.gateway.6pn.dev:51820
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
function writeTempConf(contents = SAMPLE_CONF) {
|
|
29
|
+
const path = join(tmpdir(), `wg-test-${Date.now()}.conf`);
|
|
30
|
+
writeFileSync(path, contents, "utf8");
|
|
31
|
+
return path;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("createWireGuardPeer", () => {
|
|
35
|
+
beforeEach(() => vi.clearAllMocks());
|
|
36
|
+
|
|
37
|
+
it("calls flyctl with correct args and returns conf path", () => {
|
|
38
|
+
const result = createWireGuardPeer("my-org", "sea", "my-agent");
|
|
39
|
+
expect(execFileSync).toHaveBeenCalledWith("flyctl", [
|
|
40
|
+
"wireguard",
|
|
41
|
+
"create",
|
|
42
|
+
"my-org",
|
|
43
|
+
"sea",
|
|
44
|
+
"my-agent",
|
|
45
|
+
"/tmp/my-agent.conf",
|
|
46
|
+
]);
|
|
47
|
+
expect(result).toBe("/tmp/my-agent.conf");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("extractDnsIP", () => {
|
|
52
|
+
it("parses the DNS IP from a WG config", () => {
|
|
53
|
+
const confPath = writeTempConf();
|
|
54
|
+
expect(extractDnsIP(confPath)).toBe("fdaa:0:5e4f::3");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("addKeepalive", () => {
|
|
59
|
+
it("inserts PersistentKeepalive after [Peer]", () => {
|
|
60
|
+
const confPath = writeTempConf();
|
|
61
|
+
addKeepalive(confPath);
|
|
62
|
+
const result = readFileSync(confPath, "utf8");
|
|
63
|
+
expect(result).toContain("PersistentKeepalive = 25");
|
|
64
|
+
expect(result.indexOf("[Peer]")).toBeLessThan(
|
|
65
|
+
result.indexOf("PersistentKeepalive")
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("is idempotent — does not add keepalive twice", () => {
|
|
70
|
+
const confPath = writeTempConf();
|
|
71
|
+
addKeepalive(confPath);
|
|
72
|
+
addKeepalive(confPath);
|
|
73
|
+
const result = readFileSync(confPath, "utf8");
|
|
74
|
+
const count = (result.match(/PersistentKeepalive/g) || []).length;
|
|
75
|
+
expect(count).toBe(1);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("removeWireGuardPeer", () => {
|
|
80
|
+
beforeEach(() => vi.clearAllMocks());
|
|
81
|
+
|
|
82
|
+
it("calls flyctl with correct args", () => {
|
|
83
|
+
removeWireGuardPeer("my-org", "my-agent");
|
|
84
|
+
expect(execFileSync).toHaveBeenCalledWith("flyctl", [
|
|
85
|
+
"wireguard",
|
|
86
|
+
"remove",
|
|
87
|
+
"my-org",
|
|
88
|
+
"my-agent",
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
});
|
package/src/init/index.mjs
CHANGED
|
@@ -93,11 +93,6 @@ function buildRouterYaml() {
|
|
|
93
93
|
registration:
|
|
94
94
|
ttl_minutes: 30
|
|
95
95
|
persist_path: /data/registry.json
|
|
96
|
-
|
|
97
|
-
tls:
|
|
98
|
-
ca_cert_secret: SWARP_MTLS_CA_CERT
|
|
99
|
-
router_cert_secret: SWARP_MTLS_ROUTER_CERT
|
|
100
|
-
router_key_secret: SWARP_MTLS_ROUTER_KEY
|
|
101
96
|
`;
|
|
102
97
|
}
|
|
103
98
|
|
|
@@ -136,6 +131,7 @@ function updateMcpJson(cwd) {
|
|
|
136
131
|
console.log(` update ${mcpPath} — added swarp entry`);
|
|
137
132
|
}
|
|
138
133
|
|
|
134
|
+
|
|
139
135
|
// ── Main entry point ──────────────────────────────────────────────────────────
|
|
140
136
|
|
|
141
137
|
/**
|
package/src/init/index.test.mjs
CHANGED
|
@@ -4,6 +4,24 @@ import path from 'node:path';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import { runInit } from './index.mjs';
|
|
6
6
|
|
|
7
|
+
// Track calls made to execFileSync by the module under test.
|
|
8
|
+
// We cannot vi.spyOn a CJS binding in ESM, so we use a shared call log
|
|
9
|
+
// populated via vi.mock instead.
|
|
10
|
+
const execFileSyncCalls = [];
|
|
11
|
+
let execFileSyncImpl = null;
|
|
12
|
+
|
|
13
|
+
vi.mock('node:child_process', async (importOriginal) => {
|
|
14
|
+
const original = await importOriginal();
|
|
15
|
+
return {
|
|
16
|
+
...original,
|
|
17
|
+
execFileSync: (...args) => {
|
|
18
|
+
execFileSyncCalls.push(args);
|
|
19
|
+
if (execFileSyncImpl) return execFileSyncImpl(...args);
|
|
20
|
+
return original.execFileSync(...args);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
7
25
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
8
26
|
|
|
9
27
|
function makeTmpDir() {
|
|
@@ -28,6 +46,8 @@ describe('runInit', () => {
|
|
|
28
46
|
|
|
29
47
|
beforeEach(() => {
|
|
30
48
|
tmpDir = makeTmpDir();
|
|
49
|
+
execFileSyncCalls.length = 0;
|
|
50
|
+
execFileSyncImpl = null;
|
|
31
51
|
// Silence stdout/stderr during tests
|
|
32
52
|
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
33
53
|
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
@@ -35,6 +55,7 @@ describe('runInit', () => {
|
|
|
35
55
|
|
|
36
56
|
afterEach(() => {
|
|
37
57
|
cleanDir(tmpDir);
|
|
58
|
+
execFileSyncImpl = null;
|
|
38
59
|
vi.restoreAllMocks();
|
|
39
60
|
});
|
|
40
61
|
|
|
@@ -72,7 +93,7 @@ describe('runInit', () => {
|
|
|
72
93
|
expect(fs.existsSync(p)).toBe(true);
|
|
73
94
|
const content = fs.readFileSync(p, 'utf8');
|
|
74
95
|
expect(content).toContain('grpc_port: 50051');
|
|
75
|
-
expect(content).toContain('
|
|
96
|
+
expect(content).toContain('grpc_port: 50051');
|
|
76
97
|
});
|
|
77
98
|
|
|
78
99
|
it('creates .github/workflows/deploy-agents.yml', async () => {
|
|
@@ -165,4 +186,5 @@ describe('runInit', () => {
|
|
|
165
186
|
const messages = logSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
166
187
|
expect(messages).toContain('/swarp');
|
|
167
188
|
});
|
|
189
|
+
|
|
168
190
|
});
|