10 KiB
dev-deploy operations
Running and maintaining the local development instances for the dev-deploy plugin. Three Saltcorn instances back development and testing: a SQLite MAIN instance on :3000 (which also hosts the saltcorn-idp LDAPS listener), a SQLite TEST instance on :3001, and a Postgres multi-tenant instance on :3002.
See also: architecture.md, peering.md, multitenancy.md, testing.md.
Contents
- Layout
- The three dev instances
- Environment files
- Starting the instances
- Installing the plugin
- Per-tenant install on Postgres
- Known issues
Layout
The project root is ~/claude/saltcorn. The dev-deploy plugin source lives in
the dev-deploy/ subfolder; the saltcorn-idp plugin source is a sibling in
idp/; upstream Saltcorn is checked out under saltcorn/. Each instance keeps
its database, file store, and session store under its own state directory in the
project root:
| Instance | Port | State dir | Database | LDAPS port |
|---|---|---|---|---|
| MAIN | 3000 | .dev-state/ |
SQLite (saltcorn.sqlite) |
1636 |
| TEST | 3001 | .dev-state-test/ |
SQLite (saltcorn.sqlite) |
none |
| PG (multi-tenant) | 3002 | .dev-state-pg/ |
Postgres saltcorn_idp |
1637 |
The start scripts cd into the state dir before running saltcorn serve so
each instance writes its own sessions.sqlite (Saltcorn's SQLite session store
writes sessions.sqlite at the process cwd, per
packages/server/routes/utils.js).
The three dev instances
MAIN (:3000, SQLite)
startServer.sh sources .dev-state/env.sh, runs saltcorn install-plugin -d ./dev-deploy (non-fatal on failure -- the previously installed version still
loads), then cd .dev-state and exec saltcorn serve "$@". saltcorn-idp's
LDAPS listener is enabled and configured (host/port) from the public-site admin
panel; the SALTCORN_IDP_LDAP_PORT / SALTCORN_IDP_LDAP_HOST env vars are
optional per-setting overrides that win when set (a set port also forces the
listener on). MAIN is the only instance that pins SALTCORN_IDP_LDAP_PORT=1636
in its env.sh; the others leave that port unset (PG pins 1637; see below), so
the three do not collide on the same bound port. saltcorn-idp is installed via
reinstallIdp.sh rather than in the start script (the shared plugins_folder
makes a per-boot install race on concurrent starts); it loads at serve time from
that install.
TEST (:3001, SQLite)
startServerTest.sh sources .dev-state-test/env.sh (which sets
SALTCORN_PORT=3001), installs the plugin the same way, then
cd .dev-state-test and exec saltcorn serve -p "$SALTCORN_PORT" "$@". TEST is
the promote/pull peer used by the e2e suite. Its env.sh does not pin
SALTCORN_IDP_LDAP_PORT, and the public-site LDAP config is left disabled, so no
LDAP listener runs there.
PG (:3002, Postgres multi-tenant)
startServerPg.sh sources .dev-state-pg/env.sh, then cd .dev-state-pg and
exec saltcorn serve -p 3002 "$@". The PG env deliberately does NOT set
SQLITE_FILEPATH, so getConnectObject() selects Postgres; it sets
SALTCORN_MULTI_TENANT=true to enable schema-per-tenant (Postgres only). Unlike
the SQLite start scripts, startServerPg.sh does NOT run install-plugin at
boot -- the plugin is installed into the public schema by reinstallDevDeploy.sh
and activated per tenant by installDevDeployTenant.sh (see below). After
per-tenant activation, each tenant's onLoad re-runs automatically on every
boot via init_multi_tenant -> loadAllPlugins. The PG env.sh also pins
SALTCORN_IDP_LDAP_PORT=1637 (distinct from MAIN's 1636), which overrides the
public-site config and forces the listener on, so the multi-tenant LDAP gate can
exercise tenant-in-DN binds against this instance.
Environment files
Each state dir has an env.sh that is sourced before running saltcorn. They
all prepend the in-tree CLI (saltcorn/packages/saltcorn-cli/bin) to PATH so
saltcorn ... resolves to this checkout, and load nvm.
| Variable | MAIN | TEST | PG |
|---|---|---|---|
SQLITE_FILEPATH |
.dev-state/saltcorn.sqlite |
.dev-state-test/saltcorn.sqlite |
unset (selects Postgres) |
SALTCORN_FILE_STORE |
.dev-state/files |
.dev-state-test/files |
.dev-state-pg/files |
SALTCORN_SESSION_SECRET |
set | set | set |
SALTCORN_PORT |
unset (defaults to 3000) | 3001 |
unset (passed -p 3002) |
SALTCORN_IDP_LDAP_PORT |
1636 |
unset | 1637 |
SALTCORN_MULTI_TENANT |
unset | unset | true |
SALTCORN_JWT_SECRET |
unset | unset | set |
PGHOST / PGUSER / PGDATABASE / PGPASSWORD |
unset | unset | /var/run/postgresql / scott / saltcorn_idp / peer |
On PG, Saltcorn only selects Postgres when user, password, and database are all
set (connect.ts getConnectObject). Authentication is via the Unix socket
with peer auth, which ignores the password, so PGPASSWORD=peer is a dummy that
just satisfies that check.
SALTCORN_SESSION_SECRET is set in each dev env.sh, but it is NOT required for
the plugin. dev-deploy resolves the at-rest KEK secret from the Saltcorn
installation: it falls back to db.connectObj.session_secret (which already
merges any env var and the ~/.config/.saltcorn config file), then to the config
file directly, then to the session_secret DB config. SALTCORN_SESSION_SECRET
is just an explicit override that wins when present (it is checked first). A plain
Saltcorn install configured via the config file needs no extra env var; the dev
env.sh sets it only to pin a known value across the dev instances. dev-deploy
throws dev-deploy: session_secret not available ... only if all four sources
miss. Rotating the resolved secret still invalidates all sealed data.
Starting the instances
Run from the project root:
cd ~/claude/saltcorn
./startServer.sh & # MAIN :3000 (+ LDAPS :1636)
./startServerTest.sh & # TEST :3001
./startServerPg.sh & # PG multi-tenant :3002 (+ LDAPS :1637)
The two SQLite start scripts each attempt install-plugin -d ./dev-deploy at
boot (failures are non-fatal). The PG instance does not; install it explicitly
as described below.
Installing the plugin
After editing the plugin source, reinstall it into all three instances with the servers stopped:
cd ~/claude/saltcorn
./reinstallDevDeploy.sh
reinstallDevDeploy.sh installs dev-deploy into MAIN (.dev-state), TEST
(.dev-state-test), and the PG public schema (.dev-state-pg). It is a separate
script (not folded into the start scripts) because the saltcorn plugins_folder
(~/.local/share/saltcorn-plugins) is shared by all instances, and saltcorn install-plugin:
- needs an ABSOLUTE
-dpath (itpath.join()s thenrequire()s, so a leading./is collapsed and resolved as a node module), and - aborts (EEXIST) if the per-plugin-dir
node_modulessymlinks already exist.
Doing this in each start script would race when instances boot concurrently, so
reinstalls are centralized here. Before each install the script clears the
node_modules symlinks under the plugins root so install-plugin can recreate
them cleanly.
For the PG instance, installing into the public schema is only step one; you must then activate the plugin per tenant (next section).
Per-tenant install on Postgres
The public-schema install does not create the _dd_* tables in each tenant
schema. To register + enable dev-deploy in a tenant schema and run its onLoad
(creating the _dd_* tables and bootstrapping the env row), use:
./dev-deploy/scripts/installDevDeployTenant.sh t1 t2 # named tenants
./dev-deploy/scripts/installDevDeployTenant.sh '*' # all tenants
The shell wrapper resolves the project root, sources .dev-state-pg/env.sh, and
runs installDevDeployTenant.js. The JS uses Saltcorn's supported
Plugin.loadAndSaveNewPlugin inside runWithTenant (replacing the old manual
INSERT INTO <tenant>._sc_plugins SQL hack). It sets the root-only config
tenants_unsafe_plugins=true so a LOCAL plugin can be installed into tenant
schemas, converges each tenant to exactly one _sc_plugins row, and verifies the
install by confirming both the _sc_plugins row and the tenant-schema _dd_env
table exist. With no args or a single *, it installs into every tenant from the
public _sc_tenants list.
Prerequisites:
- the tenants must already exist (
saltcorn create-tenant <name>), and - the plugin must be installed into the PG public schema once (the
reinstallDevDeploy.shpath) so the shared plugins_folder copy exists.
After per-tenant activation, each tenant's onLoad re-runs automatically on
every boot of startServerPg.sh. The SQLite MAIN/:3000 and TEST/:3001 instances
are unaffected (they set SQLITE_FILEPATH). See
multitenancy.md for the schema-qualification details.
Known issues
Shared node_modules symlinks couple the two plugins
The node_modules symlinks under the plugins root
(~/.local/share/saltcorn-plugins) are SHARED across plugins. The
clearSymlinks() step in reinstallDevDeploy.sh therefore also clears
saltcorn-idp's symlinks. After running reinstallDevDeploy.sh, you MUST also
re-run reinstallIdp.sh (and vice versa) to keep both plugins' installs
consistent, then restart the servers. reinstallIdp.sh installs saltcorn-idp
into MAIN and TEST only (it does not touch the PG instance).
Recommended sequence after editing plugin source:
# stop the servers first
./reinstallDevDeploy.sh
./dev-deploy/scripts/installDevDeployTenant.sh '*' # PG only, if using tenants
./reinstallIdp.sh
# restart the servers
Themeless freshly-created tenant returns 500 from sendWrap
A freshly created Postgres tenant has no theme/layout configured, so Saltcorn's
own admin pages that call res.sendWrap (for example GET /table/new/) return
HTTP 500. dev-deploy's admin pages self-render and are unaffected. When you need
a CSRF token for a Saltcorn admin POST against a themeless tenant, grab the token
from a dev-deploy page (for example GET /admin/dev-deploy/peers) instead -- the
mutate-then-redirect POST (such as POST /table) does not call sendWrap and so
succeeds. This is exploited throughout the PG gates; see
testing.md.