Commit Graph

8 Commits (fed4acf31ca28a24bd1c6eeb365225da388dc9fc)

Author SHA1 Message Date
nexxo f9993a1f48 Name the server variable under a credential, not the internal key
tests / pest (push) Failing after 8m35s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Under each credential stood `stripe.secret`, `dns.token`,
`inbound_mail.password`, `monitoring.token` — identifiers out of
SecretVault::REGISTRY. They mean nothing on a settings page and there is
nowhere an operator could type them; asked about directly, and the answer is
no, that was not deliberate.

The env variable name is the useful one: it is what stands in the server
file, it is what the .env tab of this same page lists, and when the badge
beside it says "Aus der Serverdatei" it answers the obvious next question —
which line?

The registry key stays where it belongs: in wire:key and in the modal
arguments, as an identifier nobody reads. The test asserts on the text node
rather than on the key's absence from the HTML, because absence would have
demanded breaking the page to pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 13:20:29 +02:00
nexxo f85f57152e Let the customer buy it themselves, and tab the integrations page
tests / pest (push) Failing after 21m20s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Every path on the site ended in "anfragen": a mailto: link and a promise
to answer the same working day. That is an afternoon of somebody's time
per customer, for a product whose whole point is that the machine does
the work — and it scales exactly as far as one inbox does.

Now: sign in, pick a package, pay, and provisioning starts by itself.
The pieces were already there — Stripe holds a Price per plan version
(the catalogue sync writes the ids), the webhook opens the contract, the
pipeline builds the VM — and what was missing was the checkout session
between them. CheckoutController opens one and gets out of the way; the
purchase becomes real on the webhook, which stays the only place allowed
to believe a payment happened. The success URL is a redirect target and
therefore a URL anybody can type, so it says "we are building it", not
"you have paid".

Nothing in the request may decide the price. The plan key arrives from a
form and everything else is re-derived from the catalogue, including the
plan VERSION, so a version published while the customer types their card
number cannot change what they were quoted.

Two cases that are not errors: no capacity is a parked order (already
built), and an existing customer is sent to the plan change rather than
sold a second, empty cloud that bills twice.

The site says what it can keep. "danach wissen Sie … wie Ihre Daten
sicher umziehen" was a promise made before seeing the thing it is about:
we do not know where a visitor's data is, what shape it is in, or whether
it can be moved at all. Migration is named as its own question, looked at
first and quoted afterwards.

The integrations page gets the settings page's treatment for the same
reason: it was one narrow column of six cards, so an operator connecting
DNS scrolled past Stripe to reach it and the raw .env editor sat at the
bottom of everything. Three tabs by what a section is FOR — services we
buy from outside, our own machines, the file underneath — the same axis
the page was rebuilt around when it stopped being split by storage
mechanism. The tab is in the query string; unlocking survives a switch,
because a confirmed password is a session fact and not a property of a
tab. The .env tab is Owner-only, like the editor it holds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:42:28 +02:00
nexxo 98afd1d7e0 Re-apply what the last release commit silently reverted
tests / pest (push) Failing after 7m54s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
The English paths and the zone migration went out in 5f74a5f and were gone
again in the tag. Reported for the fourth time, and the fourth report was
right: nothing had changed on the server.

What happened is mine and worth writing down. I have been building commits in
a private index (GIT_INDEX_FILE) to avoid staging a parallel session's
in-flight work, and moving the branch with `git update-ref`. That moves HEAD
without touching the shared index — so the index still described the PREVIOUS
commit. The very next ordinary `git commit` (the version bump) wrote that
stale index as a tree, which reverted every file of the commit before it. The
tag was cut from the revert.

Two things follow. The private-index technique is only safe while the branch
is not checked out; once it is, `git branch -f` refuses for exactly this
reason and `update-ref` is the same operation with the safety removed. And a
release is not verified by a green push — it is verified by reading the tag,
which is what finally found this.

Content unchanged from 5f74a5f: English fragments and legal paths with
permanent redirects, the guarded zone migration, and the test that walks every
route and fragment so a German path fails the suite instead of being reported
again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 17:36:43 +02:00
nexxo 1620cb9395 Release 1.3.22
tests / pest (push) Failing after 8m15s Details
tests / assets (push) Successful in 22s Details
tests / release (push) Has been skipped Details
v1.3.21 was taken by a parallel release between reading main and writing the
commit; the version file went out saying 1.3.21 on a commit that is not that
tag. Corrected here rather than by rewriting a pushed commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 17:26:46 +02:00
nexxo 5f74a5f370 Move the zone by migration, and put every path in English
tests / pest (push) Failing after 8m8s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
── The zone, for the fourth time ────────────────────────────────────────────
"Change it in the console" was the wrong answer three times running. An
installation with nothing to migrate should simply arrive on the new zone, so
a migration does it — guarded by exactly the condition clupilot:check-zone
reports on: no instance in service, therefore nothing carrying the old name in
DNS, in a certificate or in trusted_domains. With even one instance it does
nothing and logs why, because re-addressing a running Nextcloud is a migration
with an outage and a migration is not the place to start one. It leaves an
installation already on another zone alone: the setting exists so somebody
else can run this on their own domain.

