unoverse 0.1.131 → 0.1.133
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/operator/lib/deploy.sh +78 -23
- package/package.json +1 -1
package/operator/lib/deploy.sh
CHANGED
|
@@ -41,8 +41,12 @@
|
|
|
41
41
|
# exists because the opposite mistake is what wiped this exact cluster's firewall once
|
|
42
42
|
# already: an authoritative write that assumed the whole list was ours to own.
|
|
43
43
|
_operator_db_access() {
|
|
44
|
-
|
|
45
|
-
cluster
|
|
44
|
+
# `$2` is the cluster, when the CALLER already knows it. The monorepo has no ground to
|
|
45
|
+
# read one from — it develops against a cluster named only in .env — so re-deriving it
|
|
46
|
+
# from terraform.tfvars here meant this returned 0 and did nothing in the one place the
|
|
47
|
+
# developer actually types `npm run dev`.
|
|
48
|
+
local cloud="$1" dir="$ROOT/infra/$cloud" cluster="${2:-}"
|
|
49
|
+
[ -n "$cluster" ] || cluster=$(grep -E '^existing_pg_cluster_name[[:space:]]*=' "$dir/terraform.tfvars" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
|
|
46
50
|
[ -n "$cluster" ] || return 0
|
|
47
51
|
[ -n "${DIGITALOCEAN_TOKEN:-}" ] || return 0
|
|
48
52
|
mkdir -p "$ROOT/.unoverse"
|
|
@@ -55,9 +59,51 @@ const H = { Authorization: `Bearer ${T}`, "Content-Type": "application/json" };
|
|
|
55
59
|
const api = (p, o = {}) => fetch(`https://api.digitalocean.com/v2${p}`, { headers: H, ...o });
|
|
56
60
|
const read = () => { try { return fs.readFileSync(ledgerPath, "utf8").split("\n").map(s => s.trim()).filter(Boolean); } catch { return []; } };
|
|
57
61
|
|
|
62
|
+
/**
|
|
63
|
+
* A MACHINE IS A SET OF ADDRESSES, NOT ONE.
|
|
64
|
+
*
|
|
65
|
+
* This asked "what is my IP?" once and registered the answer, deleting whatever it had
|
|
66
|
+
* registered before. That is correct for one stable address and actively harmful without
|
|
67
|
+
* one: a connection that leaves through a different egress than the probe did arrives
|
|
68
|
+
* from an address nobody trusted. Observed live 2026-08-10 on a laptop whose ISP
|
|
69
|
+
* alternates two addresses roughly evenly — six probes returned three of each. Each run
|
|
70
|
+
* registered whichever it drew and REMOVED the other, so running the command again
|
|
71
|
+
* (exactly what a locked-out developer does) deleted the rule that was working, and the
|
|
72
|
+
* database stayed unreachable through both.
|
|
73
|
+
*
|
|
74
|
+
* So probe several times and register the DISTINCT SET. Replacement is unchanged: the
|
|
75
|
+
* previous set still goes, so rules never pile up. One address for a normal machine, two
|
|
76
|
+
* for a flapping one, and the developer never has to know which they are.
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* EACH PROBE MUST BE ITS OWN CONNECTION, or the loop is theatre. `fetch` keeps the
|
|
80
|
+
* connection alive, so six calls to one host ride ONE socket and one egress, and the
|
|
81
|
+
* probe reports a single address on the very machine that has two. Measured: six
|
|
82
|
+
* keep-alive probes returned the same address six times, while six separate `curl`
|
|
83
|
+
* processes returned three of each. `Connection: close` retires the socket, and rotating
|
|
84
|
+
* the endpoint keeps a single provider's own routing from deciding the answer.
|
|
85
|
+
*/
|
|
86
|
+
const ENDPOINTS = ["https://api.ipify.org", "https://icanhazip.com", "https://ifconfig.me/ip"];
|
|
87
|
+
const PROBES = 9;
|
|
88
|
+
const egress = async () => {
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
for (let i = 0; i < PROBES; i++) {
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch(ENDPOINTS[i % ENDPOINTS.length], {
|
|
93
|
+
headers: { connection: "close" },
|
|
94
|
+
signal: AbortSignal.timeout(5000),
|
|
95
|
+
});
|
|
96
|
+
const v = (await res.text()).trim();
|
|
97
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(v)) seen.add(v);
|
|
98
|
+
} catch {}
|
|
99
|
+
}
|
|
100
|
+
return [...seen];
|
|
101
|
+
};
|
|
102
|
+
|
|
58
103
|
(async () => {
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
104
|
+
const ips = await egress();
|
|
105
|
+
if (!ips.length) return;
|
|
106
|
+
const isOurs = (v) => ips.includes(v);
|
|
61
107
|
|
|
62
108
|
const list = await (await api("/databases")).json();
|
|
63
109
|
const db = (list.databases || []).find((d) => d.name === cluster);
|
|
@@ -67,26 +113,28 @@ const read = () => { try { return fs.readFileSync(ledgerPath, "utf8").split("\n"
|
|
|
67
113
|
let rules = (fw.rules || []).map((r) => ({ type: r.type, value: r.value }));
|
|
68
114
|
|
|
69
115
|
const ours = read(); // only these may be removed
|
|
70
|
-
const stale = ours.filter((v) => v
|
|
71
|
-
const
|
|
116
|
+
const stale = ours.filter((v) => !isOurs(v));
|
|
117
|
+
const missing = ips.filter((v) => !rules.some((r) => r.type === "ip_addr" && r.value === v));
|
|
118
|
+
const label = ips.join(", ");
|
|
72
119
|
// SAY SO EVEN WHEN NOTHING CHANGES. Returning silently made the command look like it had
|
|
73
120
|
// failed: the developer typed it because they could not connect, and got a blank line.
|
|
74
|
-
if (
|
|
75
|
-
fs.writeFileSync(ledgerPath,
|
|
76
|
-
console.log(` \x1b[32m✓\x1b[0m This machine (${
|
|
121
|
+
if (!missing.length && !stale.length) {
|
|
122
|
+
fs.writeFileSync(ledgerPath, ips.join("\n") + "\n");
|
|
123
|
+
console.log(` \x1b[32m✓\x1b[0m This machine (${label}) can already reach ${cluster} \x1b[2m(nothing changed)\x1b[0m`);
|
|
77
124
|
return;
|
|
78
125
|
}
|
|
79
126
|
|
|
80
127
|
rules = rules.filter((r) => !(r.type === "ip_addr" && stale.includes(r.value)));
|
|
81
|
-
|
|
128
|
+
for (const v of missing) rules.push({ type: "ip_addr", value: v });
|
|
82
129
|
|
|
83
130
|
const res = await api(`/databases/${db.id}/firewall`, { method: "PUT", body: JSON.stringify({ rules }) });
|
|
84
131
|
if (!res.ok) {
|
|
85
|
-
console.log(` \x1b[33m!\x1b[0m Could not update ${cluster}'s trusted sources — add ${
|
|
132
|
+
console.log(` \x1b[33m!\x1b[0m Could not update ${cluster}'s trusted sources — add ${label} by hand`);
|
|
86
133
|
return;
|
|
87
134
|
}
|
|
88
|
-
fs.writeFileSync(ledgerPath,
|
|
89
|
-
|
|
135
|
+
fs.writeFileSync(ledgerPath, ips.join("\n") + "\n");
|
|
136
|
+
const changed = ips.length > 1 ? `${ips.length} addresses for this machine` : "only ours changed";
|
|
137
|
+
console.log(` \x1b[32m✓\x1b[0m This machine (${label}) may reach ${cluster} \x1b[2m(${rules.length} trusted sources, ${changed})\x1b[0m`);
|
|
90
138
|
})().catch(() => {});
|
|
91
139
|
NODE
|
|
92
140
|
}
|
|
@@ -108,16 +156,21 @@ cmd_db_allow() {
|
|
|
108
156
|
# reachability is a VPC security group, owned by Terraform and changed by `deploy`, not by
|
|
109
157
|
# a developer's laptop asking an API at runtime. Offering `db-allow aws` produced a
|
|
110
158
|
# ground-picker prompt for a cloud that would then have done nothing.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
159
|
+
# A GROUND IS OPTIONAL HERE. This used to `return 1` the moment the picker found none,
|
|
160
|
+
# which made the .env fallback below unreachable in the monorepo — the one checkout that
|
|
161
|
+
# has no ground and the one the fallback was written for. Only ask the picker when there
|
|
162
|
+
# is something to pick, so an ambiguous two-ground universe still stops, while a
|
|
163
|
+
# groundless checkout proceeds to look the cluster up where it actually knows it.
|
|
164
|
+
cloud=""
|
|
165
|
+
if ls "$ROOT"/infra/*/terraform.tfvars >/dev/null 2>&1; then
|
|
166
|
+
SELF_CMD="unoverse db-allow" \
|
|
167
|
+
GROUNDS_ONLY="digitalocean" \
|
|
168
|
+
GROUNDS_ONLY_WHY="On AWS the database is reached through its VPC security group, which the ground owns — change it in infra/aws and run ${BOLD}unoverse deploy aws${NC}." \
|
|
169
|
+
cloud=$(_pick_ground "${1:-}") || return 1
|
|
170
|
+
fi
|
|
118
171
|
|
|
119
|
-
local cluster
|
|
120
|
-
cluster=$(grep -E '^existing_pg_cluster_name[[:space:]]*=' "$ROOT/infra/$cloud/terraform.tfvars" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
|
|
172
|
+
local cluster=""
|
|
173
|
+
[ -n "$cloud" ] && cluster=$(grep -E '^existing_pg_cluster_name[[:space:]]*=' "$ROOT/infra/$cloud/terraform.tfvars" 2>/dev/null | sed -E 's/.*"([^"]+)".*/\1/')
|
|
121
174
|
|
|
122
175
|
# THE MONOREPO NEEDS THIS TOO, and it has no ground. The platform's own checkout is
|
|
123
176
|
# developed against a managed cluster named only in .env, so a ground-only lookup found
|
|
@@ -147,7 +200,9 @@ cmd_db_allow() {
|
|
|
147
200
|
fi
|
|
148
201
|
|
|
149
202
|
echo ""
|
|
150
|
-
|
|
203
|
+
# Hand the cluster over: we already resolved it, from a ground OR from .env, and the
|
|
204
|
+
# helper cannot re-derive the .env case (there is no tfvars to read).
|
|
205
|
+
_operator_db_access "$cloud" "$cluster"
|
|
151
206
|
echo ""
|
|
152
207
|
info "Run this again whenever you change network"
|
|
153
208
|
echo ""
|
package/package.json
CHANGED