Operator setting up auto-update was forced to scroll down to the
"Manifest actuel" card and find the global Sync button after each step
of the launcher workflow. Add a dedicated "🔁 Recalculer hash + signer"
primary-blue button right inside the launcher card. It posts the same
action='sync' so behavior is identical — it's just discoverable from
where the operator is looking.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous markup had a <form action=remove_launcher> nested inside a
<form action=set_launcher>. HTML doesn't allow nested forms — browsers
flatten the structure unpredictably, and clicking the inner "Définir"
submit could end up posting the outer (or vice versa), causing the
section to be removed when the user actually wanted to set it.
Two sibling forms now, with a 12px gap between them. Same UX.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two things change so the operator never has to SSH for a launcher
release.
1. SignManifest::run() now also re-hashes the manifest's `launcher`
section. It looks for builds/launcher/<basename of launcher.url>;
falls back to a tolerant glob *<version>*.exe if the exact name
isn't found. Updates sizeBytes + sha256 in place. The Ed25519 sign
step at the end already covered this branch — it was just blank
data before.
2. admin/versions.php has a new "Auto-update du launcher" card above
the manifest table. Shows the announced version + minRequired, the
exe presence badge and the hash status, and a small form to set or
update the launcher entry (version + minRequired only — URL is
derived from base_url + version automatically). A "Retirer la
section" button disables the auto-update by deleting the launcher
key from versions.json. Lists the .exe files present in
builds/launcher/ for visibility.
Workflow now: edit the version in the form → SFTP-upload
PSLauncher-X.Y.Z.exe to builds/launcher/ → click "🔁 Sync" once →
manifest is hashed and signed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top bar (3-column layout)
-------------------------
- Col 1: PROSERVE Launcher wordmark.
- Col 2: license badge centered, the badge IS the click target now (a
Border with an InputBindings MouseBinding LeftClick to OpenLicense).
No more separate "🔑 Activer / changer" button cluttering the right.
- Col 3: ⚙ Paramètres + window chrome (min/max/close).
- "📁 Dossier" button removed from the top bar — install root is still
reachable from Settings.
Footer (always visible)
-----------------------
- Row 1: "🔄 Vérifier les MAJ" pinned bottom-left, always shown. The
optional "Annuler" button stays bottom-right while a download runs.
- Row 2: status text + progress bar, only shown when busy or after a
status message — the previous "footer entirely hidden when idle"
hid the check button too.
License flow split in two
-------------------------
Click on the license badge:
- License is active (valid / expired / revoked) → LicenseDetailsDialog
opens. Header pill in matching status color (green/amber/red), shows
owner, validity, issued date, machine ID with copy-to-clipboard. Two
buttons: "🗑 Désactiver la license" (with confirmation) and Close.
- No license OR after deactivation → falls through to the existing
OnboardingDialog for re-keying.
Settings rework
---------------
LICENSE section is now first in SettingsDialog with the same
green/amber/red colored chrome as the top bar — at a glance the user
sees the same status everywhere. Machine ID copy moved into this card.
Sign-manifest no longer needs exec()
------------------------------------
The "🔁 Sync" button in admin/versions.php previously shelled out to
`php tools/sign-manifest.php` via exec(). OVH mutualisé often disables
exec(), causing silent no-ops and the symptom the user just hit: the
ZIP changed (new size 469,657,770 vs manifest's stale 469,831,428) and
the launcher rejected it as size mismatch.
Refactor:
- New PSLauncher\Tools\SignManifest class with ->run() that does the
hashing, latest-bump and Ed25519 signing in-process.
- tools/sign-manifest.php is now a 6-line wrapper for the class.
- admin/versions.php's 'sync' action calls the class directly via
require_once + new — works on any host, no exec dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-contained admin under /PS_Launcher/admin/ on the same OVH host. No
JS framework, no Composer deps — just PHP 8.3 + sessions + a small CSS.
Auth & infrastructure
---------------------
- admin/lib/Auth.php: session + CSRF helper. Login via password_hash /
password_verify. session_regenerate_id on successful login.
config.example.php gains admin_password_hash, generated by:
php -r "echo password_hash('PWD', PASSWORD_DEFAULT);"
- admin/lib/Layout.php: shared header/footer/nav, formatBytes,
csrfField helpers.
- admin/.htaccess: noindex / X-Frame-Options DENY / X-Content-Type
nosniff / blocks lib/*.php from web access.
- admin/assets/style.css: matches launcher's dark theme — same
Brush.* palette mapped to CSS vars, vivid green/blue/amber status
pills consistent with the WPF UI.
Pages
-----
- index.php (Dashboard): KPIs (active/expired/revoked licenses, machines
seen 30d, validations 24h), manifest signature status, last 10
audit_log entries.
- licenses.php: full CRUD.
* Émettre: owner, expiration date, max machines, internal notes →
generates a PRSRV-XXXX-XXXX-XXXX-XXXX key, displays it ONCE in a
green callout (DB stores the key; the message stays only on this
request, never shown again).
* Prolonger (per-row, expandable form), Revoke / Unrevoke,
Reset machines (frees all slots for that license).
* Status badge: active / expired / revoked.
- versions.php: edit the manifest from the web.
* Add a version: number + release date + minLicenseDate + release
notes Markdown (creates releasenotes/{version}.md). Sets default
download URL to {base_url}/builds/proserve-{version}.zip.
* Per-row Méta (edit minLicenseDate / releasedAt), Notes (edit md
inline), toggle availableForDownload, Delete entry.
* 🔁 Sync (sign-manifest) button: shells out to
`php tools/sign-manifest.php` and shows its stdout — recomputes
sha256/sizeBytes for every uploaded ZIP, bumps `latest`, signs
Ed25519. Visual indicators on each row: zip presence, hash
computed yes/no, signature status.
* Lists orphan ZIPs in builds/ that no manifest entry references.
- audit.php: paginated audit_log viewer (100/page) with event-type
filter dropdown. JOINs licenses to show owner_name. Color-codes
events (validate_ok green, expired amber, invalid/revoked red).
Server README rewritten to document the full setup flow:
1. Create MySQL DB, run migrations/001_init.sql
2. Copy config.example.php → config.php, fill db credentials
3. php tools/generate-keypair.php → paste into config.php and into the
client's Resources/server-pubkey.txt
4. Set admin_password_hash in config.php
5. Login at /PS_Launcher/admin/
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>