@cardelli/ambit 0.2.3 → 0.3.0

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.
Files changed (38) hide show
  1. package/README.md +38 -22
  2. package/esm/cli/commands/create/index.js +64 -10
  3. package/esm/cli/commands/create/machine.d.ts.map +1 -1
  4. package/esm/cli/commands/create/machine.js +1 -1
  5. package/esm/cli/commands/deploy/index.js +2 -4
  6. package/esm/cli/commands/deploy/machine.d.ts.map +1 -1
  7. package/esm/cli/commands/destroy/app.d.ts.map +1 -1
  8. package/esm/cli/commands/destroy/index.js +2 -0
  9. package/esm/cli/commands/destroy/network.d.ts.map +1 -1
  10. package/esm/cli/commands/destroy/network.js +66 -5
  11. package/esm/cli/commands/doctor.js +13 -4
  12. package/esm/cli/commands/list.js +1 -1
  13. package/esm/cli/commands/share.d.ts +2 -0
  14. package/esm/cli/commands/share.d.ts.map +1 -0
  15. package/esm/cli/commands/share.js +250 -0
  16. package/esm/cli/commands/status.js +4 -1
  17. package/esm/cli/mod.d.ts.map +1 -1
  18. package/esm/cli/mod.js +2 -0
  19. package/esm/deno.js +1 -1
  20. package/esm/lib/command.d.ts.map +1 -1
  21. package/esm/lib/command.js +5 -7
  22. package/esm/main.d.ts +1 -0
  23. package/esm/main.d.ts.map +1 -1
  24. package/esm/main.js +2 -0
  25. package/esm/providers/fly.d.ts.map +1 -1
  26. package/esm/providers/fly.js +14 -3
  27. package/esm/providers/tailscale.d.ts +7 -0
  28. package/esm/providers/tailscale.d.ts.map +1 -1
  29. package/esm/providers/tailscale.js +23 -1
  30. package/esm/util/credentials.d.ts.map +1 -1
  31. package/esm/util/credentials.js +1 -1
  32. package/esm/util/discovery.d.ts.map +1 -1
  33. package/esm/util/discovery.js +1 -1
  34. package/esm/util/tailscale-local.d.ts +41 -0
  35. package/esm/util/tailscale-local.d.ts.map +1 -1
  36. package/esm/util/tailscale-local.js +146 -0
  37. package/esm/util/template.d.ts.map +1 -1
  38. package/package.json +1 -1
@@ -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 = { string: ["network", "org"], boolean: ["help", "json"] };
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) {
@@ -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,IA6B3B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAMF,eAAO,MAAM,MAAM,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAuCzD,CAAC"}
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
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Deploy apps to the cloud that only you and your AI agents can reach",
5
5
  "license": "MIT",
6
6
  "tasks": {
@@ -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,CA2CnB,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"}
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"}
@@ -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
- options?.stdin === "inherit" ? "inherit" : "ignore",
55
- "pipe",
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
@@ -2,6 +2,7 @@
2
2
  import "./_dnt.polyfills.js";
3
3
  import "./cli/commands/create/index.js";
4
4
  import "./cli/commands/deploy/index.js";
5
+ import "./cli/commands/share.js";
5
6
  import "./cli/commands/list.js";
6
7
  import "./cli/commands/status.js";
7
8
  import "./cli/commands/destroy/index.js";
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;AA8B7B,OAAO,gCAAgC,CAAC;AACxC,OAAO,gCAAgC,CAAC;AACxC,OAAO,wBAAwB,CAAC;AAChC,OAAO,0BAA0B,CAAC;AAClC,OAAO,iCAAiC,CAAC;AACzC,OAAO,0BAA0B,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":"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"}
1
+ {"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../src/providers/fly.ts"],"names":[],"mappings":"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"}
@@ -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(["fly", "auth", "whoami", "--json"]);
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(["fly", "status", "-a", name, "--json"]);
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;KACtD,CAAC;CACH;AAiBD,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,EACd,UAAS,MAAY,KACpB,iBAyMF,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;
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/util/credentials.ts"],"names":[],"mappings":"AAsBA,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"}
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"}
@@ -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;AAWnE,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,CA4B5B,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"}
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"}
@@ -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, } from "./constants.js";
17
+ import { DEFAULT_FLY_NETWORK, ROUTER_APP_PREFIX } from "./constants.js";
18
18
  // =============================================================================
19
19
  // 1. Which routers exist? (Fly REST API)
20
20
  // =============================================================================
