Repository navigation
feat(ns8): make a cloned instance release its hostname - #157
Conversation
|
The model provider returned an error, so this run stopped. The error is below. If it names your key, plan or balance, fix that with the provider or pick another model before re-triggering. Model settings → · Ask in Discord →
|
|
The model provider returned an error, so this run stopped. The error is below. If it names your key, plan or balance, fix that with the provider or pick another model before re-triggering. Model settings → · Ask in Discord →
|
|
Same careful look. This one is good, and the important parts are honest about the risk. What I verified rather than took on trust:
One nit, not a blocker. In Sequencing. #156 and this PR both add a section at README line 129, so they conflict. #156 merges first, then rebase this onto main. The reasoning about routes being keyed by MODULE_ID, and about a clone arriving deliberately unconfigured rather than quietly broken, is correct and worth keeping. |
|
OpenCode Go returned an error, so this run stopped. If it names your key, plan or balance, fix that with the provider or pick another model.
|
Cloning an instance from the cluster admin UI copies the calnode-data volume
and the module state. That is enough to duplicate a working instance, and it was
already working, because the core clone-module action ships a partial default
implementation: ns8-kickstart itself declares only configure-module and
get-configuration, so shipping no clone-module action is normal, not a gap.
What the default does not handle is that two of the things in state/ belong to
ONE hostname:
config.json the public hostname and its TLS flags
calnode.env BASE_URL, derived from that hostname
The host-based Traefik route is registered by configure-module and belongs to
one instance, so it is not copied: routes are keyed by MODULE_ID. A clone
therefore arrives holding the source's hostname with no route of its own, and
configuring it with that hostname is then REFUSED, because the source still
answers for it and configure-module refuses to steal a hostname that another
route already serves. The clone is not a clean failure, it is a quietly broken
instance that looks like a configuration bug.
The step releases the hostname: config.json is removed and BASE_URL dropped, so
get-configuration reports configured:false and the Settings page asks for a
hostname. The clone is then in exactly the state a freshly added instance is
in, and configure-module registers a route for whatever name it is given.
What must not be released is CALNODE_ENCRYPTION_KEY. It travels with the copied
database and is the only thing that can unwrap the database key vault; dropping
it would hand back an unreadable copy of every booking, which is strictly worse
than the bug being fixed. The rewrite therefore fails loudly rather than writing
an env file without the key, even though that is a state a mis-edited file could
otherwise produce.
Tested offline, 8 cases in test_clone_module.py, wired into the same validate
job as the existing suite so a regression fails on every push: the key survives
while BASE_URL does not, config.json is removed, a missing config.json or
calnode.env is not an error, the refusal path, file mode stays 0600, comments and
blank lines survive the rewrite, and the step is idempotent because action steps
can be retried. 17 tests total in the packaging suite, all passing.
The step runs at 20, after the core moves data in 10recvstate and before it
starts the service in 90finalize.
Honest caveat: this is verified by unit tests and by reading the core
implementation, NOT by cloning on a real cluster. That needs a live NS8 node, and
the robot suite does not cover clone yet. Worth doing before telling anyone a
clone is supported.
core.fileMode is false on this machine, so chmod 755 on the file was invisible to git and the step was committed as 100644. The validate job's exec-bit guard caught it: 'not executable in index: imageroot/actions/clone-module/ 20unclaimhostname'. An NS8 action step that is not executable is silently ignored by the agent, so the step would simply never have run. Fixed with update-index --chmod=+x, which sets the index mode explicitly rather than trusting the working tree.
Review nit, taken because it is a behaviour difference rather than a style one. The guard that protects CALNODE_ENCRYPTION_KEY ran after the env file had been rewritten, so a clone that was going to be refused had already had its env file mutated by the step that then aborted. Nothing recorded that mutation, so the clone could end up differing from its source with no trace of why. The check now runs before the write. The key still cannot be lost either way, since only BASE_URL is removed and the guard is defending against a pre-existing condition, but an aborted step now leaves both files exactly as it found them. New test asserts that: on refusal, calnode.env and config.json are byte-identical to before. 18 tests, all passing.
cab640d to
9ffc363
Compare
|
OpenCode Go returned an error, so this run stopped. If it names your key, plan or balance, fix that with the provider or pick another model.
|

Follow-up to #156. Adds the
clone-modulestep and documents the behaviour.First, a correction to what I told you
I said we shipping no
clone-moduleaction was a real functional gap. It is not.NethServer/ns8-kickstart, the official module template, declares onlyconfigure-moduleandget-configuration, and the core ships a partial defaultclone-moduleimplementation that rsyncs the volumes andstate/. So cloning already worked. My framing overstated it, and I have fixed the framing rather than quietly doing the work anyway.What the default does not handle
Two things in
state/belong to one hostname:config.json— the hostname and TLS flagscalnode.env—BASE_URL, derived from that hostnameThe Traefik route is registered by
configure-moduleand belongs to one instance, so it is not copied; routes are keyed byMODULE_ID. A clone therefore arrives holding the source's hostname with no route of its own, and configuring it with that hostname is then refused, because the source still answers for it andconfigure-modulerefuses to steal a hostname another route already serves.So the clone is not a clean failure. It is a quietly broken instance that looks like a configuration bug. That is worse than not supporting clone at all, which is worth saying plainly.
The step
imageroot/actions/clone-module/20unclaimhostnamereleases the hostname:config.jsonis removed andBASE_URLdropped.get-configurationthen reportsconfigured:falseand the Settings page asks for a hostname, so the clone lands in exactly the state a freshly added instance is in.It runs at
20, after the core moves data in10recvstateand before it starts the service in90finalize.What it must not release is
CALNODE_ENCRYPTION_KEY. It travels with the copied database and is the only thing that can unwrap the database key vault. Dropping it hands back an unreadable copy of every booking, which is strictly worse than the bug being fixed. The rewrite therefore fails loudly rather than writing an env file without the key.Tests
8 cases in
test_clone_module.py, wired into the samevalidatejob as the existing suite, so a regression fails on every push:BASE_URLdoes notconfig.jsonis removedconfig.jsonorcalnode.envis not an error060017 tests in the packaging suite in total, all passing.
Honest caveat
This is verified by unit tests and by reading the core implementation. It has not been cloned on a real cluster. That needs a live NS8 node and the robot suite does not cover clone yet.
So I would not tell anyone a clone is supported yet. Worth adding a clone case to the robot suite before this goes out, and that is the one thing here I would want done rather than assumed.
Docs
README gains a Clone and move section, stating that the encryption key is kept and the hostname is not, and why. Better documented than discovered.