Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions scripts/ops/fix-etsy-api-key.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@
* Anything else (a stray email address someone typed into the field) is left
* for its owner: we cannot invent the missing half.
*
* Run it inside the app container, which holds ENCRYPTION_KEY and DATABASE_URL:
* Run it inside the backend container, which holds ENCRYPTION_KEY and DATABASE_URL:
*
* docker cp scripts/ops/fix-etsy-api-key.mjs amcp-cloud-app:/app/backend/fix-etsy.mjs
* docker exec -w /app/backend amcp-cloud-app node fix-etsy.mjs # dry run
* docker exec -w /app/backend amcp-cloud-app node fix-etsy.mjs --apply
* docker restart amcp-cloud-app # the tool registry caches connector config
* docker cp scripts/ops/fix-etsy-api-key.mjs amcp-cloud-backend:/app/backend/fix-etsy.mjs
* docker exec -w /app/backend amcp-cloud-backend node fix-etsy.mjs # dry run
* docker exec -w /app/backend amcp-cloud-backend node fix-etsy.mjs --apply
* # These three on the droplet host, not in the container. The restart takes the API
* # down for ~30-60 s; silence the uptime probe first. It honours an expiry
* # epoch in this file (deploy/cloud/uptime-probe.sh, as deploy-cloud.yml
* # does), so a forgotten marker lapses by itself after 10 minutes:
* mkdir -p /var/lib/anythingmcp-probe && echo $(( $(date -u +%s) + 600 )) > /var/lib/anythingmcp-probe/maintenance
* docker restart amcp-cloud-backend # the tool registry caches connector config
* rm -f /var/lib/anythingmcp-probe/maintenance
*
* Secrets never reach stdout: it prints connector ids and which case applied.
*/
Expand All @@ -43,7 +49,7 @@ const SECRET_VAR = '{{ETSY_CLIENT_SECRET}}';

const KEY = process.env.ENCRYPTION_KEY;
if (!KEY) {
console.error('ENCRYPTION_KEY is not set — run this inside the app container.');
console.error('ENCRYPTION_KEY is not set — run this inside the backend container.');
process.exit(1);
}

Expand Down Expand Up @@ -155,6 +161,6 @@ console.log(
`\n${APPLY ? 'Patched' : 'Would patch'} ${patched} of ${rows.length} Etsy connectors ` +
`(${skipped} left alone).`,
);
if (!APPLY && patched > 0) console.log('Re-run with --apply, then restart the app.');
if (!APPLY && patched > 0) console.log('Re-run with --apply, then restart the backend (see the header).');

