- .htaccess: refuse .git, .env, docker, nodejs, sdlc*, scripts, archives and app internals; security headers, HSTS over TLS, optional HTTPS redirect - uploads served through app/file.php to signed-in users only - Apache/PHP hardening config for the container (ServerTokens, expose_php) - least-privilege DB account and APP_SECRET_KEY via docker/php/provision.php; SMTP passwords re-encrypted with a random IV (secret_box.php) - Socket.IO rooms from a PHP-signed token; /emit and cron refuse empty secrets - escape notification text; CLI guards on build scripts; no fixed demo password
72 lines
3.0 KiB
PHP
72 lines
3.0 KiB
PHP
<?php
|
|
/**
|
|
* provision.php — run by the container entrypoint before setup.php (CLI only).
|
|
*
|
|
* 1. Creates/updates the least-privilege database account the app connects as
|
|
* (DML + CREATE/INDEX on wms and wms2 only — the app creates td_stock_<id>
|
|
* tables with CREATE TABLE … LIKE; no DROP, ALTER, GRANT or other databases).
|
|
* 2. Brings an existing app/config.php (generated once, never regenerated) onto
|
|
* that account and adds APP_SECRET_KEY if it is missing.
|
|
*
|
|
* All values come from the environment and are passed to MariaDB as bound
|
|
* parameters or written with var_export(), so no password is placed on a
|
|
* command line or interpolated into SQL or PHP source.
|
|
*/
|
|
|
|
if (PHP_SAPI !== 'cli') {
|
|
http_response_code(404);
|
|
exit;
|
|
}
|
|
|
|
$root_pass = (string)getenv('DB_ROOT_PASSWORD');
|
|
$app_user = (string)getenv('DB_APP_USER');
|
|
$app_pass = (string)getenv('DB_APP_PASSWORD');
|
|
$secret = (string)getenv('APP_SECRET_KEY');
|
|
$config = (string)getenv('CONFIG');
|
|
|
|
// ── 1. Database account ──────────────────────────────────────────────────────
|
|
if ($app_user !== 'root') {
|
|
if (!preg_match('/^[A-Za-z0-9_]{1,32}$/', $app_user)) {
|
|
fwrite(STDERR, "[provision] DB_APP_USER must be letters, digits or _\n");
|
|
exit(1);
|
|
}
|
|
$pdo = new PDO('mysql:host=db', 'root', $root_pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
|
$pdo->exec('CREATE DATABASE IF NOT EXISTS `wms`');
|
|
$pdo->exec('CREATE DATABASE IF NOT EXISTS `wms2`');
|
|
$pdo->prepare("CREATE USER IF NOT EXISTS ?@'%' IDENTIFIED BY ?")->execute([$app_user, $app_pass]);
|
|
$pdo->prepare("ALTER USER ?@'%' IDENTIFIED BY ?")->execute([$app_user, $app_pass]);
|
|
foreach (['wms', 'wms2'] as $db) {
|
|
$pdo->exec("GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE"
|
|
. " ON `{$db}`.* TO " . $pdo->quote($app_user) . "@'%'");
|
|
}
|
|
echo "[provision] database account {$app_user} is ready\n";
|
|
}
|
|
|
|
// ── 2. Existing config.php ───────────────────────────────────────────────────
|
|
if ($config === '' || !is_file($config)) {
|
|
exit(0);
|
|
}
|
|
$src = file_get_contents($config);
|
|
$orig = $src;
|
|
|
|
$set = function (string $var, string $value) use (&$src) {
|
|
$src = preg_replace_callback(
|
|
'/^(\s*\$' . $var . '\s*=\s*)[^;]*;/m',
|
|
fn ($m) => $m[1] . var_export($value, true) . ';',
|
|
$src,
|
|
1
|
|
);
|
|
};
|
|
$set('db_user', $app_user);
|
|
$set('db_pass', $app_pass);
|
|
|
|
if ($secret !== '' && strpos($src, 'APP_SECRET_KEY') === false) {
|
|
$src = preg_replace('/\?>\s*$/', '', $src);
|
|
$src .= "\nif (!defined('APP_SECRET_KEY')) {\n\tdefine('APP_SECRET_KEY', " . var_export($secret, true) . ");\n}\n";
|
|
}
|
|
|
|
if ($src !== $orig) {
|
|
file_put_contents($config, $src);
|
|
echo "[provision] app/config.php updated (database account / secret key)\n";
|
|
}
|