@stacksjs/ts-cloud 0.9.4 → 0.10.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.
- package/dist/aws/index.js +1 -1
- package/dist/bin/chunk-08c0pzkb.js +1 -0
- package/dist/bin/chunk-12210qvj.js +1 -0
- package/dist/bin/chunk-1v6bt68w.js +6 -0
- package/dist/bin/chunk-35ttwn26.js +70 -0
- package/dist/bin/chunk-3r3be05z.js +1 -0
- package/dist/bin/chunk-41yqpdv7.js +2 -0
- package/dist/bin/chunk-5rv31chh.js +1 -0
- package/dist/bin/chunk-6eat8ezk.js +2 -0
- package/dist/bin/chunk-6gktf2m2.js +1 -0
- package/dist/bin/chunk-6zjy949y.js +141 -0
- package/dist/bin/chunk-7wzdaqh6.js +2 -0
- package/dist/bin/chunk-80ar3aty.js +1 -0
- package/dist/bin/chunk-8rwhsp5b.js +2 -0
- package/dist/bin/chunk-936vp7dx.js +44 -0
- package/dist/bin/chunk-9px3rg4a.js +4 -0
- package/dist/bin/{chunk-rxcvf10f.js → chunk-anm3ae6z.js} +129 -129
- package/dist/bin/{chunk-tx0tqq2k.js → chunk-axvr7dwa.js} +6 -6
- package/dist/bin/chunk-b0hj9m5e.js +2 -0
- package/dist/bin/{chunk-mh0kdzqp.js → chunk-b2ymh8k0.js} +1 -1
- package/dist/bin/chunk-b35989e3.js +50 -0
- package/dist/bin/chunk-c2b6nk0q.js +37 -0
- package/dist/bin/chunk-ckxpgwxw.js +3 -0
- package/dist/bin/chunk-d6zzgvtn.js +92 -0
- package/dist/bin/chunk-da8zpf82.js +1 -0
- package/dist/bin/chunk-ddmjjstg.js +1 -0
- package/dist/bin/chunk-dhhz4zyj.js +1 -0
- package/dist/bin/chunk-dj9a67c3.js +9 -0
- package/dist/bin/chunk-ds1z880f.js +1 -0
- package/dist/bin/chunk-dvkj4k6h.js +111 -0
- package/dist/bin/chunk-eh40fpvj.js +1 -0
- package/dist/bin/chunk-ey9fagrr.js +6 -0
- package/dist/bin/chunk-ezftw2q4.js +1 -0
- package/dist/bin/chunk-g6a4n3rh.js +1 -0
- package/dist/bin/chunk-g90nqfgv.js +585 -0
- package/dist/bin/chunk-gs40j75w.js +1 -0
- package/dist/bin/chunk-jjjts1vt.js +1 -0
- package/dist/bin/{chunk-etrb03mt.js → chunk-jnsbzqzz.js} +30 -30
- package/dist/bin/chunk-jnx4jgw8.js +1 -0
- package/dist/bin/chunk-jt1tndm2.js +3 -0
- package/dist/bin/chunk-kybdb0ws.js +2 -0
- package/dist/bin/chunk-m3nqw76h.js +3 -0
- package/dist/bin/chunk-mn6j22zn.js +6 -0
- package/dist/bin/chunk-nb0yjkv5.js +2 -0
- package/dist/bin/chunk-pjpa4x52.js +2 -0
- package/dist/bin/chunk-pkgkfe6s.js +2 -0
- package/dist/bin/chunk-pnz5hk8n.js +60 -0
- package/dist/bin/chunk-q46zr2tv.js +1 -0
- package/dist/bin/chunk-qtdngvs8.js +1 -0
- package/dist/bin/chunk-sbejksrh.js +2 -0
- package/dist/bin/chunk-svyd4v6c.js +34 -0
- package/dist/bin/chunk-tna7ay8d.js +1 -0
- package/dist/bin/chunk-tp4c2mv1.js +1 -0
- package/dist/bin/chunk-w5d6tr2x.js +1 -0
- package/dist/bin/chunk-wewcjam5.js +1 -0
- package/dist/bin/chunk-xv4bscrm.js +1 -0
- package/dist/bin/chunk-y9eyc5pw.js +1 -0
- package/dist/bin/chunk-y9f8649w.js +512 -0
- package/dist/bin/cli.js +428 -418
- package/dist/bin/dashboard-server.js +1 -1
- package/dist/{chunk-dskvqt67.js → chunk-06jk7ba9.js} +127 -127
- package/dist/chunk-0e16qt6k.js +5 -0
- package/dist/chunk-13hxhzaz.js +34 -0
- package/dist/chunk-2p89vt3b.js +2 -0
- package/dist/{chunk-xt3s24vr.js → chunk-77t2ctwn.js} +1 -1
- package/dist/chunk-88z37ey4.js +114 -0
- package/dist/chunk-8c4715hz.js +2 -0
- package/dist/chunk-9a22225r.js +60 -0
- package/dist/chunk-as6by62d.js +1 -0
- package/dist/chunk-cd0zpxhf.js +44 -0
- package/dist/chunk-cp54a0qn.js +6 -0
- package/dist/chunk-d67xbk4v.js +141 -0
- package/dist/chunk-dckz9xve.js +2 -0
- package/dist/chunk-e1666vjv.js +2 -0
- package/dist/chunk-hhdx5k96.js +442 -0
- package/dist/{chunk-gahsdmxf.js → chunk-kv2ztkp4.js} +52 -52
- package/dist/chunk-nrz6zypg.js +1 -0
- package/dist/chunk-q816js1y.js +1 -0
- package/dist/chunk-qh9ep11z.js +704 -0
- package/dist/chunk-rg720cqg.js +1 -0
- package/dist/chunk-snak8s7m.js +92 -0
- package/dist/chunk-vq737g82.js +1 -0
- package/dist/chunk-xd6w0c11.js +50 -0
- package/dist/chunk-xtwjt370.js +37 -0
- package/dist/chunk-y26yh5zp.js +1 -0
- package/dist/chunk-zmxffmmq.js +4 -0
- package/dist/deploy/attach-credentials.d.ts +4 -8
- package/dist/deploy/index.js +1 -1
- package/dist/dns/cloudflare.d.ts +27 -0
- package/dist/dns/godaddy.d.ts +18 -1
- package/dist/dns/index.d.ts +2 -0
- package/dist/dns/index.js +1 -1
- package/dist/dns/zone-migration.d.ts +92 -0
- package/dist/dns/zone-migration.test.d.ts +1 -0
- package/dist/drivers/hetzner/driver.d.ts +8 -1
- package/dist/drivers/index.js +1 -1
- package/dist/drivers/shared/compute-provision.d.ts +14 -2
- package/dist/drivers/shared/db-provision.d.ts +3 -2
- package/dist/drivers/shared/mail-provision.d.ts +116 -0
- package/dist/drivers/shared/mail-provision.test.d.ts +1 -0
- package/dist/drivers/shared/managed-services-probe.d.ts +47 -0
- package/dist/drivers/shared/managed-services-probe.test.d.ts +1 -0
- package/dist/drivers/shared/package-manager.d.ts +1 -0
- package/dist/drivers/shared/remote-failure.d.ts +21 -0
- package/dist/drivers/shared/remote-failure.test.d.ts +1 -0
- package/dist/drivers/shared/sqlite-shared-path.d.ts +50 -0
- package/dist/drivers/shared/sqlite-shared-path.test.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +89 -89
- package/dist/operations/plan.d.ts +136 -0
- package/dist/operations/plan.test.d.ts +1 -0
- package/dist/operations/server-rename.d.ts +84 -0
- package/dist/operations/server-rename.test.d.ts +1 -0
- package/dist/operations/site-move.d.ts +263 -0
- package/dist/operations/site-move.test.d.ts +1 -0
- package/dist/protection/index.js +1 -1
- package/dist/push/index.js +1 -1
- package/dist/spend/index.js +1 -1
- package/dist/ui/404.html +1 -1
- package/dist/ui/accept-invitation.html +1 -1
- package/dist/ui/access-denied.html +1 -1
- package/dist/ui/account/automation.html +6 -6
- package/dist/ui/account/security.html +1 -1
- package/dist/ui/applications/compose.html +5 -5
- package/dist/ui/applications/new.html +2 -2
- package/dist/ui/data/backups.html +80 -80
- package/dist/ui/data/services.html +78 -78
- package/dist/ui/data/volumes.html +50 -50
- package/dist/ui/index.html +50 -50
- package/dist/ui/infrastructure/topology.html +118 -118
- package/dist/ui/integrations.html +1 -1
- package/dist/ui/operations/alerts.html +47 -47
- package/dist/ui/operations/configuration.html +5 -5
- package/dist/ui/operations/jobs.html +76 -76
- package/dist/ui/operations/maintenance.html +6 -6
- package/dist/ui/operations/observability.html +82 -82
- package/dist/ui/operations/previews.html +6 -6
- package/dist/ui/operations/queue.html +6 -6
- package/dist/ui/operations/regions.html +6 -6
- package/dist/ui/operations/releases.html +6 -6
- package/dist/ui/operations/spend.html +54 -54
- package/dist/ui/operations/workloads.html +104 -104
- package/dist/ui/security.html +2 -2
- package/dist/ui/server/actions.html +56 -56
- package/dist/ui/server/activity.html +1 -1
- package/dist/ui/server/capacity.html +6 -6
- package/dist/ui/server/database.html +106 -106
- package/dist/ui/server/deployments.html +50 -50
- package/dist/ui/server/diagnostics.html +2 -2
- package/dist/ui/server/firewall.html +76 -76
- package/dist/ui/server/fleet.html +33 -33
- package/dist/ui/server/logs.html +23 -23
- package/dist/ui/server/metrics.html +112 -112
- package/dist/ui/server/security.html +2 -2
- package/dist/ui/server/services.html +27 -27
- package/dist/ui/server/sites.html +120 -120
- package/dist/ui/server/ssh-keys.html +52 -52
- package/dist/ui/server/team.html +144 -144
- package/dist/ui/server/terminal.html +15 -15
- package/dist/ui/serverless/alarms.html +52 -52
- package/dist/ui/serverless/assets.html +27 -27
- package/dist/ui/serverless/cost.html +2 -2
- package/dist/ui/serverless/data.html +36 -36
- package/dist/ui/serverless/deployments.html +26 -26
- package/dist/ui/serverless/firewall.html +1 -1
- package/dist/ui/serverless/functions.html +39 -39
- package/dist/ui/serverless/logs.html +24 -24
- package/dist/ui/serverless/metrics.html +2 -2
- package/dist/ui/serverless/queues.html +48 -48
- package/dist/ui/serverless/secrets.html +52 -52
- package/dist/ui/serverless/traces.html +6 -6
- package/dist/ui/serverless.html +54 -54
- package/dist/ui-src/src/charts.ts +17499 -12662
- package/dist/ui-src/src/topology.ts +2 -2
- package/package.json +3 -3
- package/dist/bin/chunk-00kg80qg.js +0 -1
- package/dist/bin/chunk-09nwf2gy.js +0 -34
- package/dist/bin/chunk-1k5kszjp.js +0 -111
- package/dist/bin/chunk-21e0mavm.js +0 -1
- package/dist/bin/chunk-271gk1n2.js +0 -6
- package/dist/bin/chunk-2empps47.js +0 -2
- package/dist/bin/chunk-38pq971s.js +0 -2
- package/dist/bin/chunk-3gykfyq4.js +0 -37
- package/dist/bin/chunk-3vqfexc4.js +0 -70
- package/dist/bin/chunk-4vrqm9h0.js +0 -2
- package/dist/bin/chunk-598yf4bs.js +0 -1
- package/dist/bin/chunk-5ectjq0b.js +0 -1
- package/dist/bin/chunk-5erkxpt7.js +0 -512
- package/dist/bin/chunk-5mrtzemj.js +0 -1
- package/dist/bin/chunk-5n9fmabb.js +0 -1
- package/dist/bin/chunk-9knpg30y.js +0 -1
- package/dist/bin/chunk-9pvsff8g.js +0 -1
- package/dist/bin/chunk-ax9mr6wm.js +0 -44
- package/dist/bin/chunk-axhnkn5g.js +0 -2
- package/dist/bin/chunk-c1vcdgvr.js +0 -1
- package/dist/bin/chunk-ctd2vg9w.js +0 -2
- package/dist/bin/chunk-dbgffky8.js +0 -1
- package/dist/bin/chunk-ddk4rt4j.js +0 -6
- package/dist/bin/chunk-dwr30vbv.js +0 -1
- package/dist/bin/chunk-e0rkrq70.js +0 -1
- package/dist/bin/chunk-e3xrwh9z.js +0 -1
- package/dist/bin/chunk-fhc6ntvd.js +0 -2
- package/dist/bin/chunk-fpm59y25.js +0 -6
- package/dist/bin/chunk-fvbxvq6b.js +0 -1
- package/dist/bin/chunk-fy8j3y8p.js +0 -92
- package/dist/bin/chunk-fz21b57w.js +0 -1
- package/dist/bin/chunk-gavvf0pg.js +0 -141
- package/dist/bin/chunk-ggn7ax8p.js +0 -4
- package/dist/bin/chunk-gpe2prkm.js +0 -1
- package/dist/bin/chunk-h5pa5men.js +0 -2
- package/dist/bin/chunk-hm08xdxf.js +0 -2
- package/dist/bin/chunk-hzgz13nf.js +0 -1
- package/dist/bin/chunk-j703btc5.js +0 -50
- package/dist/bin/chunk-k9t9wvms.js +0 -1
- package/dist/bin/chunk-m669wtgr.js +0 -1
- package/dist/bin/chunk-m7dgskt1.js +0 -1
- package/dist/bin/chunk-mr0atd2s.js +0 -2
- package/dist/bin/chunk-mz0wbbsj.js +0 -3
- package/dist/bin/chunk-ngcyftm2.js +0 -1
- package/dist/bin/chunk-p66ptpmr.js +0 -2
- package/dist/bin/chunk-qk9qgepa.js +0 -1
- package/dist/bin/chunk-rytk89az.js +0 -60
- package/dist/bin/chunk-tga3hhwk.js +0 -6
- package/dist/bin/chunk-vek8a0w6.js +0 -1
- package/dist/bin/chunk-vm3q5gf2.js +0 -3
- package/dist/bin/chunk-y7s5r8y6.js +0 -3
- package/dist/bin/chunk-yv9sth30.js +0 -1
- package/dist/bin/chunk-zmegvjp8.js +0 -581
- package/dist/chunk-0ccta6z6.js +0 -6
- package/dist/chunk-0k1cs6pp.js +0 -1
- package/dist/chunk-1egjn203.js +0 -704
- package/dist/chunk-2wx29qga.js +0 -5
- package/dist/chunk-5m5rqmv7.js +0 -1
- package/dist/chunk-8ea5022g.js +0 -114
- package/dist/chunk-8xqn3c0w.js +0 -2
- package/dist/chunk-b4esxhmw.js +0 -2
- package/dist/chunk-b8e5wzwn.js +0 -44
- package/dist/chunk-bggt4n6e.js +0 -2
- package/dist/chunk-cwkg37rs.js +0 -37
- package/dist/chunk-gnv3r2jg.js +0 -1
- package/dist/chunk-hg3c48cp.js +0 -34
- package/dist/chunk-m5azeva3.js +0 -2
- package/dist/chunk-r3qf56b3.js +0 -92
- package/dist/chunk-sdqa9ghv.js +0 -141
- package/dist/chunk-svrvvcm7.js +0 -1
- package/dist/chunk-v0p3hpc3.js +0 -60
- package/dist/chunk-wdzjbecg.js +0 -50
- package/dist/chunk-ww8k3g7j.js +0 -1
- package/dist/chunk-xmqfc62w.js +0 -1
- package/dist/chunk-xrebj679.js +0 -435
- package/dist/chunk-y21zk0pn.js +0 -4
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan-then-apply scaffolding for fleet operations that change live topology.
|
|
3
|
+
*
|
|
4
|
+
* Consolidating servers — moving an app to another box, attaching one as a site,
|
|
5
|
+
* renaming a box — is a routine cleanup that is currently an afternoon of SSH.
|
|
6
|
+
* What makes it an afternoon is not the individual steps, which are small; it is
|
|
7
|
+
* that a half-finished one leaves no way to tell what already happened. So every
|
|
8
|
+
* such operation is expressed the same way here:
|
|
9
|
+
*
|
|
10
|
+
* - **Plan first.** A step says what it would change (`from → to`) before
|
|
11
|
+
* anything is touched, and prints the same way every run so a plan can be
|
|
12
|
+
* diffed.
|
|
13
|
+
* - **Idempotent and resumable.** Resumability comes from `satisfied()`, which
|
|
14
|
+
* asks REALITY whether the step's intent already holds — not from a checkpoint
|
|
15
|
+
* file, which can disagree with the world after a crash. Re-running an
|
|
16
|
+
* operation that died halfway continues rather than starting over or
|
|
17
|
+
* double-applying, and a fully-applied operation re-runs as a clean no-op.
|
|
18
|
+
* - **Typed confirmation for the destructive half.** Irreversible steps are
|
|
19
|
+
* marked, counted separately, and gated on the operator typing the target's
|
|
20
|
+
* exact name — separately from the operation itself.
|
|
21
|
+
* - **Non-interactive.** Nothing here reads stdin: a plan is data, the
|
|
22
|
+
* confirmation is a flag, so the whole sequence is drivable from CI.
|
|
23
|
+
* - **Audited.** Each step reports through {@link ApplyPlanOptions.audit} as it
|
|
24
|
+
* starts and finishes, so the operation log carries what actually ran.
|
|
25
|
+
*
|
|
26
|
+
* @see https://github.com/stacksjs/ts-cloud/issues/167
|
|
27
|
+
*/
|
|
28
|
+
/** A value a step changes, rendered in the plan as `from → to`. */
|
|
29
|
+
export interface StepChange {
|
|
30
|
+
from: string;
|
|
31
|
+
to: string;
|
|
32
|
+
}
|
|
33
|
+
export interface OperationStep {
|
|
34
|
+
/** Stable across runs — this is what an audit log and a resumed run key on. */
|
|
35
|
+
id: string;
|
|
36
|
+
/** One line, imperative: "Rename the Hetzner server". */
|
|
37
|
+
title: string;
|
|
38
|
+
/** What the step changes. Omitted for steps that only verify. */
|
|
39
|
+
change?: StepChange;
|
|
40
|
+
/**
|
|
41
|
+
* Irreversible. Marked in the plan and gated on typed confirmation, because
|
|
42
|
+
* "delete the drained source server" and "move an app" deserve different
|
|
43
|
+
* levels of ceremony even inside one operation.
|
|
44
|
+
*/
|
|
45
|
+
destructive?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Does reality ALREADY match this step's intent? This is what makes an
|
|
48
|
+
* operation resumable: a step that is satisfied is skipped, so a run that died
|
|
49
|
+
* halfway picks up where it stopped without the caller tracking progress.
|
|
50
|
+
*
|
|
51
|
+
* Must not mutate anything, and must tolerate a partially-applied world.
|
|
52
|
+
*/
|
|
53
|
+
satisfied: () => Promise<boolean>;
|
|
54
|
+
/** Perform the change. Only called when `satisfied()` returned false. */
|
|
55
|
+
apply: () => Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
export interface OperationPlan {
|
|
58
|
+
/** Stable operation name, e.g. `server:rename`. */
|
|
59
|
+
operation: string;
|
|
60
|
+
/** What is being operated on, and what a destructive step's confirmation must match. */
|
|
61
|
+
target: string;
|
|
62
|
+
steps: OperationStep[];
|
|
63
|
+
}
|
|
64
|
+
/** A step paired with what resolving it against the live world found. */
|
|
65
|
+
export interface ResolvedStep {
|
|
66
|
+
step: OperationStep;
|
|
67
|
+
/** `satisfied` — nothing to do; `pending` — would run; `unknown` — could not tell. */
|
|
68
|
+
state: 'satisfied' | 'pending' | 'unknown';
|
|
69
|
+
/** Why the state is `unknown`. A step that cannot be checked is never skipped. */
|
|
70
|
+
reason?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Ask every step whether it is already satisfied.
|
|
74
|
+
*
|
|
75
|
+
* A `satisfied()` that THROWS resolves to `unknown` rather than failing the
|
|
76
|
+
* plan: not being able to check is a reason to run the step and to say so, not a
|
|
77
|
+
* reason to refuse to show the operator a plan. Steps are resolved in order,
|
|
78
|
+
* because a later step's check may depend on an earlier one's subject existing.
|
|
79
|
+
*/
|
|
80
|
+
export declare function resolvePlan(plan: OperationPlan): Promise<ResolvedStep[]>;
|
|
81
|
+
/** Steps that would actually run — pending, plus the ones that could not be checked. */
|
|
82
|
+
export declare function pendingSteps(resolved: readonly ResolvedStep[]): ResolvedStep[];
|
|
83
|
+
/** Do any steps that would run make an irreversible change? */
|
|
84
|
+
export declare function planIsDestructive(resolved: readonly ResolvedStep[]): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* The plan as lines, ready to print.
|
|
87
|
+
*
|
|
88
|
+
* Lines rather than printed output so the caller owns the stream and this stays
|
|
89
|
+
* testable, and in declaration order so two runs of an unchanged plan produce an
|
|
90
|
+
* identical diff.
|
|
91
|
+
*/
|
|
92
|
+
export declare function formatPlan(plan: OperationPlan, resolved: readonly ResolvedStep[]): string[];
|
|
93
|
+
export interface StepOutcome {
|
|
94
|
+
id: string;
|
|
95
|
+
title: string;
|
|
96
|
+
/** `applied` — it ran; `skipped` — already satisfied; `failed` — it threw. */
|
|
97
|
+
state: 'applied' | 'skipped' | 'failed';
|
|
98
|
+
error?: string;
|
|
99
|
+
}
|
|
100
|
+
export interface OperationOutcome {
|
|
101
|
+
operation: string;
|
|
102
|
+
target: string;
|
|
103
|
+
steps: StepOutcome[];
|
|
104
|
+
/** False when any step failed. The steps before it stay applied — see below. */
|
|
105
|
+
success: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface ApplyPlanOptions {
|
|
108
|
+
/** Progress, one line per step. */
|
|
109
|
+
log?: (message: string) => void;
|
|
110
|
+
/**
|
|
111
|
+
* Append to the operation log. Called as each step starts and finishes, so a
|
|
112
|
+
* run that dies mid-step still leaves the start recorded — which is exactly
|
|
113
|
+
* the case an operator is trying to reconstruct afterwards.
|
|
114
|
+
*/
|
|
115
|
+
audit?: (event: {
|
|
116
|
+
operation: string;
|
|
117
|
+
target: string;
|
|
118
|
+
step: string;
|
|
119
|
+
state: 'started' | StepOutcome['state'];
|
|
120
|
+
error?: string;
|
|
121
|
+
}) => void;
|
|
122
|
+
/**
|
|
123
|
+
* Exact target name, required before any irreversible step runs. A plan with
|
|
124
|
+
* no destructive pending step ignores this.
|
|
125
|
+
*/
|
|
126
|
+
confirm?: string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Run the pending steps in order.
|
|
130
|
+
*
|
|
131
|
+
* Stops at the first failure and leaves the earlier steps applied, deliberately:
|
|
132
|
+
* a topology change half-applied and reported is recoverable — re-run it, the
|
|
133
|
+
* satisfied steps skip themselves — while one silently rolled back to a state
|
|
134
|
+
* nobody has verified is not.
|
|
135
|
+
*/
|
|
136
|
+
export declare function applyPlan(plan: OperationPlan, resolved: readonly ResolvedStep[], options?: ApplyPlanOptions): Promise<OperationOutcome>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rename a server in place.
|
|
3
|
+
*
|
|
4
|
+
* Renaming is purely an identity change, and today it implies destroy-and-
|
|
5
|
+
* recreate — which is not an acceptable cost for a naming-convention fix. It is
|
|
6
|
+
* the smallest of the consolidation operations and the one with no data to move:
|
|
7
|
+
* four records that all spell the same name, kept in step.
|
|
8
|
+
*
|
|
9
|
+
* The four, and why each matters:
|
|
10
|
+
*
|
|
11
|
+
* 1. **The provider record.** What the console shows and what a human greps for.
|
|
12
|
+
* 2. **The local driver state pin** (`storage/cloud/state/<stack>.json`). This
|
|
13
|
+
* one is not cosmetic. `findComputeTargets` REJECTS a pinned server whose
|
|
14
|
+
* live name no longer matches the recorded one — a deliberate guard against
|
|
15
|
+
* a stale pin sending a database operation to another project's box — so a
|
|
16
|
+
* provider-side rename that does not update the pin quietly invalidates it.
|
|
17
|
+
* 3. **The box's hostname.** What shells, logs, and the box's own reports say.
|
|
18
|
+
* 4. **The fleet inventory record.** What every `server:*` command addresses.
|
|
19
|
+
*
|
|
20
|
+
* Ordering is chosen for the failure in between: the provider is renamed BEFORE
|
|
21
|
+
* the pin is rewritten, so a crash between them leaves the pin stale (deploys
|
|
22
|
+
* fall back to label matching and keep working) rather than pointing at a name
|
|
23
|
+
* that does not exist yet. Every step re-derives its own state, so the fix for a
|
|
24
|
+
* half-finished rename is to run it again.
|
|
25
|
+
*
|
|
26
|
+
* Nothing here is destructive: a rename is undone by renaming back, and putting
|
|
27
|
+
* typed-confirmation ceremony on a reversible operation only teaches people to
|
|
28
|
+
* type confirmations without reading them.
|
|
29
|
+
*
|
|
30
|
+
* @see https://github.com/stacksjs/ts-cloud/issues/167
|
|
31
|
+
*/
|
|
32
|
+
import type { OperationPlan } from './plan';
|
|
33
|
+
/**
|
|
34
|
+
* The side effects a rename needs, injected so the operation is testable without
|
|
35
|
+
* a provider, an SSH host, or a control-plane database — and so a second driver
|
|
36
|
+
* can supply its own without this module knowing about it.
|
|
37
|
+
*
|
|
38
|
+
* The optional members are genuinely optional: a server enrolled by hand has no
|
|
39
|
+
* provider record to rename, a project deploying purely from labels has no state
|
|
40
|
+
* pin, and a box whose host key is not pinned cannot be reached to set a
|
|
41
|
+
* hostname. Each missing capability drops its step from the plan rather than
|
|
42
|
+
* failing the operation, and the plan says which ones it left out.
|
|
43
|
+
*/
|
|
44
|
+
export interface ServerRenameEffects {
|
|
45
|
+
/** Every name already taken — provider project and inventory both. */
|
|
46
|
+
takenNames: () => Promise<string[]>;
|
|
47
|
+
/** Live provider-side name. */
|
|
48
|
+
providerName?: () => Promise<string | undefined>;
|
|
49
|
+
renameProvider?: (next: string) => Promise<void>;
|
|
50
|
+
/** Name recorded in the local driver state pin. */
|
|
51
|
+
stateName?: () => Promise<string | undefined>;
|
|
52
|
+
writeStateName?: (next: string) => Promise<void>;
|
|
53
|
+
/** Hostname reported by the box itself. */
|
|
54
|
+
remoteHostname?: () => Promise<string | undefined>;
|
|
55
|
+
setRemoteHostname?: (next: string) => Promise<void>;
|
|
56
|
+
/** Name on the fleet inventory record. */
|
|
57
|
+
inventoryName: () => string;
|
|
58
|
+
renameInventory: (next: string) => Promise<void> | void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A server name has to be a valid hostname, because it becomes one: providers
|
|
62
|
+
* reject anything else, and the box's own `hostname` is set from it.
|
|
63
|
+
*
|
|
64
|
+
* RFC 1123 labels — letters, digits and hyphens, not starting or ending with a
|
|
65
|
+
* hyphen, at most 63 characters each — joined by dots. Checked up front so a
|
|
66
|
+
* rename fails before it has touched anything, rather than halfway through with
|
|
67
|
+
* the provider renamed and the box not.
|
|
68
|
+
*/
|
|
69
|
+
export declare function validateServerName(name: string): void;
|
|
70
|
+
/**
|
|
71
|
+
* Build the plan that renames `current` to `next`.
|
|
72
|
+
*
|
|
73
|
+
* Async because the preconditions — a legal name, and one nobody else holds —
|
|
74
|
+
* are checked here rather than becoming steps. A precondition is not a unit of
|
|
75
|
+
* work; making it one would let a plan print as if it were runnable and then
|
|
76
|
+
* fail on its first step.
|
|
77
|
+
*/
|
|
78
|
+
export declare function planServerRename(current: string, next: string, effects: ServerRenameEffects): Promise<OperationPlan>;
|
|
79
|
+
/**
|
|
80
|
+
* Shell that sets the box's hostname persistently and keeps `/etc/hosts` in
|
|
81
|
+
* step, so `sudo` and anything else resolving the local name does not stall on a
|
|
82
|
+
* hostname with no entry.
|
|
83
|
+
*/
|
|
84
|
+
export declare function buildSetHostnameScript(next: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move a deployed site from one box to another.
|
|
3
|
+
*
|
|
4
|
+
* The second of the consolidation operations: three boxes with one app each
|
|
5
|
+
* should be able to become one box with three sites, and today that is an
|
|
6
|
+
* afternoon of SSH. What makes it an afternoon is not the individual steps but
|
|
7
|
+
* the fact that a half-finished one leaves nothing to tell you what already
|
|
8
|
+
* happened — so this is expressed as a plan on the same scaffolding as
|
|
9
|
+
* `server:rename` (see `./plan`), where every step re-derives its own state and
|
|
10
|
+
* a run that dies is resumed by running it again.
|
|
11
|
+
*
|
|
12
|
+
* It moves the site's on-box footprint WHOLESALE — the whole
|
|
13
|
+
* `/var/www/<slug>-<site>` tree (every release, `shared/`, and the `current`
|
|
14
|
+
* symlink) plus the systemd units that run it. Copying those units is not a
|
|
15
|
+
* parallel mechanism: they are literally the units the deploy wrote, moved. The
|
|
16
|
+
* alternative, rebuilding from the repo on the target, would not be a move — it
|
|
17
|
+
* would be a fresh deploy that happens to be preceded by a data copy, and it
|
|
18
|
+
* would silently pick up whatever the repo says today rather than what is
|
|
19
|
+
* actually running.
|
|
20
|
+
*
|
|
21
|
+
* **Nothing here is irreversible.** The source's tree is never deleted, only
|
|
22
|
+
* drained: its units are stopped and disabled and its gateway route removed, so
|
|
23
|
+
* a bad cutover is undone by starting them again and pointing DNS back. That is
|
|
24
|
+
* the reversibility the operation is required to have — it lasts until the
|
|
25
|
+
* source SERVER is deleted, which is a separate, deliberately separate,
|
|
26
|
+
* destructive operation (`server:destroy`).
|
|
27
|
+
*
|
|
28
|
+
* Ordering is chosen for what a failure in the middle leaves behind. The source
|
|
29
|
+
* keeps serving until the target has passed a health gate, DNS is cut over only
|
|
30
|
+
* after that, and the source is drained last — so every prefix of this plan is a
|
|
31
|
+
* working system, either on the old box or the new one.
|
|
32
|
+
*
|
|
33
|
+
* @see https://github.com/stacksjs/ts-cloud/issues/167
|
|
34
|
+
*/
|
|
35
|
+
import type { OperationPlan } from './plan';
|
|
36
|
+
/** Where a snapshot archive is staged on both boxes. */
|
|
37
|
+
export declare function siteMoveArchivePath(slug: string, siteName: string): string;
|
|
38
|
+
/** Systemd unit-file glob covering a site's app, scheduler, queue and daemon units. */
|
|
39
|
+
export declare function siteUnitGlob(slug: string, siteName: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Stop the site's BACKGROUND units on the source — scheduler, queue workers,
|
|
42
|
+
* daemons — leaving the web service running.
|
|
43
|
+
*
|
|
44
|
+
* The snapshot has to be consistent, and background work is what writes to it: a
|
|
45
|
+
* queue worker committing to a SQLite database halfway through `tar` produces an
|
|
46
|
+
* archive with a torn page in it. The web service keeps serving because the
|
|
47
|
+
* source is still the live site at this point; read traffic during the copy is
|
|
48
|
+
* fine, and stopping it here would be an outage taken long before the target is
|
|
49
|
+
* ready to replace it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildPauseWorkersScript(slug: string, siteName: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Read-only companion to {@link buildPauseWorkersScript}. Separate because a
|
|
54
|
+
* plan's `satisfied()` must never mutate: if checking whether the workers are
|
|
55
|
+
* paused also paused them, a dry run would change the world.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildWorkersStateScript(slug: string, siteName: string): string;
|
|
58
|
+
/** Are the site's background units all stopped on the source? */
|
|
59
|
+
export declare function parseWorkersPaused(output: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Archive the site's whole footprint on the source: its install tree and its
|
|
62
|
+
* unit files, in one tarball.
|
|
63
|
+
*
|
|
64
|
+
* `-h` is deliberately NOT passed: `current` and the shared-path symlinks must
|
|
65
|
+
* stay symlinks. Dereferencing them would flatten `current` into a second copy
|
|
66
|
+
* of the live release and turn every shared path into a per-release file again —
|
|
67
|
+
* which is the exact failure `sharedPaths` exists to prevent.
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildSnapshotScript(slug: string, siteName: string, appBase: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Unpack the archive on the target and bring the app up.
|
|
72
|
+
*
|
|
73
|
+
* The app unit is started but the gateway is not yet pointed at it: the site
|
|
74
|
+
* has to answer on loopback before anything is routed to it, and DNS is not
|
|
75
|
+
* touched until it has. Background units are enabled but NOT started — they stay
|
|
76
|
+
* paused until the source has been drained, so the two boxes can never both be
|
|
77
|
+
* running the same scheduler against the same data.
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildRestoreScript(slug: string, siteName: string, appBase: string): string;
|
|
80
|
+
/** Is the site's tree already on the target, with its app unit running? */
|
|
81
|
+
export declare function buildTargetStateScript(slug: string, siteName: string, appBase: string): string;
|
|
82
|
+
/** Does the target already hold the site's tree AND run its app unit? */
|
|
83
|
+
export declare function parseTargetReady(output: string): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Health gate on the target, before any traffic is sent to it.
|
|
86
|
+
*
|
|
87
|
+
* Polls loopback rather than the public name on purpose: the public name still
|
|
88
|
+
* points at the SOURCE at this point in the plan, so asking it would cheerfully
|
|
89
|
+
* report the old box as healthy and wave a broken target through.
|
|
90
|
+
*/
|
|
91
|
+
export declare function buildHealthGateScript(port: number, path?: string, attempts?: number): string;
|
|
92
|
+
/**
|
|
93
|
+
* Drain the site on the source: stop and disable every unit, and drop its
|
|
94
|
+
* gateway fragment so the old box stops answering for it.
|
|
95
|
+
*
|
|
96
|
+
* The tree is left exactly where it is. That is what keeps the whole operation
|
|
97
|
+
* reversible: undoing a bad cutover is `systemctl start` plus pointing DNS back,
|
|
98
|
+
* with the data still sitting on the source. It stops being reversible when the
|
|
99
|
+
* source SERVER is deleted, which this operation deliberately does not do.
|
|
100
|
+
*/
|
|
101
|
+
export declare function buildDrainSourceScript(slug: string, siteName: string): string;
|
|
102
|
+
/**
|
|
103
|
+
* Read-only companion to {@link buildDrainSourceScript} — see
|
|
104
|
+
* {@link buildWorkersStateScript} for why the two are not one script.
|
|
105
|
+
*/
|
|
106
|
+
export declare function buildSourceStateScript(slug: string, siteName: string): string;
|
|
107
|
+
/** Is the site fully stopped on the source? */
|
|
108
|
+
export declare function parseSourceDrained(output: string): boolean;
|
|
109
|
+
/** Where a site's TLS material is staged while it crosses between boxes. */
|
|
110
|
+
export declare function siteMoveCertArchivePath(slug: string, siteName: string): string;
|
|
111
|
+
/**
|
|
112
|
+
* Report a checksum per hostname for the cert the box holds, or `absent`.
|
|
113
|
+
*
|
|
114
|
+
* A checksum rather than "does the file exist": the target may hold an OLDER
|
|
115
|
+
* certificate for the same hostname — an expired one from a previous tenancy,
|
|
116
|
+
* or a staging cert issued while the domain still pointed elsewhere — and
|
|
117
|
+
* treating that as "already carried" would hand the cutover a certificate
|
|
118
|
+
* browsers reject.
|
|
119
|
+
*
|
|
120
|
+
* Read-only, so a plan can use it without changing either box.
|
|
121
|
+
*/
|
|
122
|
+
export declare function buildCertificateStateScript(certsDir: string, domains: readonly string[]): string;
|
|
123
|
+
/** `hostname → checksum | 'absent'` from {@link buildCertificateStateScript}. */
|
|
124
|
+
export declare function parseCertificateState(output: string): Map<string, string>;
|
|
125
|
+
/**
|
|
126
|
+
* Is the target's TLS material already what the source holds?
|
|
127
|
+
*
|
|
128
|
+
* Only hostnames the SOURCE has a certificate for are compared. A site running
|
|
129
|
+
* behind on-demand TLS may legitimately have none yet, and a move must not
|
|
130
|
+
* block on carrying something that does not exist.
|
|
131
|
+
*/
|
|
132
|
+
export declare function certificatesMatch(source: Map<string, string>, target: Map<string, string>): boolean;
|
|
133
|
+
/** Pack a site's certificate and private key for each hostname it serves. */
|
|
134
|
+
export declare function buildCertificatePackScript(certsDir: string, domains: readonly string[], archive: string): string;
|
|
135
|
+
/**
|
|
136
|
+
* Unpack the certificates on the target.
|
|
137
|
+
*
|
|
138
|
+
* The private keys are re-chmodded to 0600 after extraction. `tar` restores the
|
|
139
|
+
* modes it recorded, so this is belt-and-braces — but a world-readable private
|
|
140
|
+
* key on a shared box is the kind of mistake that is silent until it is not.
|
|
141
|
+
*/
|
|
142
|
+
export declare function buildCertificateUnpackScript(certsDir: string, archive: string): string;
|
|
143
|
+
/**
|
|
144
|
+
* The side effects a move needs, injected so the operation is testable without
|
|
145
|
+
* two live boxes, a provider, or a DNS account.
|
|
146
|
+
*/
|
|
147
|
+
export interface SiteMoveEffects {
|
|
148
|
+
/** Run a script on the source box; resolves with stdout, rejects on failure. */
|
|
149
|
+
runOnSource: (script: string) => Promise<string>;
|
|
150
|
+
/** Run a script on the target box. */
|
|
151
|
+
runOnTarget: (script: string) => Promise<string>;
|
|
152
|
+
/**
|
|
153
|
+
* Carry the staged archive from the source to the target. Separate from the
|
|
154
|
+
* two exec effects because how bytes get between two boxes is a deployment
|
|
155
|
+
* decision — through the operator, over a direct SSH hop, via object storage —
|
|
156
|
+
* and none of it belongs in this plan.
|
|
157
|
+
*/
|
|
158
|
+
transferArchive: () => Promise<void>;
|
|
159
|
+
/** Is the archive already staged on the target? Lets the transfer resume. */
|
|
160
|
+
archiveStaged: () => Promise<boolean>;
|
|
161
|
+
/** Point the site's hostname at the target. Resolves with any provider warnings. */
|
|
162
|
+
cutoverDns: () => Promise<string[]>;
|
|
163
|
+
/** Address the site's hostname currently publishes, for the cutover check. */
|
|
164
|
+
publishedAddress: () => Promise<string | undefined>;
|
|
165
|
+
/** Refresh the target's gateway so it routes the site. */
|
|
166
|
+
refreshTargetGateway: () => Promise<void>;
|
|
167
|
+
/** Does the target's gateway already route this site? */
|
|
168
|
+
targetRoutesSite: () => Promise<boolean>;
|
|
169
|
+
/**
|
|
170
|
+
* How to carry an ON-BOX database, when the project has one.
|
|
171
|
+
*
|
|
172
|
+
* A SQLite database rides along in the tree already — it lives under
|
|
173
|
+
* `shared/`, which is the whole point of `sharedPaths`. An engine database
|
|
174
|
+
* does not: Postgres and MySQL keep their data in the engine's own directory,
|
|
175
|
+
* which belongs to the box rather than to the site. Moving the tree and not
|
|
176
|
+
* the database would cut DNS over to a target serving an empty app, so the
|
|
177
|
+
* plan REFUSES to be built when a project has one of these and no way to
|
|
178
|
+
* carry it — see {@link SiteMoveOptions.database}.
|
|
179
|
+
*
|
|
180
|
+
* Absent when the database is external (RDS, a managed host): the target
|
|
181
|
+
* connects to the same endpoint the source did, and there is nothing to move.
|
|
182
|
+
*/
|
|
183
|
+
database?: SiteMoveDatabaseEffects;
|
|
184
|
+
/**
|
|
185
|
+
* How to carry the site's TLS material, when the project terminates TLS on the
|
|
186
|
+
* box itself.
|
|
187
|
+
*
|
|
188
|
+
* Certificates live in the gateway's cert directory, which belongs to the box
|
|
189
|
+
* rather than to the site — the same shape as an on-box database, and the same
|
|
190
|
+
* failure if it is left behind. With pinned production certificates the
|
|
191
|
+
* cutover lands on a box that has no certificate for the hostname and every
|
|
192
|
+
* browser refuses it. With on-demand TLS the target re-issues on first
|
|
193
|
+
* request, so it self-heals, but only after a visible gap during which the
|
|
194
|
+
* site is down.
|
|
195
|
+
*
|
|
196
|
+
* Absent when TLS is terminated somewhere else entirely — a CDN, a managed
|
|
197
|
+
* load balancer — where the box never holds the material to begin with.
|
|
198
|
+
*/
|
|
199
|
+
certificates?: SiteMoveCertificateEffects;
|
|
200
|
+
}
|
|
201
|
+
/** Carrying a site's TLS material between boxes. */
|
|
202
|
+
export interface SiteMoveCertificateEffects {
|
|
203
|
+
/**
|
|
204
|
+
* Does the target already hold exactly what the source holds?
|
|
205
|
+
*
|
|
206
|
+
* Compared by checksum rather than existence: an older certificate for the
|
|
207
|
+
* same hostname on the target is not the one being moved.
|
|
208
|
+
*/
|
|
209
|
+
inPlace: () => Promise<boolean>;
|
|
210
|
+
/** Pack on the source, carry, unpack on the target. */
|
|
211
|
+
carry: () => Promise<void>;
|
|
212
|
+
}
|
|
213
|
+
/** Carrying an on-box engine database between boxes, in resumable pieces. */
|
|
214
|
+
export interface SiteMoveDatabaseEffects {
|
|
215
|
+
/** Dump it on the source. */
|
|
216
|
+
dump: () => Promise<void>;
|
|
217
|
+
/** Carry the dump to the target. */
|
|
218
|
+
transferDump: () => Promise<void>;
|
|
219
|
+
/** Is the dump already staged on the target? Lets the transfer resume. */
|
|
220
|
+
dumpStaged: () => Promise<boolean>;
|
|
221
|
+
/** Create the role + database on the target, then load the dump into it. */
|
|
222
|
+
restore: () => Promise<void>;
|
|
223
|
+
/**
|
|
224
|
+
* Does the target ALREADY hold a database of this name with tables in it?
|
|
225
|
+
*
|
|
226
|
+
* Checked as a precondition, not as a step: loading a dump over someone
|
|
227
|
+
* else's data is the one genuinely destructive thing a move could do, and the
|
|
228
|
+
* answer decides whether the operation may run at all.
|
|
229
|
+
*/
|
|
230
|
+
targetHasData: () => Promise<boolean>;
|
|
231
|
+
}
|
|
232
|
+
export interface SiteMoveOptions {
|
|
233
|
+
slug: string;
|
|
234
|
+
siteName: string;
|
|
235
|
+
/** Install base on both boxes — `siteInstallBase(slug, siteName)`. */
|
|
236
|
+
appBase: string;
|
|
237
|
+
/** Source and target box names, for the plan's own prose. */
|
|
238
|
+
from: string;
|
|
239
|
+
to: string;
|
|
240
|
+
/** Target's public address, which DNS is cut over to. */
|
|
241
|
+
targetAddress: string;
|
|
242
|
+
/** Loopback port the health gate polls. Omitted for a portless site. */
|
|
243
|
+
port?: number;
|
|
244
|
+
/** Health-check path, defaulting to `/`. */
|
|
245
|
+
healthCheckPath?: string;
|
|
246
|
+
/**
|
|
247
|
+
* The on-box engine database this site's project owns, when it has one —
|
|
248
|
+
* `resolveAppDatabase(config)` narrowed by `isLocalDatabase`. Naming it here
|
|
249
|
+
* is what makes the move refuse to run without a way to carry it, rather than
|
|
250
|
+
* moving the app and quietly leaving its data behind.
|
|
251
|
+
*/
|
|
252
|
+
database?: {
|
|
253
|
+
name: string;
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Build the plan that moves `siteName` from one box to another.
|
|
258
|
+
*
|
|
259
|
+
* Async because the preconditions — the site exists on the source, the target is
|
|
260
|
+
* not already serving a different tree there — are checked here rather than
|
|
261
|
+
* becoming steps. A precondition is not a unit of work.
|
|
262
|
+
*/
|
|
263
|
+
export declare function planSiteMove(options: SiteMoveOptions, effects: SiteMoveEffects): Promise<OperationPlan>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/protection/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$k as
|
|
1
|
+
import{$k as D,Wk as y,Xk as z,Yk as A,Zk as B,_k as C,al as E,bl as F,cl as G,dl as H,el as I,fl as J,gl as K,hl as L,il as M}from"../chunk-y26yh5zp.js";import{jl as s,kl as t,ll as u,ml as v,nl as w,ol as x}from"../chunk-xtwjt370.js";import{pl as k,ql as l,rl as m,sl as n,tl as o,ul as p,vl as q,wl as r}from"../chunk-cd0zpxhf.js";import{Js as a,Ks as b,Ls as c,Ms as d,Ns as e,Os as f,Ps as g,Qs as h,Rs as i,Ss as j}from"../chunk-06jk7ba9.js";import"../chunk-77t2ctwn.js";export{b as CHAIN_HEADER,H as DEFAULT_ATTACK_MODE_HOURS,k as DEFAULT_DDOS_THRESHOLDS,e as DEFAULT_RECURSION_LIMITS,a as DEPTH_HEADER,G as MAX_CONTROL_HOURS,F as PROTECTION_SETTING,I as ProtectionControlStore,y as RATE_LIMIT_KEY_SEPARATOR,C as RateLimiter,h as RecursionGuard,c as TRACE_HEADER,L as applyControlsToDdos,K as applyControlsToRateLimits,r as applyMitigationFactor,E as defaultRateLimitRules,J as describePosture,s as escapeSecLang,d as functionFingerprint,z as globMatches,u as globToRegex,g as inspectInvocation,t as isSafePattern,l as isValidCidr,M as mergeControlsIntoConfig,f as parseChain,q as planMitigation,i as propagationHeaders,D as rateLimitHeaders,B as rateLimitKey,j as recursionBlockedResponse,p as renderDdosInstallScript,o as renderNftablesRuleset,n as renderSysctlFile,v as renderWafConfig,w as renderWafInstallScript,A as ruleMatches,m as sysctlHardening,x as wafEventTelemetry};
|
package/dist/push/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"../chunk-
|
|
1
|
+
import"../chunk-77t2ctwn.js";import*as h from"node:http2";import{createSign as b}from"node:crypto";var f="api.push.apple.com",k="api.sandbox.push.apple.com",S=2700000;class R{config;token=null;tokenGeneratedAt=0;client=null;host;constructor(e){this.config=e,this.host=e.production?f:k}generateToken(){let e=Math.floor(Date.now()/1000);if(this.token&&Date.now()-this.tokenGeneratedAt<S)return this.token;let t={alg:"ES256",kid:this.config.keyId},s={iss:this.config.teamId,iat:e},r=Buffer.from(JSON.stringify(t)).toString("base64url"),n=Buffer.from(JSON.stringify(s)).toString("base64url"),i=`${r}.${n}`,o=b("SHA256");o.update(i);let a=o.sign(this.config.privateKey),l=this.derToRaw(a).toString("base64url");return this.token=`${i}.${l}`,this.tokenGeneratedAt=Date.now(),this.token}derToRaw(e){let t=2;if(e[t]!==2)throw Error("Invalid DER signature: expected 0x02 for R");t++;let s=e[t];t++;let r=e.subarray(t,t+s);if(t+=s,e[t]!==2)throw Error("Invalid DER signature: expected 0x02 for S");t++;let n=e[t];t++;let i=e.subarray(t,t+n);if(r[0]===0&&r.length===33)r=r.subarray(1);if(i[0]===0&&i.length===33)i=i.subarray(1);let o=Buffer.alloc(64);return r.copy(o,32-r.length),i.copy(o,64-i.length),o}async getClient(){if(this.client&&!this.client.destroyed)return this.client;return new Promise((e,t)=>{this.client=h.connect(`https://${this.host}`),this.client.on("error",(s)=>{t(s)}),this.client.on("connect",()=>{e(this.client)}),this.client.on("close",()=>{this.client=null})})}buildPayload(e){let t={};if(e.title||e.body||e.subtitle){if(t.alert={},e.title)t.alert.title=e.title;if(e.subtitle)t.alert.subtitle=e.subtitle;if(e.body)t.alert.body=e.body}if(e.badge!==void 0)t.badge=e.badge;if(e.sound!==void 0)t.sound=e.sound;if(e.category)t.category=e.category;if(e.threadId)t["thread-id"]=e.threadId;if(e.contentAvailable)t["content-available"]=1;if(e.mutableContent)t["mutable-content"]=1;if(e.targetContentId)t["target-content-id"]=e.targetContentId;let s={aps:t};if(e.data)Object.assign(s,e.data);return s}async send(e){try{let t=await this.getClient(),s=this.generateToken(),r=JSON.stringify(this.buildPayload(e)),n={":method":"POST",":path":`/3/device/${e.deviceToken}`,authorization:`bearer ${s}`,"apns-topic":this.config.bundleId,"apns-push-type":e.pushType||"alert","apns-priority":String(e.priority||10)};if(e.expiration!==void 0)n["apns-expiration"]=String(e.expiration);if(e.collapseId)n["apns-collapse-id"]=e.collapseId;return new Promise((i)=>{let o=t.request(n),a="",d=0,l;o.on("response",(c)=>{d=c[":status"],l=c["apns-id"]}),o.on("data",(c)=>{a+=c.toString()}),o.on("end",()=>{if(d===200)i({success:!0,deviceToken:e.deviceToken,apnsId:l,statusCode:d});else{let c="Unknown error",g,u;if(a)try{let p=JSON.parse(a);g=p.reason,u=p.timestamp,c=g||c}catch{c=a}i({success:!1,deviceToken:e.deviceToken,apnsId:l,statusCode:d,error:c,reason:g,timestamp:u})}}),o.on("error",(c)=>{i({success:!1,deviceToken:e.deviceToken,error:c.message})}),o.write(r),o.end()})}catch(t){return{success:!1,deviceToken:e.deviceToken,error:t.message}}}async sendBatch(e,t){let s=t?.concurrency||10,r=[];for(let n=0;n<e.length;n+=s){let i=e.slice(n,n+s),o=await Promise.all(i.map((a)=>this.send(a)));r.push(...o)}return{sent:r.filter((n)=>n.success).length,failed:r.filter((n)=>!n.success).length,results:r}}async sendSimple(e,t,s,r){return this.send({deviceToken:e,title:t,body:s,data:r})}async sendSilent(e,t){return this.send({deviceToken:e,contentAvailable:!0,pushType:"background",priority:5,data:t})}close(){if(this.client)this.client.close(),this.client=null;this.token=null,this.tokenGeneratedAt=0}}import{createSign as T}from"node:crypto";var C="https://fcm.googleapis.com/v1/projects",y="https://oauth2.googleapis.com/token",P=3300000;class m{config;accessToken=null;tokenExpiresAt=0;constructor(e){this.config=e}static fromServiceAccount(e){return new m({projectId:e.project_id,clientEmail:e.client_email,privateKey:e.private_key})}generateJWT(){let e=Math.floor(Date.now()/1000),t=e+3600,s={alg:"RS256",typ:"JWT"},r={iss:this.config.clientEmail,sub:this.config.clientEmail,aud:y,iat:e,exp:t,scope:"https://www.googleapis.com/auth/firebase.messaging"},n=Buffer.from(JSON.stringify(s)).toString("base64url"),i=Buffer.from(JSON.stringify(r)).toString("base64url"),o=`${n}.${i}`,a=T("SHA256");a.update(o);let d=a.sign(this.config.privateKey,"base64url");return`${o}.${d}`}async getAccessToken(){if(this.accessToken&&Date.now()<this.tokenExpiresAt)return this.accessToken;let e=this.generateJWT(),t=await fetch(y,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"urn:ietf:params:oauth:grant-type:jwt-bearer",assertion:e}).toString()});if(!t.ok){let r=await t.text();throw Error(`Failed to get access token: ${r}`)}let s=await t.json();return this.accessToken=s.access_token,this.tokenExpiresAt=Date.now()+P,this.accessToken}buildMessage(e){let t={};if(e.token)t.token=e.token;else if(e.topic)t.topic=e.topic;else if(e.condition)t.condition=e.condition;if(e.title||e.body||e.imageUrl){if(t.notification={},e.title)t.notification.title=e.title;if(e.body)t.notification.body=e.body;if(e.imageUrl)t.notification.image=e.imageUrl}if(e.data&&Object.keys(e.data).length>0)t.data=e.data;if(e.android){if(t.android={priority:e.android.priority||"high"},e.android.ttl)t.android.ttl=`${e.android.ttl}s`;if(e.android.collapseKey)t.android.collapse_key=e.android.collapseKey;if(e.android.directBootOk)t.android.direct_boot_ok=e.android.directBootOk;let s={};if(e.android.channelId)s.channel_id=e.android.channelId;if(e.android.icon)s.icon=e.android.icon;if(e.android.color)s.color=e.android.color;if(e.android.sound)s.sound=e.android.sound;if(e.android.clickAction)s.click_action=e.android.clickAction;if(e.android.tag)s.tag=e.android.tag;if(e.android.visibility)s.visibility=e.android.visibility;if(e.android.notificationCount!==void 0)s.notification_count=e.android.notificationCount;if(Object.keys(s).length>0)t.android.notification=s}if(e.webpush)t.webpush=e.webpush;if(e.apns)t.apns=e.apns;if(e.fcmOptions)t.fcm_options=e.fcmOptions;return{message:t}}async send(e){try{let t=await this.getAccessToken(),s=this.buildMessage(e),r=await fetch(`${C}/${this.config.projectId}/messages:send`,{method:"POST",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"},body:JSON.stringify(s)}),n=await r.json();if(r.ok)return{success:!0,messageId:n.name};else return{success:!1,error:n.error?.message||"Unknown error",errorCode:n.error?.status}}catch(t){return{success:!1,error:t.message}}}async sendBatch(e,t,s){let r=s?.concurrency||10,n=[];for(let i=0;i<e.length;i+=r){let a=e.slice(i,i+r).map(async(l)=>({...await this.send({...t,token:l}),token:l})),d=await Promise.all(a);n.push(...d)}return{sent:n.filter((i)=>i.success).length,failed:n.filter((i)=>!i.success).length,results:n}}async sendToTopic(e,t){return this.send({...t,topic:e})}async sendToCondition(e,t){return this.send({...t,condition:e})}async sendSimple(e,t,s,r){return this.send({token:e,title:t,body:s,data:r})}async sendSilent(e,t){return this.send({token:e,data:t,android:{priority:"high"}})}async subscribeToTopic(e,t){try{let s=await this.getAccessToken(),r=await fetch("https://iid.googleapis.com/iid/v1:batchAdd",{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:JSON.stringify({to:`/topics/${t}`,registration_tokens:e})});if(r.ok)return{success:!0};else return{success:!1,error:(await r.json()).error?.message||"Failed to subscribe"}}catch(s){return{success:!1,error:s.message}}}async unsubscribeFromTopic(e,t){try{let s=await this.getAccessToken(),r=await fetch("https://iid.googleapis.com/iid/v1:batchRemove",{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:JSON.stringify({to:`/topics/${t}`,registration_tokens:e})});if(r.ok)return{success:!0};else return{success:!1,error:(await r.json()).error?.message||"Failed to unsubscribe"}}catch(s){return{success:!1,error:s.message}}}}export{R as APNsClient,m as FCMClient};
|
package/dist/spend/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$i as
|
|
1
|
+
import{$i as c,$j as da,Aj as D,Bj as E,Cj as F,Dj as G,Ej as H,Fj as I,Fk as qa,Gj as J,Gk as ra,Hj as K,Hk as sa,Ij as L,Ik as ta,Jj as M,Jk as ua,Kj as N,Kk as va,Lj as O,Lk as wa,Mj as P,Mk as xa,Nj as Q,Nk as ya,Oj as R,Ok as za,Pj as S,Pk as Aa,Qj as T,Qk as Ba,Rj as U,Sj as V,Tj as W,Uj as X,Uk as Ca,Vj as Y,Vk as Da,Wj as Z,Xj as _,Yj as aa,Zi as a,Zj as ba,_i as b,_j as ca,aj as d,ak as ea,bj as e,bk as fa,cj as f,ck as ga,dj as g,dk as ha,ej as h,ek as ia,fj as i,fk as ja,gj as j,gk as ka,hj as k,hk as la,ij as l,ik as ma,jj as m,jk as na,kj as n,kk as oa,lj as o,lk as pa,mj as p,nj as q,oj as r,pj as s,qj as t,rj as u,sj as v,tj as w,uj as x,vj as y,wj as z,xj as A,yj as B,zj as C}from"../chunk-kv2ztkp4.js";import"../chunk-cd0zpxhf.js";import"../chunk-06jk7ba9.js";import"../chunk-77t2ctwn.js";export{Z as AnomalyConfigStore,za as AwsSpendTransport,Aa as ComputeSpendTransport,x as CounterTracker,u as DEFAULT_METER_MAPPINGS,l as DEFAULT_PRICE_BOOK,B as DEFAULT_THRESHOLDS,_ as DETECTABLE_SIGNALS,c as ENFORCEMENT_ACTIONS,K as ENFORCEMENT_DESCRIPTIONS,d as ENFORCEMENT_SEVERITY,ea as FOCUS_VERSION,b as METERS,a as METER_KEYS,e as MICRO_CENTS,G as MIN_PROJECTION_CONFIDENCE,pa as RecordingSpendTransport,X as SENSITIVITY_THRESHOLDS,xa as SPEND_CYCLE_SECONDS,ka as SPEND_GATE_SETTING,ua as SPEND_LEASE_SETTING,ba as SignalSource,ma as SpendCapError,la as SpendGate,va as SpendLoopLease,ta as SpendNotificationRouter,wa as SpendRunner,ia as SpendService,C as SpendStore,qa as URGENT_SPEND_EVENTS,L as USER_VISIBLE_ACTIONS,z as aggregateDeltas,W as anomalyOptionsForSignal,na as assertSpendAllows,q as budgetWindow,Ba as compositeSpendTransport,oa as createEnforcementHandlers,U as detectAnomalies,V as detectLatestAnomaly,I as evaluateBudget,T as ewma,v as findMapping,g as findPriceEntry,ra as formatCents,F as formatTimeToExhaustion,H as governingLimitCents,Ca as handleSpendRequest,s as hourBucket,t as hourBucketsBetween,P as isOperationAllowed,m as isValidTimeZone,da as lookbackHoursForSignal,Q as median,R as medianAbsoluteDeviation,J as mergeDecisions,k as mergePriceBooks,y as meterTelemetry,ca as optionsForSignal,E as percentOfLimit,M as planEnforcement,r as previousBudgetWindow,h as priceUsage,i as priceUsageLines,D as projectSpend,S as robustZScore,j as roundCents,Y as routeMatches,N as runEnforcement,A as scopeKey,aa as signalDefinition,sa as spendNotificationText,Da as spendOpenApiPaths,ya as startSpendLoop,ha as streamFocusJsonl,O as strongestActiveAction,f as tieredMicroCents,ga as toFocusJsonl,fa as toFocusRecords,w as usageDeltaKey,p as utcInstantOfLocal,ja as zeroFill,o as zoneOffsetMs,n as zonedParts};
|
package/dist/ui/404.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en" data-theme="dark"><head><meta charset="UTF-8"><meta name="stx-build" content="
|
|
1
|
+
<!DOCTYPE html><html lang="en" data-theme="dark"><head><meta charset="UTF-8"><meta name="stx-build" content="mt3hwv4ydcyeyt"><meta name="title" content="stx Project"><meta name="description" content="A website built with stx templating engine"><meta property="og:title" content="stx Project"><meta property="og:description" content="A website built with stx templating engine"><meta property="og:type" content="website"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="stx Project"><meta name="twitter:description" content="A website built with stx templating engine"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>ts-cloud - Accept invitation</title><link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect x='3' y='3' width='26' height='26' rx='8' fill='%235a8be0'/%3E%3C/svg%3E"><link rel="preconnect" href="https://fonts.googleapis.com"><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"><link rel="stylesheet" href="/_stx/style.448ab192.css"><style data-stx-cloak>[x-cloak]{display:none !important}</style><link data-crosswind="generated" rel="stylesheet" href="/_stx/crosswind.616c25f3.css"></head><body class="auth-page"><main class="auth-card" id="main-content"><a class="brand" href="/"><span class="dot"></span><span>ts-cloud</span></a><div><span class="context-label">Organization invitation</span><h1>Join your team</h1><p>Choose the dashboard username and password you will use after this invitation is accepted.</p></div><form id="accept-invitation-form" class="auth-form"><div class="field-block"><label for="invite-name">Name</label><input id="invite-name" name="name" autocomplete="name" placeholder="Chris Breuer"></div><div class="field-block"><label for="invite-username">Username</label><input id="invite-username" name="username" autocomplete="username" minlength="2" maxlength="32" required></div><div class="field-block"><label for="invite-password">Password</label><input id="invite-password" name="password" type="password" autocomplete="new-password" minlength="12" required><small>At least 12 characters.</small></div><button class="btn" type="submit">Accept invitation</button><div id="invitation-status" class="auth-status" role="status"></div></form><p class="note">If this link expired or was already used, ask an organization owner to resend the invitation.</p></main> <script data-stx-scoped data-stx-run="always">
|
|
2
2
|
;(function() {
|
|
3
3
|
'use strict';
|
|
4
4
|
(() => {
|