@waukeshamakerspace/db-kit 0.2.1 → 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.
- package/README.md +70 -6
- package/dist/admin.d.ts +60 -0
- package/dist/admin.d.ts.map +1 -1
- package/dist/admin.js +173 -1
- package/dist/admin.js.map +1 -1
- package/dist/bin/provision.d.ts +3 -0
- package/dist/bin/provision.d.ts.map +1 -0
- package/dist/bin/provision.js +102 -0
- package/dist/bin/provision.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -27,11 +27,12 @@ need no environment at all.
|
|
|
27
27
|
|
|
28
28
|
### Scripts
|
|
29
29
|
|
|
30
|
-
The
|
|
30
|
+
The admin scripts ship as binaries, so a service's `package.json` is:
|
|
31
31
|
|
|
32
32
|
```json
|
|
33
33
|
{
|
|
34
34
|
"scripts": {
|
|
35
|
+
"db:provision": "wmi-db-provision",
|
|
35
36
|
"db:ensure": "wmi-db-ensure",
|
|
36
37
|
"db:migrate": "wmi-db-migrate"
|
|
37
38
|
}
|
|
@@ -41,9 +42,71 @@ The two admin scripts ship as binaries, so a service's `package.json` is:
|
|
|
41
42
|
`wmi-db-migrate` takes the migrations folder as an optional argument
|
|
42
43
|
(`MIGRATIONS_DIR` also works), defaulting to `./migrations`.
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
That replaces the hard-coded
|
|
46
|
-
versions, which assumed every repo
|
|
45
|
+
`wmi-db-ensure` and `wmi-db-migrate` walk up from the cwd to the nearest `.env`
|
|
46
|
+
and say which file they loaded. That replaces the hard-coded
|
|
47
|
+
`resolve(cwd, '../../.env')` in the copied versions, which assumed every repo
|
|
48
|
+
was exactly two levels deep. **`wmi-db-provision` deliberately does not**, for
|
|
49
|
+
the reason below.
|
|
50
|
+
|
|
51
|
+
### Provisioning
|
|
52
|
+
|
|
53
|
+
`wmi-db-provision` creates a database and its two scoped users. It replaces the
|
|
54
|
+
block of manual SQL that each repo has been carrying in `docs/deploy.md`, so
|
|
55
|
+
dev and production get set up the same way.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
wmi-db-provision journeyman --admin-url mysql://admin:pw@rds.internal:3306
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
It creates, idempotently:
|
|
62
|
+
|
|
63
|
+
| | |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `` `journeyman` `` | `CREATE DATABASE IF NOT EXISTS` |
|
|
66
|
+
| `journeyman_migrator` | `ALTER, CREATE, DROP, INDEX, REFERENCES, SELECT, INSERT, UPDATE, DELETE` |
|
|
67
|
+
| `journeyman_app` | `SELECT, INSERT, UPDATE, DELETE` |
|
|
68
|
+
|
|
69
|
+
**The migrator holds DML as well as DDL on purpose.** Drizzle maintains its
|
|
70
|
+
`__drizzle_migrations` bookkeeping table over the same connection, so a migrator
|
|
71
|
+
granted DDL alone looks correct and then fails partway through the first
|
|
72
|
+
migration it tries to record.
|
|
73
|
+
|
|
74
|
+
**The app user never holds DDL**, so a bug cannot alter its own schema. Narrow
|
|
75
|
+
it further with `--app-privileges`, which accepts only those four DML
|
|
76
|
+
privileges and refuses anything else:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Journeyman's Lambda user: its append-only events table cannot be rewritten
|
|
80
|
+
wmi-db-provision journeyman --admin-url ... --app-privileges SELECT,INSERT
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Both passwords are generated here, printed once, and never written to disk. The
|
|
84
|
+
output is ready to paste: `DATABASE_URL` + `DATABASE_NAME` for the service's
|
|
85
|
+
`.env` and `samconfig.toml`, and the migrator's URI for the
|
|
86
|
+
`MIGRATOR_DATABASE_URL` GitHub secret.
|
|
87
|
+
|
|
88
|
+
`--dry-run` prints the exact statements and exits without connecting, which is
|
|
89
|
+
also how you hand the SQL to someone else to run.
|
|
90
|
+
|
|
91
|
+
Two things worth knowing before re-running it:
|
|
92
|
+
|
|
93
|
+
- **It rotates both passwords.** `CREATE USER IF NOT EXISTS` is a no-op for an
|
|
94
|
+
existing account, so the password is set with a following `ALTER USER`.
|
|
95
|
+
Without that a re-run would print credentials that do not work. Existing
|
|
96
|
+
deployments need the new values.
|
|
97
|
+
- **It revokes before granting**, so a re-run tightens an account that was
|
|
98
|
+
over-granted by hand rather than only widening it. Narrowing an existing
|
|
99
|
+
`journeyman_app` to `SELECT,INSERT` really does take `UPDATE` and `DELETE`
|
|
100
|
+
away.
|
|
101
|
+
|
|
102
|
+
The admin credential is passed every time, via `--admin-url` or
|
|
103
|
+
`WMI_DB_ADMIN_URL` (which keeps it out of shell history). This bin never reads
|
|
104
|
+
the service's `.env`, because a stray `DATABASE_URL` must not be able to decide
|
|
105
|
+
which server gets provisioned.
|
|
106
|
+
|
|
107
|
+
The generated SQL is verified against MySQL 8.4 in a container, not just
|
|
108
|
+
asserted as strings: the grants, the privilege boundary (the app user is
|
|
109
|
+
refused `CREATE`, `ALTER`, and `DROP`), password rotation, and narrowing.
|
|
47
110
|
|
|
48
111
|
### The convention
|
|
49
112
|
|
|
@@ -56,8 +119,9 @@ app connect to the same place as different users:
|
|
|
56
119
|
- `<service>_app` holds only the DML the service needs
|
|
57
120
|
|
|
58
121
|
Journeyman takes that further: its Lambda user has `SELECT` and `INSERT` only,
|
|
59
|
-
so its append-only events table cannot be rewritten even by a bug. That
|
|
60
|
-
|
|
122
|
+
so its append-only events table cannot be rewritten even by a bug. That is what
|
|
123
|
+
`wmi-db-provision --app-privileges SELECT,INSERT` produces. The convention used
|
|
124
|
+
to live as hand-written SQL in each repo's deploy doc; it is now executable.
|
|
61
125
|
|
|
62
126
|
## Notable fixes over the copied scripts
|
|
63
127
|
|
package/dist/admin.d.ts
CHANGED
|
@@ -12,6 +12,66 @@ export type Logger = {
|
|
|
12
12
|
* it is interpolated into DDL where binding cannot help.
|
|
13
13
|
*/
|
|
14
14
|
export declare function ensureDatabase(config: DatabaseConfig, logger?: Logger): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* The migrator's grants. DDL *and* DML, deliberately: Drizzle keeps its
|
|
17
|
+
* `__drizzle_migrations` bookkeeping table over the same connection, so a
|
|
18
|
+
* migrator holding DDL alone looks right and then fails partway through the
|
|
19
|
+
* first migration it records.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MIGRATOR_PRIVILEGES: readonly ["ALTER", "CREATE", "DROP", "INDEX", "REFERENCES", "SELECT", "INSERT", "UPDATE", "DELETE"];
|
|
22
|
+
/** The app's grants: row movement only, never schema. */
|
|
23
|
+
export declare const APP_PRIVILEGES: readonly ["SELECT", "INSERT", "UPDATE", "DELETE"];
|
|
24
|
+
export type ProvisionStatement = {
|
|
25
|
+
sql: string;
|
|
26
|
+
/**
|
|
27
|
+
* MySQL error numbers that mean "already in the desired state". Tolerated so
|
|
28
|
+
* a re-run converges instead of failing halfway.
|
|
29
|
+
*/
|
|
30
|
+
tolerate?: number[];
|
|
31
|
+
};
|
|
32
|
+
export type ProvisionedUser = {
|
|
33
|
+
user: string;
|
|
34
|
+
password: string;
|
|
35
|
+
/** Server URI carrying this user's credentials. No database path. */
|
|
36
|
+
url: string;
|
|
37
|
+
};
|
|
38
|
+
export type ProvisionPlan = {
|
|
39
|
+
name: string;
|
|
40
|
+
statements: ProvisionStatement[];
|
|
41
|
+
migrator: ProvisionedUser;
|
|
42
|
+
app: ProvisionedUser;
|
|
43
|
+
};
|
|
44
|
+
export type ProvisionOptions = {
|
|
45
|
+
/**
|
|
46
|
+
* Admin credential, server only. Passed in deliberately and never read from
|
|
47
|
+
* the service's own .env, so this cannot quietly provision whichever
|
|
48
|
+
* instance a stray DATABASE_URL happens to point at.
|
|
49
|
+
*/
|
|
50
|
+
adminUrl: string;
|
|
51
|
+
/** Database name. Validated before it reaches DDL. */
|
|
52
|
+
name: string;
|
|
53
|
+
/** Host part of both accounts. Defaults to `%`. */
|
|
54
|
+
grantHost?: string;
|
|
55
|
+
/** Narrow the app user, e.g. `['SELECT', 'INSERT']`. Defaults to all four DML. */
|
|
56
|
+
appPrivileges?: readonly string[];
|
|
57
|
+
/** Injectable for tests. Defaults to 24 random bytes, base64url. */
|
|
58
|
+
generatePassword?: () => string;
|
|
59
|
+
};
|
|
60
|
+
/** 192 bits, in an alphabet that needs no escaping in a URI or a SQL literal. */
|
|
61
|
+
export declare const generatePassword: () => string;
|
|
62
|
+
/**
|
|
63
|
+
* Build the provisioning plan without touching a server. Separated from
|
|
64
|
+
* execution so `--dry-run` prints exactly what would run, and so the statement
|
|
65
|
+
* order and grants are testable without a MySQL instance.
|
|
66
|
+
*/
|
|
67
|
+
export declare function planProvision(options: ProvisionOptions): ProvisionPlan;
|
|
68
|
+
/**
|
|
69
|
+
* Create the database and its two scoped users, idempotently.
|
|
70
|
+
*
|
|
71
|
+
* Re-running rotates both passwords and re-applies the grants, so the plan it
|
|
72
|
+
* prints is always the truth about how to connect afterwards.
|
|
73
|
+
*/
|
|
74
|
+
export declare function provisionDatabase(plan: ProvisionPlan, adminUrl: string, logger?: Logger): Promise<void>;
|
|
15
75
|
/**
|
|
16
76
|
* Apply pending Drizzle migrations.
|
|
17
77
|
*
|
package/dist/admin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAqC,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAErF,MAAM,MAAM,MAAM,GAAG;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CAAC;AAIzD;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAQf;AAUD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,qGAUtB,CAAC;AAEX,yDAAyD;AACzD,eAAO,MAAM,cAAc,mDAAoD,CAAC;AAgBhF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,eAAe,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,MAAM,CAAC;CACjC,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,QAAO,MAA+C,CAAC;AA6BpF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAkFtE;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,gBAAgB,EAAE,MAAM,EACxB,MAAM,GAAE,MAAsB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
package/dist/admin.js
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
// goes further: its Lambda user has SELECT+INSERT only, so the append-only
|
|
10
10
|
// events table cannot be rewritten even by a bug.
|
|
11
11
|
// ============================================================================
|
|
12
|
+
import { randomBytes } from 'node:crypto';
|
|
12
13
|
import { drizzle } from 'drizzle-orm/mysql2';
|
|
13
14
|
import { migrate } from 'drizzle-orm/mysql2/migrator';
|
|
14
15
|
import mysql from 'mysql2/promise';
|
|
15
|
-
import { databaseUri, serverUri } from './config.js';
|
|
16
|
+
import { SAFE_NAME, databaseUri, serverUri } from './config.js';
|
|
16
17
|
const consoleLogger = { info: (message) => console.log(message) };
|
|
17
18
|
/**
|
|
18
19
|
* Create the database if it does not exist.
|
|
@@ -33,6 +34,177 @@ export async function ensureDatabase(config, logger = consoleLogger) {
|
|
|
33
34
|
await connection.end();
|
|
34
35
|
}
|
|
35
36
|
}
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Provisioning: the database plus its two scoped users.
|
|
39
|
+
//
|
|
40
|
+
// This is the convention `shared/database.md` describes and that every repo
|
|
41
|
+
// has so far encoded by hand in a deploy doc. A `<db>_migrator` owns the
|
|
42
|
+
// schema, a `<db>_app` can only move rows through it.
|
|
43
|
+
// ============================================================================
|
|
44
|
+
/**
|
|
45
|
+
* The migrator's grants. DDL *and* DML, deliberately: Drizzle keeps its
|
|
46
|
+
* `__drizzle_migrations` bookkeeping table over the same connection, so a
|
|
47
|
+
* migrator holding DDL alone looks right and then fails partway through the
|
|
48
|
+
* first migration it records.
|
|
49
|
+
*/
|
|
50
|
+
export const MIGRATOR_PRIVILEGES = [
|
|
51
|
+
'ALTER',
|
|
52
|
+
'CREATE',
|
|
53
|
+
'DROP',
|
|
54
|
+
'INDEX',
|
|
55
|
+
'REFERENCES',
|
|
56
|
+
'SELECT',
|
|
57
|
+
'INSERT',
|
|
58
|
+
'UPDATE',
|
|
59
|
+
'DELETE',
|
|
60
|
+
];
|
|
61
|
+
/** The app's grants: row movement only, never schema. */
|
|
62
|
+
export const APP_PRIVILEGES = ['SELECT', 'INSERT', 'UPDATE', 'DELETE'];
|
|
63
|
+
/**
|
|
64
|
+
* What an app user may be narrowed to. An allowlist rather than a DDL denylist,
|
|
65
|
+
* so a privilege nobody thought about cannot slip through: Journeyman's Lambda
|
|
66
|
+
* user is SELECT+INSERT so its append-only events table cannot be rewritten
|
|
67
|
+
* even by a bug, and that is the shape of tightening this supports.
|
|
68
|
+
*/
|
|
69
|
+
const NARROWABLE_APP_PRIVILEGES = new Set(APP_PRIVILEGES);
|
|
70
|
+
/** base64url: safe unquoted in a URI and unescaped in a SQL string literal. */
|
|
71
|
+
const SAFE_SECRET = /^[A-Za-z0-9_-]+$/;
|
|
72
|
+
/** MySQL account name host part. `%` is any host, the RDS default. */
|
|
73
|
+
const SAFE_HOST = /^[A-Za-z0-9_%.:-]+$/;
|
|
74
|
+
/** 192 bits, in an alphabet that needs no escaping in a URI or a SQL literal. */
|
|
75
|
+
export const generatePassword = () => randomBytes(24).toString('base64url');
|
|
76
|
+
function assertServerOnly(url) {
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
parsed = new URL(url);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
throw new Error(`db-kit: admin URL is not a valid URI: ${JSON.stringify(url)}`);
|
|
83
|
+
}
|
|
84
|
+
if (parsed.pathname && parsed.pathname !== '/' && parsed.pathname !== '') {
|
|
85
|
+
throw new Error(`db-kit: admin URL must be the server only, with no database path (got "${parsed.pathname}").`);
|
|
86
|
+
}
|
|
87
|
+
return parsed;
|
|
88
|
+
}
|
|
89
|
+
function credentialUrl(admin, user, password) {
|
|
90
|
+
const url = new URL(admin.href);
|
|
91
|
+
url.username = user;
|
|
92
|
+
url.password = password;
|
|
93
|
+
url.pathname = '';
|
|
94
|
+
return url.href;
|
|
95
|
+
}
|
|
96
|
+
function grant(privileges, name, user, host) {
|
|
97
|
+
return `GRANT ${privileges.join(', ')} ON \`${name}\`.* TO '${user}'@'${host}'`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Build the provisioning plan without touching a server. Separated from
|
|
101
|
+
* execution so `--dry-run` prints exactly what would run, and so the statement
|
|
102
|
+
* order and grants are testable without a MySQL instance.
|
|
103
|
+
*/
|
|
104
|
+
export function planProvision(options) {
|
|
105
|
+
const name = options.name.trim();
|
|
106
|
+
if (!name)
|
|
107
|
+
throw new Error('db-kit: a database name is required');
|
|
108
|
+
// The name is interpolated into DDL, where binding cannot help.
|
|
109
|
+
if (!SAFE_NAME.test(name)) {
|
|
110
|
+
throw new Error(`db-kit: refusing an unsafe database name: ${JSON.stringify(name)}`);
|
|
111
|
+
}
|
|
112
|
+
const host = options.grantHost ?? '%';
|
|
113
|
+
if (!SAFE_HOST.test(host)) {
|
|
114
|
+
throw new Error(`db-kit: refusing an unsafe grant host: ${JSON.stringify(host)}`);
|
|
115
|
+
}
|
|
116
|
+
const appPrivileges = options.appPrivileges ?? APP_PRIVILEGES;
|
|
117
|
+
if (appPrivileges.length === 0) {
|
|
118
|
+
throw new Error('db-kit: the app user needs at least one privilege');
|
|
119
|
+
}
|
|
120
|
+
for (const privilege of appPrivileges) {
|
|
121
|
+
if (!NARROWABLE_APP_PRIVILEGES.has(privilege)) {
|
|
122
|
+
throw new Error(`db-kit: refusing to grant ${JSON.stringify(privilege)} to the app user. ` +
|
|
123
|
+
`It may hold only ${[...NARROWABLE_APP_PRIVILEGES].join(', ')}; schema changes belong to the migrator.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const admin = assertServerOnly(options.adminUrl);
|
|
127
|
+
const generate = options.generatePassword ?? generatePassword;
|
|
128
|
+
const migratorUser = `${name}_migrator`;
|
|
129
|
+
const appUser = `${name}_app`;
|
|
130
|
+
const migratorPassword = generate();
|
|
131
|
+
const appPassword = generate();
|
|
132
|
+
for (const secret of [migratorPassword, appPassword]) {
|
|
133
|
+
if (!SAFE_SECRET.test(secret)) {
|
|
134
|
+
throw new Error('db-kit: generated password contains characters that are unsafe unescaped');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const account = (user, password) => [
|
|
138
|
+
{ sql: `CREATE USER IF NOT EXISTS '${user}'@'${host}' IDENTIFIED BY '${password}'` },
|
|
139
|
+
// The CREATE above is a no-op for an existing account, so set the password
|
|
140
|
+
// explicitly. Otherwise a re-run prints credentials that do not work.
|
|
141
|
+
{ sql: `ALTER USER '${user}'@'${host}' IDENTIFIED BY '${password}'` },
|
|
142
|
+
// Grants are additive, so revoke first: re-running must be able to TIGHTEN
|
|
143
|
+
// an account that was over-granted by hand, not just widen it. 1141 is
|
|
144
|
+
// "no such grant defined", which is the expected state on a fresh account.
|
|
145
|
+
//
|
|
146
|
+
// Two statements, not one. `REVOKE ALL PRIVILEGES, GRANT OPTION` is a
|
|
147
|
+
// separate grammar that takes no ON clause and drops everything globally;
|
|
148
|
+
// combining it with `ON db.*` is a syntax error, which is what MySQL 8.4
|
|
149
|
+
// said when this was first written as one line.
|
|
150
|
+
{
|
|
151
|
+
sql: `REVOKE ALL PRIVILEGES ON \`${name}\`.* FROM '${user}'@'${host}'`,
|
|
152
|
+
tolerate: [1141],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
sql: `REVOKE GRANT OPTION ON \`${name}\`.* FROM '${user}'@'${host}'`,
|
|
156
|
+
tolerate: [1141],
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
return {
|
|
160
|
+
name,
|
|
161
|
+
statements: [
|
|
162
|
+
{ sql: `CREATE DATABASE IF NOT EXISTS \`${name}\`` },
|
|
163
|
+
...account(migratorUser, migratorPassword),
|
|
164
|
+
{ sql: grant(MIGRATOR_PRIVILEGES, name, migratorUser, host) },
|
|
165
|
+
...account(appUser, appPassword),
|
|
166
|
+
{ sql: grant(appPrivileges, name, appUser, host) },
|
|
167
|
+
{ sql: 'FLUSH PRIVILEGES' },
|
|
168
|
+
],
|
|
169
|
+
migrator: {
|
|
170
|
+
user: migratorUser,
|
|
171
|
+
password: migratorPassword,
|
|
172
|
+
url: credentialUrl(admin, migratorUser, migratorPassword),
|
|
173
|
+
},
|
|
174
|
+
app: {
|
|
175
|
+
user: appUser,
|
|
176
|
+
password: appPassword,
|
|
177
|
+
url: credentialUrl(admin, appUser, appPassword),
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Create the database and its two scoped users, idempotently.
|
|
183
|
+
*
|
|
184
|
+
* Re-running rotates both passwords and re-applies the grants, so the plan it
|
|
185
|
+
* prints is always the truth about how to connect afterwards.
|
|
186
|
+
*/
|
|
187
|
+
export async function provisionDatabase(plan, adminUrl, logger = consoleLogger) {
|
|
188
|
+
const connection = await mysql.createConnection({ uri: adminUrl });
|
|
189
|
+
try {
|
|
190
|
+
for (const statement of plan.statements) {
|
|
191
|
+
try {
|
|
192
|
+
await connection.query(statement.sql);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
const errno = error.errno;
|
|
196
|
+
if (statement.tolerate && errno !== undefined && statement.tolerate.includes(errno)) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
logger.info(`Provisioned ${plan.name} with ${plan.migrator.user} and ${plan.app.user}.`);
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
await connection.end();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
36
208
|
/**
|
|
37
209
|
* Apply pending Drizzle migrations.
|
|
38
210
|
*
|
package/dist/admin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,sEAAsE;AACtE,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,wEAAwE;AACxE,gFAAgF;AAChF,2EAA2E;AAC3E,kDAAkD;AAClD,+EAA+E;AAE/E,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,KAAK,MAAM,gBAAgB,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAuB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provision.d.ts","sourceRoot":"","sources":["../../src/bin/provision.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `wmi-db-provision <name> --admin-url <uri>` — create a service's database and
|
|
3
|
+
// its two scoped users.
|
|
4
|
+
//
|
|
5
|
+
// Replaces the block of manual SQL each repo carries in docs/deploy.md.
|
|
6
|
+
// "db:provision": "wmi-db-provision"
|
|
7
|
+
//
|
|
8
|
+
// Note what this does NOT do: it never calls loadNearestEnv(). The other two
|
|
9
|
+
// bins read the service's .env on purpose; this one must not, or it would
|
|
10
|
+
// happily provision whichever instance a stray DATABASE_URL points at. The
|
|
11
|
+
// admin credential is supplied deliberately every time.
|
|
12
|
+
import { parseArgs } from 'node:util';
|
|
13
|
+
import { APP_PRIVILEGES, planProvision, provisionDatabase } from '../admin.js';
|
|
14
|
+
const USAGE = `Usage: wmi-db-provision <database> --admin-url <uri> [options]
|
|
15
|
+
|
|
16
|
+
Creates, idempotently:
|
|
17
|
+
\`<database>\`
|
|
18
|
+
<database>_migrator schema owner (DDL + DML, for Drizzle's bookkeeping)
|
|
19
|
+
<database>_app row access only (${APP_PRIVILEGES.join(', ')})
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--admin-url <uri> Admin credential, server only, no database path.
|
|
23
|
+
Reads WMI_DB_ADMIN_URL if the flag is omitted, which
|
|
24
|
+
keeps the password out of your shell history.
|
|
25
|
+
--app-privileges <l> Narrow the app user, e.g. SELECT,INSERT for an
|
|
26
|
+
append-only service. Default: ${APP_PRIVILEGES.join(',')}
|
|
27
|
+
--grant-host <host> Host part of both accounts. Default: %
|
|
28
|
+
--dry-run Print the exact statements and exit without connecting.
|
|
29
|
+
--help
|
|
30
|
+
|
|
31
|
+
Passwords are generated here, printed once, and never written to disk.
|
|
32
|
+
Re-running rotates them and re-applies the grants.`;
|
|
33
|
+
function fail(message) {
|
|
34
|
+
console.error(message);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = parseArgs({
|
|
40
|
+
allowPositionals: true,
|
|
41
|
+
options: {
|
|
42
|
+
'admin-url': { type: 'string' },
|
|
43
|
+
'app-privileges': { type: 'string' },
|
|
44
|
+
'grant-host': { type: 'string' },
|
|
45
|
+
'dry-run': { type: 'boolean', default: false },
|
|
46
|
+
help: { type: 'boolean', default: false },
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
fail(`${error instanceof Error ? error.message : error}\n\n${USAGE}`);
|
|
52
|
+
}
|
|
53
|
+
const { values, positionals } = parsed;
|
|
54
|
+
if (values.help) {
|
|
55
|
+
console.log(USAGE);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
const name = positionals[0];
|
|
59
|
+
if (!name)
|
|
60
|
+
fail(`A database name is required.\n\n${USAGE}`);
|
|
61
|
+
if (positionals.length > 1) {
|
|
62
|
+
fail(`Unexpected argument ${JSON.stringify(positionals[1])}. One database at a time.\n\n${USAGE}`);
|
|
63
|
+
}
|
|
64
|
+
const adminUrl = values['admin-url'] ?? process.env.WMI_DB_ADMIN_URL;
|
|
65
|
+
if (!adminUrl) {
|
|
66
|
+
fail('An admin credential is required: pass --admin-url or set WMI_DB_ADMIN_URL.\n' +
|
|
67
|
+
'It is never read from the service .env, so that provisioning cannot target ' +
|
|
68
|
+
'the wrong instance by accident.');
|
|
69
|
+
}
|
|
70
|
+
const appPrivileges = values['app-privileges']
|
|
71
|
+
?.split(',')
|
|
72
|
+
.map((p) => p.trim().toUpperCase())
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
try {
|
|
75
|
+
const plan = planProvision({
|
|
76
|
+
adminUrl,
|
|
77
|
+
name,
|
|
78
|
+
...(values['grant-host'] ? { grantHost: values['grant-host'] } : {}),
|
|
79
|
+
...(appPrivileges?.length ? { appPrivileges } : {}),
|
|
80
|
+
});
|
|
81
|
+
if (values['dry-run']) {
|
|
82
|
+
console.log(`-- Dry run for \`${plan.name}\`. Nothing was executed.`);
|
|
83
|
+
console.log(plan.statements.map((s) => `${s.sql};`).join('\n'));
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
await provisionDatabase(plan, adminUrl);
|
|
87
|
+
// Once, to stdout, and nowhere else.
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log('Copy these now. They are shown once and never written to disk.');
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log(` The service (.env, samconfig.toml):`);
|
|
92
|
+
console.log(` DATABASE_URL=${plan.app.url}`);
|
|
93
|
+
console.log(` DATABASE_NAME=${plan.name}`);
|
|
94
|
+
console.log('');
|
|
95
|
+
console.log(` The deploy workflow (GitHub secret MIGRATOR_DATABASE_URL):`);
|
|
96
|
+
console.log(` ${plan.migrator.url}`);
|
|
97
|
+
console.log('');
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=provision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/bin/provision.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { DEFAULT_CONNECTION_LIMIT, SAFE_NAME, databaseConfig, databaseConfigFromEnv, databaseUri, serverUri, type DatabaseConfig, } from './config.js';
|
|
2
2
|
export { createDbHandle, type Db, type DbHandle } from './client.js';
|
|
3
|
-
export { ensureDatabase, runMigrations, type Logger } from './admin.js';
|
|
3
|
+
export { APP_PRIVILEGES, MIGRATOR_PRIVILEGES, ensureDatabase, generatePassword, planProvision, provisionDatabase, runMigrations, type Logger, type ProvisionOptions, type ProvisionPlan, type ProvisionStatement, type ProvisionedUser, } from './admin.js';
|
|
4
4
|
export { findNearestEnvFile, loadNearestEnv } from './env.js';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAErE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAErE,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,KAAK,MAAM,EACX,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { DEFAULT_CONNECTION_LIMIT, SAFE_NAME, databaseConfig, databaseConfigFromEnv, databaseUri, serverUri, } from './config.js';
|
|
2
2
|
export { createDbHandle } from './client.js';
|
|
3
|
-
export { ensureDatabase, runMigrations } from './admin.js';
|
|
3
|
+
export { APP_PRIVILEGES, MIGRATOR_PRIVILEGES, ensureDatabase, generatePassword, planProvision, provisionDatabase, runMigrations, } from './admin.js';
|
|
4
4
|
export { findNearestEnvFile, loadNearestEnv } from './env.js';
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,WAAW,EACX,SAAS,GAEV,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAA0B,MAAM,aAAa,CAAC;AAErE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,WAAW,EACX,SAAS,GAEV,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAA0B,MAAM,aAAa,CAAC;AAErE,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,aAAa,GAMd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waukeshamakerspace/db-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MySQL + Drizzle plumbing for Waukesha Makerspace services: the house DATABASE_URL/DATABASE_NAME convention, a lazy pool, and the ensure/migrate commands every WMI repo was copying by hand",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
22
|
"wmi-db-ensure": "dist/bin/ensure.js",
|
|
23
|
-
"wmi-db-migrate": "dist/bin/migrate.js"
|
|
23
|
+
"wmi-db-migrate": "dist/bin/migrate.js",
|
|
24
|
+
"wmi-db-provision": "dist/bin/provision.js"
|
|
24
25
|
},
|
|
25
26
|
"files": [
|
|
26
27
|
"dist/"
|