await prisma.$disconnect();
20 changes: 13 additions & 7 deletions scripts/ops/fix-reddit-oauth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,18 @@
* catalog no longer ships are soft-deprecated exactly as a full catalog
* re-sync would (deprecatedAt set, disabled; nothing is deleted).
*
* Run it inside the app container, which holds ENCRYPTION_KEY and DATABASE_URL:
* Run it inside the backend container, which holds ENCRYPTION_KEY and DATABASE_URL:
*
* docker cp scripts/ops/fix-reddit-oauth.mjs amcp-cloud-app:/app/backend/fix-reddit.mjs
* docker exec -w /app/backend amcp-cloud-app node fix-reddit.mjs # dry run
* docker exec -w /app/backend amcp-cloud-app node fix-reddit.mjs --apply
* docker restart amcp-cloud-app # the tool registry caches connector config
* docker cp scripts/ops/fix-reddit-oauth.mjs amcp-cloud-backend:/app/backend/fix-reddit.mjs
* docker exec -w /app/backend amcp-cloud-backend node fix-reddit.mjs # dry run
* docker exec -w /app/backend amcp-cloud-backend node fix-reddit.mjs --apply
* # These three on the droplet host, not in the container. The restart takes the API
* # down for ~30-60 s; silence the uptime probe first. It honours an expiry
* # epoch in this file (deploy/cloud/uptime-probe.sh, as deploy-cloud.yml
* # does), so a forgotten marker lapses by itself after 10 minutes:
* mkdir -p /var/lib/anythingmcp-probe && echo $(( $(date -u +%s) + 600 )) > /var/lib/anythingmcp-probe/maintenance
* docker restart amcp-cloud-backend # the tool registry caches connector config
* rm -f /var/lib/anythingmcp-probe/maintenance
*
* Secrets never reach stdout: it prints connector ids, which case applied, and
* whether the client ID/secret are present, never their values.
Expand All @@ -61,7 +67,7 @@ const CATALOG_TOOLS = new Set([

const KEY = process.env.ENCRYPTION_KEY;
if (!KEY) {
console.error('ENCRYPTION_KEY is not set — run this inside the app container.');
console.error('ENCRYPTION_KEY is not set — run this inside the backend container.');
process.exit(1);
}

Expand Down Expand Up @@ -207,6 +213,6 @@ console.log(
`\n${APPLY ? 'Patched' : 'Would patch'} ${patched} of ${rows.length} Reddit connectors ` +
`(${skipped} unchanged or left alone). ${needCustomer} still need the customer's client ID and secret.`,
);
if (!APPLY && patched > 0) console.log('Re-run with --apply, then restart the app.');
if (!APPLY && patched > 0) console.log('Re-run with --apply, then restart the backend (see the header).');

await prisma.$disconnect();
169 changes: 0 additions & 169 deletions scripts/ops/migrate-amadeus-client-credentials.mjs

This file was deleted.

16 changes: 14 additions & 2 deletions scripts/ops/migrate-deutsche-bahn-cloud.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
* node scripts/ops/migrate-deutsche-bahn-cloud.mjs http://motis:8080 > /tmp/db.sql
* scp /tmp/db.sql root@<droplet>:/tmp/db.sql
* ssh root@<droplet> 'docker exec -i amcp-cloud-postgres psql -U amcp -d anythingmcp -v ON_ERROR_STOP=1 -1 -f - < /tmp/db.sql'
* ssh root@<droplet> 'docker restart amcp-cloud-app' # the tool registry is in memory
* # The restart takes the API down for ~30-60 s; silence the uptime probe
* # first. It honours an expiry
* # epoch in this file (deploy/cloud/uptime-probe.sh, as deploy-cloud.yml
* # does), so a forgotten marker lapses by itself after 10 minutes:
* ssh root@<droplet> 'mkdir -p /var/lib/anythingmcp-probe && echo $(( $(date -u +%s) + 600 )) > /var/lib/anythingmcp-probe/maintenance'
* ssh root@<droplet> 'docker restart amcp-cloud-backend' # the tool registry is in memory
* ssh root@<droplet> 'rm -f /var/lib/anythingmcp-probe/maintenance'
*
* Why not the catalog re-sync: the change is structural (every endpoint
* moved) so the boot-time reconciler will not apply it, the per-connector
Expand Down Expand Up @@ -75,9 +81,15 @@ const json = (v) => `${lit(JSON.stringify(v))}::jsonb`;
const out = [];
out.push('-- deutsche-bahn: db-rest → MOTIS. Generated by scripts/ops/migrate-deutsche-bahn-cloud.mjs');
out.push(`-- adapterVersion ${adapterVersion}, MOTIS at ${motisUrl}`);
// Safe to re-run: a connector already on this adapterVersion and this MOTIS
// URL is left out of db_targets, so a second run reports it as already current
// and writes nothing (updated_at included).
const matches = `(config->>'adapterSlug' = 'deutsche-bahn' OR base_url LIKE ${lit(OLD_BASE + '%')})`;
const isCurrent = `(COALESCE(config->>'adapterVersion', '') = ${lit(adapterVersion)} AND base_url = ${lit(motisUrl)})`;
out.push('CREATE TEMP TABLE db_targets AS');
out.push(' SELECT id FROM connectors');
out.push(` WHERE config->>'adapterSlug' = 'deutsche-bahn' OR base_url LIKE ${lit(OLD_BASE + '%')};`);
out.push(` WHERE ${matches} AND NOT ${isCurrent};`);
out.push(`SELECT count(*) AS already_current FROM connectors WHERE ${matches} AND ${isCurrent};`);
out.push("SELECT count(*) AS connectors_to_migrate FROM db_targets;");

out.push('UPDATE connectors SET');
Expand Down
79 changes: 70 additions & 9 deletions scripts/ops/migrate-vinted-cloud.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,27 @@
* A connector is recognised by `config.adapterSlug`, or — for installs that
* predate the baseline — by a www.vinted.* base URL.
*
* Run it inside the app container, which holds ENCRYPTION_KEY and DATABASE_URL,
* Safe to re-run. A connector already at the target — base URL, LOGIN_TOKEN
* with this adapter's authConfig, `config.adapterVersion` equal to this
* adapter's, every catalog tool present, live and matching the catalog
* (description, parameters, endpoint, useProxy off), and no live tool the
* catalog dropped — is listed as "already current" and not written. Without
* this check a dry run after --apply listed every connector as still to do.
*
* Run it inside the backend container, which holds ENCRYPTION_KEY and DATABASE_URL,
* after the release carrying the new adapter is deployed:
*
* docker cp scripts/ops/migrate-vinted-cloud.mjs amcp-cloud-app:/app/backend/migrate-vinted.mjs
* docker cp packages/backend/src/adapters/intl/vinted.json amcp-cloud-app:/app/backend/vinted.json
* docker exec -w /app/backend amcp-cloud-app node migrate-vinted.mjs vinted.json # dry run
* docker exec -w /app/backend amcp-cloud-app node migrate-vinted.mjs vinted.json --apply
* docker restart amcp-cloud-app # the tool registry caches connector config
* docker cp scripts/ops/migrate-vinted-cloud.mjs amcp-cloud-backend:/app/backend/migrate-vinted.mjs
* docker cp packages/backend/src/adapters/intl/vinted.json amcp-cloud-backend:/app/backend/vinted.json
* docker exec -w /app/backend amcp-cloud-backend node migrate-vinted.mjs vinted.json # dry run
* docker exec -w /app/backend amcp-cloud-backend node migrate-vinted.mjs vinted.json --apply
* # These three on the droplet host, not in the container. The restart takes the API
* # down for ~30-60 s; silence the uptime probe first. It honours an expiry
* # epoch in this file (deploy/cloud/uptime-probe.sh, as deploy-cloud.yml
* # does), so a forgotten marker lapses by itself after 10 minutes:
* mkdir -p /var/lib/anythingmcp-probe && echo $(( $(date -u +%s) + 600 )) > /var/lib/anythingmcp-probe/maintenance
* docker restart amcp-cloud-backend # the tool registry caches connector config
* rm -f /var/lib/anythingmcp-probe/maintenance
*
* Prints connector ids and what changed; never secrets.
*/
Expand All @@ -57,7 +70,7 @@ if (adapter.slug !== 'vinted' || adapter.connector.authType !== 'LOGIN_TOKEN') {

const KEY = process.env.ENCRYPTION_KEY;
if (!KEY) {
console.error('ENCRYPTION_KEY is not set — run this inside the app container.');
console.error('ENCRYPTION_KEY is not set — run this inside the backend container.');
process.exit(1);
}

Expand Down Expand Up @@ -137,9 +150,53 @@ const rows = await prisma.connector.findMany({
const catalogByName = new Map(adapter.tools.map((t) => [t.name, t]));
const authConfig = encrypt(JSON.stringify(adapter.connector.authConfig));
const hasMapping = (m) => m !== null && m !== undefined;
const same = (a, b) => JSON.stringify(canonicalize(a ?? null)) === JSON.stringify(canonicalize(b ?? null));
const targetAuth = canonicalize(adapter.connector.authConfig);

/** Stored authConfig equals the adapter's. Undecryptable → not current. */
function authIsCurrent(stored) {
if (!stored) return false;
try {
return same(JSON.parse(decrypt(stored)), targetAuth);
} catch {
return false;
}
}

/** Every reason the row is not yet at the target state; empty when it is. */
function pendingChanges(c, cfg) {
const why = [];
if (c.baseUrl !== adapter.connector.baseUrl) why.push('baseUrl');
if (c.authType !== 'LOGIN_TOKEN' || !authIsCurrent(c.authConfig)) why.push('auth');
if (cfg.adapterSlug !== 'vinted' || cfg.adapterVersion !== adapterVersion) why.push('adapterVersion');
const byName = new Map(c.tools.map((t) => [t.name, t]));
for (const ct of adapter.tools) {
const et = byName.get(ct.name);
if (
!et ||
et.deprecatedAt ||
et.useProxy ||
et.description !== ct.description ||
!same(et.parameters, ct.parameters) ||
!same(et.endpointMapping, ct.endpointMapping)
) {
why.push(`tool ${ct.name}`);
}
}
for (const t of c.tools) if (!t.deprecatedAt && !catalogByName.has(t.name)) why.push(`tool -${t.name}`);
return why;
}

let migrated = 0;
let current = 0;

for (const c of rows) {
const cfg = c.config && typeof c.config === 'object' ? c.config : {};
if (pendingChanges(c, cfg).length === 0) {
console.log(`= ${c.id} — already current`);
current++;
continue;
}
const baseline = typeof cfg.instructionsBaseline === 'string' ? cfg.instructionsBaseline : null;
const userEdited = baseline !== null && baseline !== hashInstructions(c.instructions);
const notes = [`baseUrl ${c.baseUrl} → ${adapter.connector.baseUrl}`, `auth ${c.authType} → LOGIN_TOKEN`];
Expand Down Expand Up @@ -213,9 +270,13 @@ for (const c of rows) {
await prisma.$transaction([connectorUpdate, ...updates, ...creates, ...deprecates]);
}
console.log(`${APPLY ? '✓' : '→'} ${c.id} — ${notes.join(', ')}`);
migrated++;
}

console.log(`\n${APPLY ? 'Migrated' : 'Would migrate'} ${rows.length} Vinted connectors (adapterVersion ${adapterVersion}).`);
if (!APPLY && rows.length > 0) console.log('Re-run with --apply, then restart the app.');
console.log(
`\n${APPLY ? 'Migrated' : 'Would migrate'} ${migrated} of ${rows.length} Vinted connectors ` +
`(${current} already current; adapterVersion ${adapterVersion}).`,
);
if (!APPLY && migrated > 0) console.log('Re-run with --apply, then restart the backend (see the header).');

await prisma.$disconnect();
Loading
Loading