That fixes both reports at once — the security page and the price sheet read
the same setting, which is the entire reason neither of them has the domain
typed into it.

── R13, applied to all of them this time ────────────────────────────────────
"Ich schreibe das nun zum dritten Mal" — fair. The rule was cited, I changed
the one route that had been pointed at, and left every other German path
standing. A fragment is part of a URL too.

  #produkt → #product      /legal/impressum   → /legal/imprint
  #ablauf  → #process      /legal/datenschutz → /legal/privacy
  #preise  → #pricing      /legal/agb         → /legal/terms
  #fragen  → #faq          (old paths redirect, permanently — they have been
  #kontakt → #contact       in the footer and in mail for weeks)

The headings stay German. "Impressum" is the legal term an Austrian company
has to use; the address is not part of the copy.

A test now walks every registered route and every fragment the site links to,
so the next German path fails the suite instead of being reported a fourth
time.

Three existing tests set config('provisioning.dns.zone') and read it back
through the accessor. That worked while the setting was empty and stopped the
moment the migration filled it — they set the setting now, and the one whose
premise is "nothing saved yet" establishes that premise itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 17:26:20 +02:00
nexxo 33e5b15099 Restart the workers automatically after saving .env, instead of handing the operator back to the shell
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 02:26:34 +02:00
nexxo 90131f088a Add a raw .env editor to the Integrations page, with a net under it
Everything the console has no field for is still real — MAIL_*, DB_*,
APP_KEY, whichever key nobody has built a form for yet — and without a way to
touch it from here, the operator needs a shell and the console page misses
its own point. EnvFileEditor is the net, not just the warning:

1. Validates before writing. A line that is neither blank, nor a comment, nor
   KEY=value is rejected outright, and an empty file (syntactically "valid" by
   that rule, but not survivable) is refused too. write() never touches the
   file before checking the new content.
2. Backs up before every write that actually happens — a timestamped copy
   beside .env, before the new content lands. Never pruned automatically; the
   page says so, next to where it says where they land.
3. Names the keys a mistake here can lock an operator out with — APP_KEY,
   DB_*, REDIS_*, SESSION_* — rather than a blanket warning nobody reads.
4. Gated by secrets.manage and the same confirmed password the vault entries
   use — this is the one place on the page that can reach every credential
   the vault otherwise keeps write-only.
5. Says plainly what saving does not do: queue, queue-provisioning, scheduler
   and reverb only read .env at their own startup, and names the restart
   command plus config:clear.
6. Marks which .env keys SecretVault currently overrides, so editing a line
   that a stored vault value already shadows does not look broken.

Two issues surfaced by Codex review and fixed before this commit: the raw
file content stayed in the Livewire component snapshot after the password
confirmation window expired on its own (not only on an explicit re-lock), and
the first save on an installation with no .env yet failed trying to back up a
file that was never there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 01:51:27 +02:00
nexxo 3fadaa14b0 Merge credentials and infrastructure into one Integrations page
Zugangsdaten and Infrastruktur split the same subject by storage mechanism —
SecretVault vs App\Support\Settings — instead of by what each field
configures, which is not a distinction an operator setting up Stripe or DNS
should have to know or care about. One page now, grouped by integration:
Zahlungen (Stripe), DNS (Hetzner), Monitoring, VPN/WireGuard, SSH-Identität. A
secret and a plain setting sit side by side within a section; the difference
stays visible — a vault entry is write-only and shows only an outline, a
setting shows its value in full — as a property of the field, not a reason
for a separate page.

Both storage mechanisms are unchanged underneath: this is a presentation
change, not a data migration. The vault keeps its own gate (secrets.manage +
a recently confirmed password); plain settings keep theirs (hosts.manage
alone). Reachable with either capability, since the two are no longer two
pages a role happens to see one, both, or neither of — every section and
every save action still checks its own capability server-side.

admin.secrets and admin.infrastructure redirect here permanently rather than
404ing a bookmark. The nav entry that replaces both is visible to either
capability — Navigation's capability field now accepts an array meaning "any
of these", not only a single ability.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 01:49:39 +02:00