@@ -8,6 +8,47 @@ export declare const isAcceptRoutesEnabled: () => Promise<boolean>;
8
8
  */
9
9
  export declare const enableAcceptRoutes: () => Promise<boolean>;
10
10
  export declare const waitForDevice: (provider: TailscaleProvider, hostname: string, timeoutMs?: number) => Promise<TailscaleDevice>;
11
+ /**
12
+ * Asserts that a patch operation only added data and never removed any
13
+ * top-level key or shortened any existing array. Throws if the invariant is
14
+ * violated so callers can surface a hard error before writing to the API.
15
+ */
16
+ export declare const assertAdditivePatch: (original: Record<string, unknown>, patched: Record<string, unknown>) => void;
11
17
  export declare const isTagOwnerConfigured: (policy: Record<string, unknown> | null, tag: string) => boolean;
12
18
  export declare const isAutoApproverConfigured: (policy: Record<string, unknown> | null, tag: string) => boolean;
19
+ /**
20
+ * Returns a new policy with the given tag added to tagOwners.
21
+ * No-op if the tag is already present.
22
+ */
23
+ export declare const patchTagOwner: (policy: Record<string, unknown>, tag: string, owners?: string[]) => Record<string, unknown>;
24
+ /**
25
+ * Returns a new policy with the given route + tag added to autoApprovers.
26
+ * No-op if the tag is already an approver for any route.
27
+ */
28
+ export declare const patchAutoApprover: (policy: Record<string, unknown>, tag: string, route: string) => Record<string, unknown>;
29
+ /**
30
+ * Returns true if there is already an accept rule where `src` contains the
31
+ * given source member and `dst` contains the given destination.
32
+ */
33
+ export declare const isAclRuleConfigured: (policy: Record<string, unknown> | null, src: string, dst: string) => boolean;
34
+ /**
35
+ * Returns a new policy with an accept rule `src → dst` appended to `acls`.
36
+ * No-op if an identical rule already exists.
37
+ */
38
+ export declare const patchAclRule: (policy: Record<string, unknown>, src: string, dst: string) => Record<string, unknown>;
39
+ /**
40
+ * Returns a new policy with all accept rules matching `src → dst` removed from `acls`.
41
+ * No-op if no such rule exists.
42
+ */
43
+ export declare const unpatchAclRule: (policy: Record<string, unknown>, src: string, dst: string) => Record<string, unknown>;
44
+ /**
45
+ * Returns a new policy with the given tag removed from tagOwners.
46
+ * No-op if the tag is not present.
47
+ */
48
+ export declare const unpatchTagOwner: (policy: Record<string, unknown>, tag: string) => Record<string, unknown>;
49
+ /**
50
+ * Returns a new policy with the given tag removed from all autoApprovers routes.
51
+ * If a route's approver list becomes empty after removal, the route entry is dropped.
52
+ */
53
+ export declare const unpatchAutoApprover: (policy: Record<string, unknown>, tag: string) => Record<string, unknown>;
13
54
  //# sourceMappingURL=tailscale-local.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tailscale-local.d.ts","sourceRoot":"","sources":["../../src/util/tailscale-local.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAMnE,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,OAAO,CAE5D,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,OAAO,CAM7D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,OAAO,CAG1D,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,UAAU,iBAAiB,EAC3B,UAAU,MAAM,EAChB,YAAW,MAAe,KACzB,OAAO,CAAC,eAAe,CAazB,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OASF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OAgBF,CAAC"}
1
+ {"version":3,"file":"tailscale-local.d.ts","sourceRoot":"","sources":["../../src/util/tailscale-local.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAMnE,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,OAAO,CAE5D,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,OAAO,CAM7D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,OAAO,CAG1D,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,UAAU,iBAAiB,EAC3B,UAAU,MAAM,EAChB,YAAW,MAAe,KACzB,OAAO,CAAC,eAAe,CAazB,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC/B,IAiBF,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OASF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OAgBF,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,EACX,SAAQ,MAAM,EAAwB,KACrC,MAAM,CAAC,MAAM,EAAE,OAAO,CAIxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,EACX,OAAO,MAAM,KACZ,MAAM,CAAC,MAAM,EAAE,OAAO,CAYxB,CAAC;AAaF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,EACX,KAAK,MAAM,KACV,OAUF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,GACvB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,EACX,KAAK,MAAM,KACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAOxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,EACX,KAAK,MAAM,KACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAaxB,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,KACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAQxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,MAAM,KACV,MAAM,CAAC,MAAM,EAAE,OAAO,CA+BxB,CAAC"}