@cardelli/ambit 0.2.3 → 0.3.1
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/README.md +38 -22
- package/esm/cli/commands/create/index.js +53 -17
- package/esm/cli/commands/create/machine.d.ts +2 -1
- package/esm/cli/commands/create/machine.d.ts.map +1 -1
- package/esm/cli/commands/create/machine.js +70 -29
- package/esm/cli/commands/deploy/index.js +2 -4
- package/esm/cli/commands/deploy/machine.d.ts.map +1 -1
- package/esm/cli/commands/destroy/app.d.ts.map +1 -1
- package/esm/cli/commands/destroy/index.js +2 -0
- package/esm/cli/commands/destroy/network.d.ts.map +1 -1
- package/esm/cli/commands/destroy/network.js +66 -5
- package/esm/cli/commands/doctor.js +13 -4
- package/esm/cli/commands/list.js +1 -1
- package/esm/cli/commands/share.d.ts +2 -0
- package/esm/cli/commands/share.d.ts.map +1 -0
- package/esm/cli/commands/share.js +250 -0
- package/esm/cli/commands/status.js +4 -1
- package/esm/cli/mod.d.ts.map +1 -1
- package/esm/cli/mod.js +2 -0
- package/esm/deno.js +1 -1
- package/esm/lib/command.d.ts.map +1 -1
- package/esm/lib/command.js +5 -7
- package/esm/main.d.ts +1 -0
- package/esm/main.d.ts.map +1 -1
- package/esm/main.js +2 -0
- package/esm/providers/fly.d.ts.map +1 -1
- package/esm/providers/fly.js +14 -3
- package/esm/providers/tailscale.d.ts +7 -0
- package/esm/providers/tailscale.d.ts.map +1 -1
- package/esm/providers/tailscale.js +23 -1
- package/esm/router/start.sh +7 -4
- package/esm/util/constants.d.ts +0 -1
- package/esm/util/constants.d.ts.map +1 -1
- package/esm/util/constants.js +0 -1
- package/esm/util/credentials.d.ts.map +1 -1
- package/esm/util/credentials.js +1 -1
- package/esm/util/discovery.d.ts.map +1 -1
- package/esm/util/discovery.js +1 -1
- package/esm/util/tailscale-local.d.ts +41 -0
- package/esm/util/tailscale-local.d.ts.map +1 -1
- package/esm/util/tailscale-local.js +146 -0
- package/esm/util/template.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -130,7 +130,9 @@ const stageSummary = (out, results) => {
|
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
out.blank();
|
|
133
|
-
out.text(issues === 0
|
|
133
|
+
out.text(issues === 0
|
|
134
|
+
? "All Checks Passed."
|
|
135
|
+
: `${issues} Issue${issues > 1 ? "s" : ""} Found.`);
|
|
134
136
|
out.blank();
|
|
135
137
|
out.print();
|
|
136
138
|
};
|
|
@@ -138,7 +140,10 @@ const stageSummary = (out, results) => {
|
|
|
138
140
|
// Doctor Network
|
|
139
141
|
// =============================================================================
|
|
140
142
|
const doctorNetwork = async (argv) => {
|
|
141
|
-
const opts = {
|
|
143
|
+
const opts = {
|
|
144
|
+
string: ["network", "org"],
|
|
145
|
+
boolean: ["help", "json"],
|
|
146
|
+
};
|
|
142
147
|
const args = parseArgs(argv, opts);
|
|
143
148
|
checkArgs(args, opts, "ambit doctor network");
|
|
144
149
|
if (args.help) {
|
|
@@ -168,7 +173,8 @@ ${bold("CHECKS")}
|
|
|
168
173
|
out.blank().header("ambit Doctor: Network").blank();
|
|
169
174
|
const results = [];
|
|
170
175
|
const report = makeReporter(results, out);
|
|
171
|
-
const network = (typeof args._[0] === "string" ? args._[0] : undefined) ||
|
|
176
|
+
const network = (typeof args._[0] === "string" ? args._[0] : undefined) ||
|
|
177
|
+
args.network;
|
|
172
178
|
const { fly, tailscale, org } = await initSession(out, {
|
|
173
179
|
json: args.json,
|
|
174
180
|
org: args.org,
|
|
@@ -181,7 +187,10 @@ ${bold("CHECKS")}
|
|
|
181
187
|
// Doctor App
|
|
182
188
|
// =============================================================================
|
|
183
189
|
const doctorApp = async (argv) => {
|
|
184
|
-
const opts = {
|
|
190
|
+
const opts = {
|
|
191
|
+
string: ["network", "org"],
|
|
192
|
+
boolean: ["help", "json"],
|
|
193
|
+
};
|
|
185
194
|
const args = parseArgs(argv, opts);
|
|
186
195
|
checkArgs(args, opts, "ambit doctor app");
|
|
187
196
|
if (args.help) {
|
package/esm/cli/commands/list.js
CHANGED
|
@@ -7,7 +7,7 @@ import { bold } from "../../lib/cli.js";
|
|
|
7
7
|
import { checkArgs } from "../../lib/args.js";
|
|
8
8
|
import { createOutput } from "../../lib/output.js";
|
|
9
9
|
import { registerCommand } from "../mod.js";
|
|
10
|
-
import { discoverRouters
|
|
10
|
+
import { discoverRouters } from "../../util/discovery.js";
|
|
11
11
|
import { initSession } from "../../util/session.js";
|
|
12
12
|
// =============================================================================
|
|
13
13
|
// Stage: Render
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/share.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Share Command - Grant Members Access to a Network via Tailscale ACL
|
|
3
|
+
// =============================================================================
|
|
4
|
+
import { parseArgs } from "../../deps/jsr.io/@std/cli/1.0.28/mod.js";
|
|
5
|
+
import { bold } from "../../lib/cli.js";
|
|
6
|
+
import { checkArgs } from "../../lib/args.js";
|
|
7
|
+
import { createOutput } from "../../lib/output.js";
|
|
8
|
+
import { registerCommand } from "../mod.js";
|
|
9
|
+
import { z } from "../../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
|
|
10
|
+
import { findRouterApp, getRouterMachineInfo } from "../../util/discovery.js";
|
|
11
|
+
import { getRouterTag } from "../../util/naming.js";
|
|
12
|
+
import { assertAdditivePatch, isAclRuleConfigured, patchAclRule, } from "../../util/tailscale-local.js";
|
|
13
|
+
import { initSession } from "../../util/session.js";
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Validation
|
|
16
|
+
// =============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Valid members: Tailscale group/tag/autogroup prefixes, or a plain email.
|
|
19
|
+
*/
|
|
20
|
+
const MemberSchema = z.union([
|
|
21
|
+
z.string().startsWith("group:").min(7, 'Must be in the form "group:<name>"'),
|
|
22
|
+
z.string().startsWith("tag:").min(5, 'Must be in the form "tag:<name>"'),
|
|
23
|
+
z.string().startsWith("autogroup:").min(11, 'Must be in the form "autogroup:<name>"'),
|
|
24
|
+
z.email(),
|
|
25
|
+
]);
|
|
26
|
+
const parseMembers = (raw) => {
|
|
27
|
+
const members = [];
|
|
28
|
+
const errors = [];
|
|
29
|
+
for (const entry of raw) {
|
|
30
|
+
const result = MemberSchema.safeParse(String(entry));
|
|
31
|
+
if (result.success) {
|
|
32
|
+
members.push(result.data);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
errors.push(` '${entry}' — Must Be a Group ("group:team"), Tag ("tag:router"), Autogroup ("autogroup:member"), or Email`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { members, errors };
|
|
39
|
+
};
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// ACL Error Helper
|
|
42
|
+
// =============================================================================
|
|
43
|
+
const handleAclSetFailure = (out, result, action) => {
|
|
44
|
+
if (result.status === 403) {
|
|
45
|
+
out.err(`${action}: Permission Denied (HTTP 403)`);
|
|
46
|
+
return out.die("Your API Token Lacks ACL Write Permission (policy_file scope required)");
|
|
47
|
+
}
|
|
48
|
+
return out.die(`${action}: ${result.error ?? `HTTP ${result.status}`}`);
|
|
49
|
+
};
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Stage 1: Discover Router
|
|
52
|
+
// =============================================================================
|
|
53
|
+
const stageDiscover = async (ctx) => {
|
|
54
|
+
ctx.out.header("Step 1: Discover Router").blank();
|
|
55
|
+
const routerSpinner = ctx.out.spinner(`Looking Up Router for '${ctx.network}'`);
|
|
56
|
+
const router = await findRouterApp(ctx.fly, ctx.org, ctx.network);
|
|
57
|
+
if (!router) {
|
|
58
|
+
routerSpinner.fail(`No Router Found for '${ctx.network}'`);
|
|
59
|
+
return ctx.out.die(`No Router Found for Network '${ctx.network}'. Create It with: ambit create ${ctx.network}`);
|
|
60
|
+
}
|
|
61
|
+
ctx.appName = router.appName;
|
|
62
|
+
routerSpinner.success(`Found Router: ${router.appName}`);
|
|
63
|
+
const machineSpinner = ctx.out.spinner("Getting Subnet");
|
|
64
|
+
const machine = await getRouterMachineInfo(ctx.fly, router.appName);
|
|
65
|
+
if (!machine?.subnet) {
|
|
66
|
+
machineSpinner.fail("No Subnet Found");
|
|
67
|
+
return ctx.out.die(`Router Has No Subnet Yet. Ensure the Router Is Running: ambit status network ${ctx.network}`);
|
|
68
|
+
}
|
|
69
|
+
ctx.subnet = machine.subnet;
|
|
70
|
+
machineSpinner.success(`Subnet: ${machine.subnet}`);
|
|
71
|
+
const device = await ctx.tailscale.devices.getByHostname(router.appName);
|
|
72
|
+
ctx.tag = device?.tags?.[0] ?? getRouterTag(ctx.network);
|
|
73
|
+
ctx.out.ok(`Tag: ${ctx.tag}`);
|
|
74
|
+
ctx.out.blank();
|
|
75
|
+
};
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// Stage 2: Update ACL Policy
|
|
78
|
+
// =============================================================================
|
|
79
|
+
const stageUpdateAcl = async (ctx) => {
|
|
80
|
+
ctx.out.header("Step 2: Update ACL Policy").blank();
|
|
81
|
+
const policy = await ctx.tailscale.acl.getPolicy();
|
|
82
|
+
if (!policy) {
|
|
83
|
+
return ctx.out.die("Could Not Read Tailscale ACL Policy");
|
|
84
|
+
}
|
|
85
|
+
const dnsDst = `${ctx.tag}:53`;
|
|
86
|
+
const subnetDst = `${ctx.subnet}:*`;
|
|
87
|
+
let updated = policy;
|
|
88
|
+
for (const member of ctx.members) {
|
|
89
|
+
const hasDns = isAclRuleConfigured(updated, member, dnsDst);
|
|
90
|
+
const hasSubnet = isAclRuleConfigured(updated, member, subnetDst);
|
|
91
|
+
if (hasDns && hasSubnet) {
|
|
92
|
+
ctx.out.skip(`${member} — Already Has Full Access`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (hasDns) {
|
|
96
|
+
ctx.out.skip(`${member} — DNS Rule Already Present`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
updated = patchAclRule(updated, member, dnsDst);
|
|
100
|
+
ctx.rulesAdded++;
|
|
101
|
+
}
|
|
102
|
+
if (hasSubnet) {
|
|
103
|
+
ctx.out.skip(`${member} — Subnet Rule Already Present`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
updated = patchAclRule(updated, member, subnetDst);
|
|
107
|
+
ctx.rulesAdded++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (ctx.rulesAdded === 0) {
|
|
111
|
+
ctx.out.blank().ok(`All Members Already Have Full Access to '${ctx.network}'`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
assertAdditivePatch(policy, updated);
|
|
115
|
+
const validateSpinner = ctx.out.spinner("Validating Policy");
|
|
116
|
+
const validateResult = await ctx.tailscale.acl.validatePolicy(updated);
|
|
117
|
+
if (!validateResult.ok) {
|
|
118
|
+
validateSpinner.fail("Policy Validation Failed");
|
|
119
|
+
return handleAclSetFailure(ctx.out, validateResult, "Validating ACL Policy");
|
|
120
|
+
}
|
|
121
|
+
validateSpinner.success("Policy Valid");
|
|
122
|
+
const n = ctx.rulesAdded;
|
|
123
|
+
const patchSpinner = ctx.out.spinner(`Updating ACL Policy (${n} new rule${n !== 1 ? "s" : ""})`);
|
|
124
|
+
const result = await ctx.tailscale.acl.setPolicy(updated);
|
|
125
|
+
if (!result.ok) {
|
|
126
|
+
patchSpinner.fail("Failed to Update ACL Policy");
|
|
127
|
+
return handleAclSetFailure(ctx.out, result, "Updating ACL Policy");
|
|
128
|
+
}
|
|
129
|
+
patchSpinner.success(`ACL Policy Updated (${n} rule${n !== 1 ? "s" : ""} added)`);
|
|
130
|
+
};
|
|
131
|
+
// =============================================================================
|
|
132
|
+
// Stage 3: Summary
|
|
133
|
+
// =============================================================================
|
|
134
|
+
const stageSummary = (ctx) => {
|
|
135
|
+
const { out, network, members, tag, subnet, rulesAdded } = ctx;
|
|
136
|
+
out.done({ network, members, tag: tag, subnet: subnet, rulesAdded });
|
|
137
|
+
if (rulesAdded === 0) {
|
|
138
|
+
out.blank();
|
|
139
|
+
out.print();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
out.blank()
|
|
143
|
+
.header("=".repeat(50))
|
|
144
|
+
.header(" Access Granted!")
|
|
145
|
+
.header("=".repeat(50))
|
|
146
|
+
.blank();
|
|
147
|
+
for (const member of members) {
|
|
148
|
+
out.ok(`${member}`);
|
|
149
|
+
}
|
|
150
|
+
out.blank()
|
|
151
|
+
.dim(` DNS: → ${tag}:53`)
|
|
152
|
+
.dim(` Subnet: → ${subnet}:*`)
|
|
153
|
+
.blank()
|
|
154
|
+
.dim("Invite People to Your Tailnet:")
|
|
155
|
+
.link(" https://login.tailscale.com/admin/users")
|
|
156
|
+
.blank()
|
|
157
|
+
.dim("To Fine-Tune Which Apps They Can Reach:")
|
|
158
|
+
.link(" https://login.tailscale.com/admin/acls/visual/general-access-rules")
|
|
159
|
+
.blank();
|
|
160
|
+
out.print();
|
|
161
|
+
};
|
|
162
|
+
// =============================================================================
|
|
163
|
+
// Share Command
|
|
164
|
+
// =============================================================================
|
|
165
|
+
const share = async (argv) => {
|
|
166
|
+
const opts = {
|
|
167
|
+
string: ["org"],
|
|
168
|
+
boolean: ["help", "json"],
|
|
169
|
+
};
|
|
170
|
+
const args = parseArgs(argv, opts);
|
|
171
|
+
checkArgs(args, opts, "ambit share");
|
|
172
|
+
if (args.help) {
|
|
173
|
+
console.log(`
|
|
174
|
+
${bold("ambit share")} - Grant Members Access to a Network
|
|
175
|
+
|
|
176
|
+
${bold("USAGE")}
|
|
177
|
+
ambit share <network> <member> [<member>...] [options]
|
|
178
|
+
|
|
179
|
+
${bold("MEMBER TYPES")}
|
|
180
|
+
group:<name> A Tailscale group (e.g. group:team)
|
|
181
|
+
tag:<name> A device tag (e.g. tag:router)
|
|
182
|
+
autogroup:<name> A built-in group (e.g. autogroup:member)
|
|
183
|
+
<email> A Tailscale user (e.g. alice@example.com)
|
|
184
|
+
|
|
185
|
+
${bold("OPTIONS")}
|
|
186
|
+
--org <org> Fly.io organization slug
|
|
187
|
+
--json Output as JSON
|
|
188
|
+
|
|
189
|
+
${bold("DESCRIPTION")}
|
|
190
|
+
Grants access to a network by updating your Tailscale ACL policy.
|
|
191
|
+
For each member, ambit adds two rules:
|
|
192
|
+
1. DNS: <member> → <tag>:53 (resolve *.${args._[0] || "<network>"} names)
|
|
193
|
+
2. Subnet: <member> → <subnet>:* (reach apps on the network)
|
|
194
|
+
|
|
195
|
+
The command is idempotent — re-running it is safe.
|
|
196
|
+
|
|
197
|
+
${bold("EXAMPLES")}
|
|
198
|
+
ambit share browsers group:team
|
|
199
|
+
ambit share browsers group:team alice@example.com group:contractors
|
|
200
|
+
ambit share browsers group:team --org my-org
|
|
201
|
+
`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const out = createOutput(args.json);
|
|
205
|
+
const networkArg = args._[0];
|
|
206
|
+
if (!networkArg || typeof networkArg !== "string") {
|
|
207
|
+
return out.die("Network Name Required. Usage: ambit share <network> <member> [...]");
|
|
208
|
+
}
|
|
209
|
+
const rawMembers = args._.slice(1);
|
|
210
|
+
if (rawMembers.length === 0) {
|
|
211
|
+
return out.die("At Least One Member Required. Usage: ambit share <network> <member> [...]");
|
|
212
|
+
}
|
|
213
|
+
const { members, errors } = parseMembers(rawMembers);
|
|
214
|
+
if (errors.length > 0) {
|
|
215
|
+
for (const err of errors)
|
|
216
|
+
out.err(err);
|
|
217
|
+
return out.die(`Invalid Member${errors.length > 1 ? "s" : ""}: Must Be "group:<name>", "tag:<name>", "autogroup:<name>", or a Valid Email`);
|
|
218
|
+
}
|
|
219
|
+
out.blank()
|
|
220
|
+
.header("=".repeat(50))
|
|
221
|
+
.header(` ambit Share: ${networkArg}`)
|
|
222
|
+
.header("=".repeat(50))
|
|
223
|
+
.blank();
|
|
224
|
+
const { fly, tailscale, org } = await initSession(out, {
|
|
225
|
+
json: args.json,
|
|
226
|
+
org: args.org,
|
|
227
|
+
});
|
|
228
|
+
const ctx = {
|
|
229
|
+
fly,
|
|
230
|
+
tailscale,
|
|
231
|
+
out,
|
|
232
|
+
network: networkArg,
|
|
233
|
+
org,
|
|
234
|
+
members,
|
|
235
|
+
json: args.json,
|
|
236
|
+
rulesAdded: 0,
|
|
237
|
+
};
|
|
238
|
+
await stageDiscover(ctx);
|
|
239
|
+
await stageUpdateAcl(ctx);
|
|
240
|
+
stageSummary(ctx);
|
|
241
|
+
};
|
|
242
|
+
// =============================================================================
|
|
243
|
+
// Register Command
|
|
244
|
+
// =============================================================================
|
|
245
|
+
registerCommand({
|
|
246
|
+
name: "share",
|
|
247
|
+
description: "Grant members access to a network via Tailscale ACL rules",
|
|
248
|
+
usage: "ambit share <network> <member> [<member>...]",
|
|
249
|
+
run: share,
|
|
250
|
+
});
|
|
@@ -224,7 +224,10 @@ const stageAppStatus = async (fly, tailscale, app, network, org, json) => {
|
|
|
224
224
|
// Status App Subcommand
|
|
225
225
|
// =============================================================================
|
|
226
226
|
const statusApp = async (argv) => {
|
|
227
|
-
const opts = {
|
|
227
|
+
const opts = {
|
|
228
|
+
string: ["network", "org"],
|
|
229
|
+
boolean: ["help", "json"],
|
|
230
|
+
};
|
|
228
231
|
const args = parseArgs(argv, opts);
|
|
229
232
|
checkArgs(args, opts, "ambit status app");
|
|
230
233
|
if (args.help) {
|
package/esm/cli/mod.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/cli/mod.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAQD,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,KAAG,IAElD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,OAAO,GAAG,SAEnD,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,OAAO,EAExC,CAAC;AAQF,eAAO,MAAM,QAAQ,QAAO,
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/cli/mod.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAQD,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,KAAG,IAElD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,OAAO,GAAG,SAEnD,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,OAAO,EAExC,CAAC;AAQF,eAAO,MAAM,QAAQ,QAAO,IA+B3B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAMF,eAAO,MAAM,MAAM,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAuCzD,CAAC"}
|
package/esm/cli/mod.js
CHANGED
|
@@ -32,6 +32,7 @@ ${bold("USAGE")}
|
|
|
32
32
|
${bold("COMMANDS")}
|
|
33
33
|
create Create a Tailscale subnet router on a Fly.io custom network
|
|
34
34
|
deploy Deploy an app safely on a custom private network
|
|
35
|
+
share Grant a Tailscale group access to a network via ACL rules
|
|
35
36
|
list List all discovered routers across networks
|
|
36
37
|
status Show router status, network, and tailnet info
|
|
37
38
|
destroy Destroy a network (router) or a workload app
|
|
@@ -43,6 +44,7 @@ ${bold("OPTIONS")}
|
|
|
43
44
|
|
|
44
45
|
${bold("EXAMPLES")}
|
|
45
46
|
ambit create browsers
|
|
47
|
+
ambit share browsers group:team
|
|
46
48
|
ambit list
|
|
47
49
|
ambit status --network browsers
|
|
48
50
|
ambit destroy network browsers
|
package/esm/deno.js
CHANGED
package/esm/lib/command.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/lib/command.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAMrC,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAC5B;AAMD,qBAAa,SAAS;IAIlB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM;IALzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;gBAGV,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM;IAKzB,iDAAiD;IACjD,IAAI,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC;IAapB,8BAA8B;IAC9B,IAAI,MAAM,IAAI,MAAM,CAEnB;CACF;AAMD;;;GAGG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EAAE,EACd,UAAU,UAAU,KACnB,OAAO,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/lib/command.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAMrC,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAC5B;AAMD,qBAAa,SAAS;IAIlB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM;IALzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;gBAGV,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM;IAKzB,iDAAiD;IACjD,IAAI,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC;IAapB,8BAA8B;IAC9B,IAAI,MAAM,IAAI,MAAM,CAEnB;CACF;AAMD;;;GAGG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EAAE,EACd,UAAU,UAAU,KACnB,OAAO,CAAC,SAAS,CAyCnB,CAAC;AAMF,oCAAoC;AACpC,eAAO,MAAM,OAAO,GAAI,CAAC,EACvB,MAAM,MAAM,EAAE,EACd,UAAU,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAuD,CAAC"}
|
package/esm/lib/command.js
CHANGED
|
@@ -48,13 +48,11 @@ export const runCommand = (args, options) => {
|
|
|
48
48
|
const child = spawn(cmd, cmdArgs, {
|
|
49
49
|
cwd: options?.cwd,
|
|
50
50
|
env: options?.env ? { ...process.env, ...options.env } : undefined,
|
|
51
|
-
stdio: interactive
|
|
52
|
-
? "inherit"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"pipe",
|
|
57
|
-
],
|
|
51
|
+
stdio: interactive ? "inherit" : [
|
|
52
|
+
options?.stdin === "inherit" ? "inherit" : "ignore",
|
|
53
|
+
"pipe",
|
|
54
|
+
"pipe",
|
|
55
|
+
],
|
|
58
56
|
});
|
|
59
57
|
if (interactive) {
|
|
60
58
|
child.on("error", () => {
|
package/esm/main.d.ts
CHANGED
package/esm/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA,OAAO,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA,OAAO,qBAAqB,CAAC;AA+B7B,OAAO,gCAAgC,CAAC;AACxC,OAAO,gCAAgC,CAAC;AACxC,OAAO,yBAAyB,CAAC;AACjC,OAAO,wBAAwB,CAAC;AAChC,OAAO,0BAA0B,CAAC;AAClC,OAAO,iCAAiC,CAAC;AACzC,OAAO,0BAA0B,CAAC"}
|
package/esm/main.js
CHANGED
|
@@ -12,6 +12,7 @@ import * as dntShim from "./_dnt.shims.js";
|
|
|
12
12
|
// Commands:
|
|
13
13
|
// create Create a Tailscale subnet router on a Fly.io custom network
|
|
14
14
|
// deploy Deploy an app safely on a custom private network
|
|
15
|
+
// share Grant a group access to a network via Tailscale ACL rules
|
|
15
16
|
// status Show router status, network, and tailnet info
|
|
16
17
|
// destroy Destroy a network (router) or a workload app
|
|
17
18
|
// doctor Check that Tailscale and the router are working correctly
|
|
@@ -28,6 +29,7 @@ import { runCli } from "./cli/mod.js";
|
|
|
28
29
|
import { Spinner, statusErr } from "./lib/cli.js";
|
|
29
30
|
import "./cli/commands/create/index.js";
|
|
30
31
|
import "./cli/commands/deploy/index.js";
|
|
32
|
+
import "./cli/commands/share.js";
|
|
31
33
|
import "./cli/commands/list.js";
|
|
32
34
|
import "./cli/commands/status.js";
|
|
33
35
|
import "./cli/commands/destroy/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../src/providers/fly.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EAIf,KAAK,KAAK,EAEV,KAAK,UAAU,EAIhB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../src/providers/fly.ts"],"names":[],"mappings":"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"}
|
package/esm/providers/fly.js
CHANGED
|
@@ -9,7 +9,7 @@ import { dirname, resolve } from "../deps/jsr.io/@std/path/1.1.4/mod.js";
|
|
|
9
9
|
import { FlyAppInfoListSchema, FlyAppsListSchema, FlyAuthSchema, FlyIpListSchema, FlyMachinesListSchema, FlyOrgsSchema, FlyStatusSchema, } from "../schemas/fly.js";
|
|
10
10
|
import { fileExists } from "../lib/cli.js";
|
|
11
11
|
import { getWorkloadAppName } from "../util/naming.js";
|
|
12
|
-
import { extractErrorDetail, getSizeConfig, mapMachines } from "../util/fly-transforms.js";
|
|
12
|
+
import { extractErrorDetail, getSizeConfig, mapMachines, } from "../util/fly-transforms.js";
|
|
13
13
|
// =============================================================================
|
|
14
14
|
// Deploy Error
|
|
15
15
|
// =============================================================================
|
|
@@ -60,7 +60,12 @@ export const createFlyProvider = () => {
|
|
|
60
60
|
if (!loginResult.ok) {
|
|
61
61
|
return die("Fly.io Authentication Failed");
|
|
62
62
|
}
|
|
63
|
-
const checkResult = await runCommand([
|
|
63
|
+
const checkResult = await runCommand([
|
|
64
|
+
"fly",
|
|
65
|
+
"auth",
|
|
66
|
+
"whoami",
|
|
67
|
+
"--json",
|
|
68
|
+
]);
|
|
64
69
|
if (!checkResult.ok) {
|
|
65
70
|
return die("Fly.io Authentication Verification Failed");
|
|
66
71
|
}
|
|
@@ -155,7 +160,13 @@ export const createFlyProvider = () => {
|
|
|
155
160
|
}
|
|
156
161
|
},
|
|
157
162
|
async exists(name) {
|
|
158
|
-
const result = await runCommand([
|
|
163
|
+
const result = await runCommand([
|
|
164
|
+
"fly",
|
|
165
|
+
"status",
|
|
166
|
+
"-a",
|
|
167
|
+
name,
|
|
168
|
+
"--json",
|
|
169
|
+
]);
|
|
159
170
|
return result.json().match({
|
|
160
171
|
ok: (data) => {
|
|
161
172
|
const parsed = FlyStatusSchema.safeParse(data);
|
|
@@ -4,6 +4,11 @@ export interface DeviceRoutes {
|
|
|
4
4
|
enabled: string[];
|
|
5
5
|
unapproved: string[];
|
|
6
6
|
}
|
|
7
|
+
export interface AclSetResult {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
status: number;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
7
12
|
export interface TailscaleProvider {
|
|
8
13
|
auth: {
|
|
9
14
|
validateKey(): Promise<boolean>;
|
|
@@ -25,6 +30,8 @@ export interface TailscaleProvider {
|
|
|
25
30
|
};
|
|
26
31
|
acl: {
|
|
27
32
|
getPolicy(): Promise<Record<string, unknown> | null>;
|
|
33
|
+
setPolicy(policy: Record<string, unknown>): Promise<AclSetResult>;
|
|
34
|
+
validatePolicy(policy: Record<string, unknown>): Promise<AclSetResult>;
|
|
28
35
|
};
|
|
29
36
|
}
|
|
30
37
|
export declare const createTailscaleProvider: (apiKey: string, tailnet?: string) => TailscaleProvider;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tailscale.d.ts","sourceRoot":"","sources":["../../src/providers/tailscale.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,mBAAmB,EAExB,KAAK,eAAe,EAErB,MAAM,yBAAyB,CAAC;AAYjC,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QACJ,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;KACxD,CAAC;IACF,OAAO,EAAE;QACP,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QACnC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;QACjE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACnC,CAAC;IACF,MAAM,EAAE;QACN,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;QACpD,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC5D,CAAC;IACF,GAAG,EAAE;QACH,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/D,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC3C,CAAC;IACF,GAAG,EAAE;QACH,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"tailscale.d.ts","sourceRoot":"","sources":["../../src/providers/tailscale.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,mBAAmB,EAExB,KAAK,eAAe,EAErB,MAAM,yBAAyB,CAAC;AAYjC,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QACJ,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;KACxD,CAAC;IACF,OAAO,EAAE;QACP,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QACnC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;QACjE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACnC,CAAC;IACF,MAAM,EAAE;QACN,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;QACpD,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC5D,CAAC;IACF,GAAG,EAAE;QACH,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/D,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAC3C,CAAC;IACF,GAAG,EAAE;QACH,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QACrD,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;QAClE,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;KACxE,CAAC;CACH;AAiBD,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,EACd,UAAS,MAAY,KACpB,iBA4OF,CAAC"}
|
|
@@ -21,7 +21,7 @@ export const createTailscaleProvider = (apiKey, tailnet = "-") => {
|
|
|
21
21
|
const response = await fetch(`${API_BASE}${path}`, {
|
|
22
22
|
method,
|
|
23
23
|
headers: headers(),
|
|
24
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
24
|
+
body: body ? JSON.stringify(body, null, 2) : undefined,
|
|
25
25
|
});
|
|
26
26
|
if (!response.ok) {
|
|
27
27
|
const text = await response.text();
|
|
@@ -144,6 +144,28 @@ export const createTailscaleProvider = (apiKey, tailnet = "-") => {
|
|
|
144
144
|
}
|
|
145
145
|
return result.data;
|
|
146
146
|
},
|
|
147
|
+
async setPolicy(policy) {
|
|
148
|
+
const result = await request("POST", `/tailnet/${tailnet}/acl`, policy);
|
|
149
|
+
if (!result.ok) {
|
|
150
|
+
return { ok: false, status: result.status, error: result.error };
|
|
151
|
+
}
|
|
152
|
+
return { ok: true, status: result.status };
|
|
153
|
+
},
|
|
154
|
+
async validatePolicy(policy) {
|
|
155
|
+
const result = await request("POST", `/tailnet/${tailnet}/acl/validate`, policy);
|
|
156
|
+
if (!result.ok) {
|
|
157
|
+
return { ok: false, status: result.status, error: result.error };
|
|
158
|
+
}
|
|
159
|
+
const body = result.data ?? {};
|
|
160
|
+
if (Object.keys(body).length > 0) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
status: result.status,
|
|
164
|
+
error: JSON.stringify(body),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return { ok: true, status: result.status };
|
|
168
|
+
},
|
|
147
169
|
},
|
|
148
170
|
};
|
|
149
171
|
return provider;
|
package/esm/router/start.sh
CHANGED
|
@@ -5,7 +5,8 @@ set -e
|
|
|
5
5
|
# ambit - Self-Configuring Tailscale Subnet Router
|
|
6
6
|
# =============================================================================
|
|
7
7
|
# State is persisted to /var/lib/tailscale via Fly volume.
|
|
8
|
-
# On first run:
|
|
8
|
+
# On first run: waits for TAILSCALE_AUTHKEY (delivered by the CLI after
|
|
9
|
+
# autoApprovers are in place), then authenticates and advertises routes.
|
|
9
10
|
# On restart: reuses existing state, no new device created.
|
|
10
11
|
# The router never receives the user's API token — only a single-use,
|
|
11
12
|
# tag-scoped auth key that expires after 5 minutes.
|
|
@@ -41,10 +42,12 @@ if /usr/local/bin/tailscale status --json 2>/dev/null | jq -e '.BackendState ==
|
|
|
41
42
|
--hostname="${FLY_APP_NAME:-ambit}" \
|
|
42
43
|
--advertise-routes="${SUBNET}"
|
|
43
44
|
else
|
|
44
|
-
# First run
|
|
45
|
+
# First run — auth key is delivered by the CLI via `fly secrets set` after
|
|
46
|
+
# autoApprovers are configured. If it's not here yet, wait for the restart
|
|
47
|
+
# that the non-staged secrets set triggers.
|
|
45
48
|
if [ -z "${TAILSCALE_AUTHKEY}" ]; then
|
|
46
|
-
echo "Router:
|
|
47
|
-
|
|
49
|
+
echo "Router: Waiting for Auth Key (CLI will deliver via secrets)"
|
|
50
|
+
while true; do sleep 5; done
|
|
48
51
|
fi
|
|
49
52
|
|
|
50
53
|
echo "Router: Authenticating to Tailscale"
|
package/esm/util/constants.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ export declare const ROUTER_APP_PREFIX = "ambit-";
|
|
|
2
2
|
export declare const DEFAULT_FLY_NETWORK = "default";
|
|
3
3
|
export declare const ROUTER_DOCKER_DIR: string;
|
|
4
4
|
export declare const SOCKS_PROXY_PORT = 1080;
|
|
5
|
-
export declare const FLY_PRIVATE_SUBNET = "fdaa::/16";
|
|
6
5
|
export declare const TAILSCALE_API_KEY_PREFIX = "tskey-api-";
|
|
7
6
|
export declare const ENV_TAILSCALE_API_KEY = "TAILSCALE_API_KEY";
|
|
8
7
|
export declare const FLYCTL_INSTALL_URL = "https://fly.io/docs/flyctl/install/";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/util/constants.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C,eAAO,MAAM,iBAAiB,QACnB,CAAC;AAMZ,eAAO,MAAM,gBAAgB,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/util/constants.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAE1C,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C,eAAO,MAAM,iBAAiB,QACnB,CAAC;AAMZ,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAMrC,eAAO,MAAM,wBAAwB,eAAe,CAAC;AAErD,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAMzD,eAAO,MAAM,kBAAkB,wCAAwC,CAAC;AAMxE,eAAO,MAAM,wBAAwB,sBAAsB,CAAC;AAC5D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAM5C,eAAO,MAAM,2BAA2B,yBAAyB,CAAC"}
|
package/esm/util/constants.js
CHANGED
|
@@ -12,7 +12,6 @@ export const ROUTER_DOCKER_DIR = new URL("../router", globalThis[Symbol.for("imp
|
|
|
12
12
|
// Networking
|
|
13
13
|
// =============================================================================
|
|
14
14
|
export const SOCKS_PROXY_PORT = 1080;
|
|
15
|
-
export const FLY_PRIVATE_SUBNET = "fdaa::/16";
|
|
16
15
|
// =============================================================================
|
|
17
16
|
// Tailscale
|
|
18
17
|
// =============================================================================
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/util/credentials.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/util/credentials.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,eAAe;IAC9B,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,eAAO,MAAM,2BAA2B,QAAO,eA0B9C,CAAC;AAMF,eAAO,MAAM,kBAAkB,QAAO,eAerC,CAAC;AAMF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC5B,KAAK;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,KAC1D,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,CAyBlC,CAAC"}
|
package/esm/util/credentials.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// =============================================================================
|
|
4
4
|
import * as dntShim from "../_dnt.shims.js";
|
|
5
5
|
import { z } from "../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
|
|
6
|
-
import { commandExists, ensureConfigDir, fileExists, getConfigDir } from "../lib/cli.js";
|
|
6
|
+
import { commandExists, ensureConfigDir, fileExists, getConfigDir, } from "../lib/cli.js";
|
|
7
7
|
import { ENV_TAILSCALE_API_KEY } from "./constants.js";
|
|
8
8
|
// =============================================================================
|
|
9
9
|
// Schema
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/util/discovery.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/util/discovery.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAQnE,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAMD,wDAAwD;AACxD,eAAO,MAAM,cAAc,GACzB,KAAK,WAAW,EAChB,KAAK,MAAM,KACV,OAAO,CAAC,SAAS,EAAE,CAerB,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,aAAa,GACxB,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,KACd,OAAO,CAAC,SAAS,GAAG,IAAI,CAG1B,CAAC;AAMF,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,GACpC,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,KACd,OAAO,CAAC,WAAW,EAAE,CAcvB,CAAC;AAEF;;+EAE+E;AAC/E,eAAO,MAAM,eAAe,GAC1B,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,EACf,UAAU,MAAM,KACf,OAAO,CAAC,WAAW,GAAG,IAAI,CAgC5B,CAAC;AAMF,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,GAC/B,KAAK,WAAW,EAChB,SAAS,MAAM,KACd,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAclC,CAAC;AAMF,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,GACjC,WAAW,iBAAiB,EAC5B,SAAS,MAAM,KACd,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAcpC,CAAC;AAMF,kEAAkE;AAClE,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG;IACvC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,KAAK;IAAE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,EAC7D,KAAK,WAAW,EAChB,WAAW,iBAAiB,EAC5B,KAAK,MAAM,KACV,OAAO,CAAC,cAAc,EAAE,CAa1B,CAAC"}
|
package/esm/util/discovery.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// =============================================================================
|
|
15
15
|
import { getRouterSuffix, getWorkloadAppName } from "./naming.js";
|
|
16
16
|
import { extractSubnet } from "./fly-transforms.js";
|
|
17
|
-
import { DEFAULT_FLY_NETWORK, ROUTER_APP_PREFIX
|
|
17
|
+
import { DEFAULT_FLY_NETWORK, ROUTER_APP_PREFIX } from "./constants.js";
|
|
18
18
|
// =============================================================================
|
|
19
19
|
// 1. Which routers exist? (Fly REST API)
|
|
20
20
|
// =============================================================================
|