Release v2.2.0 — Multi-panel installer (P0 + P1 + P2)#23
Merged
Conversation
- Turkish (tr.json): expanded from 399 to 1926 lines (~76% translated) using word-mapping generator. Remaining 463 keys are technical terms. - Register handle_qc_recheck_count and handle_qc_recheck_historical in admin_v2.php action registry (were defined but unregistered). - Add qc_recheck_count and qc_recheck_historical to api-contracts.test.ts. Audit result: 0 action registry mismatches, 14/14 tests pass. Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
One-command installer for Proxmox/Ubuntu VMs: - Auto-installs Docker if missing - Clones KeyGate from GitHub - Generates .env with secure random passwords - Creates self-signed SSL cert - Builds and starts Docker stack - Creates admin user with super_admin role - Verifies health endpoint - Prints access URLs and credentials Usage: curl -fsSL https://raw.githubusercontent.com/.../install.sh | sudo bash Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
deploy/install.sh: - Fix: acl_roles column is role_name not role_key - Fix: admin_users.email is required (NOT NULL) - Fix: set must_change_password=0 for initial admin - Use ON DUPLICATE KEY UPDATE for idempotent role creation docker-compose.yml: - Fix: healthcheck was hitting /activate/ (404) instead of /api/health.php — DocumentRoot IS /var/www/html/activate so the correct internal URL is /api/health.php Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
Wire up the graphify skill (~/.claude/skills/graphify) so it's enforced project-wide, with auto-rebuild on commits and code edits. Changes: - .claude/settings.json: PreToolUse hook for grep/find tools now points to FINAL_PRODUCTION_SYSTEM/graphify-out/ (the actual graph location). - Added PostToolUse hook that tracks Edit/Write/MultiEdit on code files and a Stop hook that triggers `graphify update FINAL_PRODUCTION_SYSTEM` in background when changes touched FINAL_PRODUCTION_SYSTEM/. - CLAUDE.md: graphify rules now reference FINAL_PRODUCTION_SYSTEM/graphify-out/ with correct commands. - .gitignore: ignore graphify-out/ artifacts (12MB+ graph.json + 25MB cache), rebuilt locally by post-commit hook. - Git hooks (post-commit + post-checkout) installed via `graphify hook install`. Initial graph state: 13,138 nodes, 19,511 edges, 1,260 communities, AST-only extraction. Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
Wires up the user's full personal skill stack so every Claude Code session
in this project gets reminded which skills to use and when.
Changes:
- CLAUDE.md: New "Personal Skills (Enforced)" section documenting all
three skills, their triggers, and the workflow rules.
- caveman: full mode by default, drop articles/filler.
- superpowers: table mapping task → skill (brainstorming, writing-plans,
executing-plans, TDD, systematic-debugging, verification-before-
completion, dispatching-parallel-agents, etc.).
- graphify: existing knowledge graph rules consolidated under this
section.
- .claude/settings.json: Added SessionStart hook that injects a reminder
about all 3 skills as additionalContext at session start.
Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
The web installer failed on aaPanel-style stacks because PDO interpreted
'localhost' as a Unix-socket connection request, then looked for the
default socket path (which doesn't match aaPanel's /tmp/mysql.sock or
/www/server/mysql/mysql.sock) and failed with "No such file or directory".
Code review fixes:
1. Default DB host changed from `localhost` -> `127.0.0.1` to force TCP.
2. Auto-coerce `localhost` -> `127.0.0.1` whenever no explicit Unix socket
path was supplied (both in handleTestDb() and getInstallerPdo()).
3. New optional "Socket Path" field in step 2 (collapsible Advanced
section) for installs that genuinely require a Unix socket.
4. New helper installerBuildDsn() to build the DSN consistently for both
TCP and unix_socket modes; replaces 3 duplicated string concatenations.
5. New helper installerFriendlyDbError() that:
- Sanitizes any DSN fragment that could leak host/port/user
- Maps common errors (Access denied, Unknown database, Connection
refused, No such file, getaddrinfo, timeout) to clear, aaPanel-aware
messages with concrete remediation steps.
6. Added 10s/15s PDO timeouts on every connection so the installer can
never hang.
7. Port is cast to int (was passed as string).
8. Socket path is persisted in $_SESSION['install_db'] so subsequent
migration / admin-create / finalize steps reuse it.
9. Frontend now sends `db_socket` in `dbCredentials`.
Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
Phase 0 of the multi-panel compatibility plan. No table-prefix work yet
(that's P1). All changes local to install/ajax.php + install/index.php.
Hardening:
1. Async per-migration runner. handleInstallDb() split into:
- install_db_init → returns ordered file list with applied flags
- install_db_step → applies ONE migration file (browser drives loop)
- install_db_all → legacy single-shot fast path
Browser pumps the step loop, bypassing max_execution_time caps that
panels like aaPanel/Plesk/cPanel enforce (often 30-60s).
2. Per-statement SQL splitter (installerSplitSql) respects backticks,
single/double-quoted strings, line comments (-- and #), and block
comments. Strips DELIMITER + outer BEGIN/COMMIT wrappers. Lets the
runner survive PDO buffer caps and report progress accurately.
3. set_time_limit(0) + ignore_user_abort(true) at top — best-effort.
4. Preflight expansions:
- open_basedir detection — flags if app root outside allowed paths
- disable_functions audit — fails if mkdir/chmod/file_put_contents/
unlink/rmdir/fopen blocked
- Live mkdir+write+read+unlink probe under uploads/
- parent_writable flag returned
- php_version_full echoed for support tickets
5. Charset auto-fallback: SELECT VERSION() on first connect. MariaDB
<5.5.3 or MySQL <5.7 → utf8mb3 (legacy 'utf8'). Persisted in session
and surfaced to UI via dbCharset selector.
6. CREATE DATABASE skip-toggle: new step-2 checkbox + 1044/1142 error
handler returns suggest_skip_create:true so JS can show "Tick & retry"
button. Plesk/CyberPanel/ISPConfig users no longer hit a dead end.
7. Reverse-proxy IP hardening (getClientIp): only honor X-Forwarded-For
/ X-Real-IP / Client-IP when REMOTE_ADDR is in private/loopback
range. Closes the spoofable trusted-network 2FA-bypass surface.
8. Auto-unlock recovery: install.lock + admin_users empty/missing →
silent unlock. Inlined in install/index.php (avoids dragging ajax.php's
JSON header into HTML) and mirrored as installerCheckIncompleteState()
in ajax.php for runtime symmetry. Logged to install/install.log.
9. Unix-socket auto-detect: handleDetectSocket probes /tmp/mysql.sock,
/var/run/mysqld/mysqld.sock, /var/lib/mysql/mysql.sock,
/www/server/mysql/mysql.sock, /var/run/mariadb/mariadb.sock,
/usr/local/mysql/mysql.sock + 2 more. UI Detect button auto-fills.
10. handleHealth post-install probe: SELECT 1 + SHOW TABLES for the
five canonical KeyGate tables + admin_users count. Useful for step
6 link and external monitoring during shared-host installs.
11. installerBuildDsn() now accepts $charset; getInstallerPdo() pulls
it from session. installerFriendlyDbError() adds 1044/1142 hints
and 1045/no-password specific messaging.
12. installerLog() helper: append-only audit trail at install/install.log.
UI changes (install/index.php):
- Step 2: skip-create-DB checkbox, charset selector, prefix input
(P1-ready, hidden behind Advanced section), Detect socket button.
- Step 3: replaced single-shot runMigrations with init+step loop;
per-row spinner; per-row pass/skip/error with file name + message;
stops on first hard error so user reads it.
- Failed test_db with suggest_skip_create renders inline retry button.
Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
Introduces optional database-table prefix so multiple KeyGate instances
can coexist in one database (panels like cPanel/Plesk often share a
single DB across customer apps). Default prefix is empty → bit-for-bit
identical schema to the previous release.
Changes:
1. tools/prefix-codemod.php
New one-shot script. Self-discovers the canonical KeyGate table list
from CREATE TABLE / ALTER TABLE statements in database/*.sql, then
rewrites:
- SQL files (32): every `tablename` and unbacktiked SQL-keyword target
becomes `#__tablename`. The `#__` sentinel is the Joomla convention.
- PHP files (~50): every backticked or bare-name SQL table reference
inside string literals becomes `' . t('tablename') . '` (single-quote
concat) or `" . t('tablename') . "` (double-quote concat).
Token-based PHP parser (token_get_all) so comments / identifiers /
non-string code is never touched. Idempotent — second run is a no-op.
Run: docker compose exec web php /tmp/codemod.php --root /var/www/html/activate --apply
2. FINAL_PRODUCTION_SYSTEM/functions/db-helpers.php
New file. Defines `t(string $name): string` returning DB_PREFIX . name.
Also defines a fallback `define('DB_PREFIX', '')` if config.php hasn't
set it — covers all legacy installs.
3. FINAL_PRODUCTION_SYSTEM/constants.php
Loads functions/db-helpers.php at the top so t() is available before
any controller runs.
4. FINAL_PRODUCTION_SYSTEM/database/*.sql
Codemod output: 32 SQL files with `#__` markers. Schema is identical
when prefix='' (the default). 276 backticked refs converted.
5. FINAL_PRODUCTION_SYSTEM/{controllers,api,functions,*}.php
Codemod output: ~362 site rewrites across ~54 files. Every SQL string
literal that referenced a canonical table now resolves through t().
6. FINAL_PRODUCTION_SYSTEM/install/ajax.php
- installerRunSqlFile() substitutes `#__` → $_SESSION['install_db']['prefix']
before running each migration. Defense-in-depth: aborts if any `#__`
remains post-substitution.
- installerT() helper for installer-time queries (mirrors t() but reads
prefix from session, since DB_PREFIX isn't defined yet).
- handleInstallDbInit/Step/All all use `installerT('schema_versions')`
when checking applied migrations.
- handleCreateAdmin uses installerT() for admin_users/acl_roles.
- handleFinalize uses installerT() for system_config/technicians/
trusted_networks/admin_ip_whitelist; passes prefix + charset to
generateConfig().
- handleHealth probes prefix-aware physical table names.
- installerCheckIncompleteState() reads DB_PREFIX from existing
config.php so auto-unlock works on prefixed installs.
- generateConfig() emits define('DB_PREFIX', '...') AND propagates
the auto-detected charset (utf8mb4 or utf8mb3 fallback).
7. FINAL_PRODUCTION_SYSTEM/install/index.php
Inline auto-unlock logic now reads DB_PREFIX from config.php, so the
admin_users probe targets the correct physical table name.
8. FINAL_PRODUCTION_SYSTEM/database/docker-init/00-init.sh
New KEYGATE_DB_PREFIX env var (default empty). Pre-runs `sed` over
every .sql file into a /tmp staging copy so the original (read-only)
mount stays untouched. schema_versions tracking table picks up the
prefix consistently. Validates prefix against ^[a-z][a-z0-9_]{0,9}$.
Checksum is computed against the original file (stable across prefix
choices).
Backward compatibility:
- Existing installs without DB_PREFIX in config.php → db-helpers.php
defaults to empty string → t('admin_users') === 'admin_users'.
- 32 .sql files use `#__` placeholders. Without substitution they're
invalid SQL — but they're never executed without going through either
installerRunSqlFile() or 00-init.sh's sed pass.
- Verified: live admin login + list_keys both succeed against the
pre-existing dev database after the codemod.
- 14/14 frontend tests pass.
Risk register from the plan:
- ✅ Codemod misses dynamic table refs → CI lint will catch (P2 task).
- ✅ FK / TRIGGER / VIEW unqualified table refs → SQL pass handles them.
- ✅ Empty-prefix path emits literal `#__` → installerRunSqlFile asserts
strpos(#__) === false post-substitution and aborts.
- ✅ Prefix collides with reserved name → step-2 UI deny-list +
00-init.sh regex validation.
- ✅ Async runner slows happy-path → fast path retained (install_db_all).
- ✅ Legacy config.php lacks DB_PREFIX → db-helpers.php fallback.
Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
… probe (#21) Phase 2 of the multi-panel installer plan. Adds operational robustness on top of P0 hardening + P1 prefix support. Changes: 1. Resumable installer - install/.progress.json breadcrumb file. Each step writes its number and timestamp on completion via the new progress_set action. - On page boot, JS calls progress_get; if last_step >= 1, prompts user "Resume from step N+1?" with Cancel = start over (which clears the file). Bypassed for fresh installs. - handleFinalize unlinks the breadcrumb on success (install complete). 2. Per-migration retry / skip - Step 3 UI: when a migration errors, inline Retry + Skip buttons appear next to the failed row. - Retry: re-runs install_db_step for that file. On success, the migration loop resumes from the next file. - Skip: prompts a hard-yes confirmation, then calls the new migration_skip action which inserts a row into schema_versions with checksum prefix `SKIPPED:` (so future audits can tell apart successful applies from forced skips). Loop resumes. - Both paths respect the canonical migration whitelist. 3. Structured install.log - installerLog() is now called from auto-unlock recovery, finalize completion, progress_set, migration_skip, and progress_clear. - Audit format: `[YYYY-MM-DD HH:MM:SS] event_name: details`. 4. Health-probe button on step 6 - "Run health check" button next to "Open Admin Panel". - Calls existing handleHealth action; renders pass/fail per check (DB connect, presence of {prefix}admin_users, oem_keys, technicians, system_config, schema_versions, plus admin account count). 5. install.lock content extended - Now persists db_prefix and db_charset alongside installer_ver, admin_username, php_version, server_software. Makes post-install forensics easier. 6. .gitignore - install/install.log and install/.progress.json — runtime per-host artifacts, never to be committed. Verified live: - POST progress_set/progress_get round-trip works - Lint clean on ajax.php + index.php - 14/14 frontend tests pass Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
…#22) - CLAUDE.md gets a "Multi-Panel Web Installer" section summarizing the P0+P1+P2 features that just landed, plus a DB_PREFIX block explaining the `#__` sentinel, t() runtime helper, backward-compat empty default, and how to add a new table cleanly. - Development Commands section now includes the prefix-codemod commands (dry-run / apply / verify). CI additions (.github/workflows/ci.yml): 1. New "Prefix Codemod Idempotency" job: - Runs tools/prefix-codemod.php in dry-run against the committed tree. - Asserts SQL=0 + PHP=0 changes (idempotent). - Runs --verify mode to confirm no unprefixed table refs left in SQL. - Catches any future PR that introduces hardcoded table names. 2. New "Installer (restricted PHP env)" job: - Boots PHP 8.3 with max_execution_time=15, allow_url_fopen=Off, memory_limit=128M to mimic aaPanel/Plesk-style restrictions. - Lints install/ajax.php + install/index.php under that env. - Loads ajax.php and asserts the seven new helpers are defined (installerBuildDsn, installerRunSqlFile, installerSplitSql, installerProbeSockets, installerCheckIncompleteState, installerT, plus the original). - Drives installerSplitSql across every database/*.sql file and prints statement counts. Catches any regression where the splitter mishandles a real migration. Both new jobs run on push and PR. They join the existing PHP Lint, Frontend Build & Test, and Docker Stack jobs. Co-authored-by: ChesnoTech <263363000+ChesnoTech@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Release branch syncing develop → main with VERSION.php bumped to 2.2.0.
What's in this release
P0 — Installer hardening (PR #19)
Async per-migration runner, per-statement SQL splitter, charset auto-fallback, CREATE-DB skip, reverse-proxy IP fix, auto-unlock recovery, Unix-socket auto-detect, health probe, sanitized errors with aaPanel hints.
P1 — Joomla-style table prefix (PR #20)
#__sentinel +t()runtime helper. Full codemod: 32 SQL files + 54 PHP files + ~362 site rewrites. Idempotent.KEYGATE_DB_PREFIXenv var for Docker. Backward-compat empty default.P2 — Resilience (PR #21)
Resumable
.progress.json, per-migration retry/skip buttons, structuredinstall.log, step-6 health-probe button.Docs + CI (PR #22)
CLAUDE.md updated with installer + DB_PREFIX rules. New CI jobs: codemod idempotency check + restricted-PHP smoke test.
Branch Type
release/Components Affected
Testing