Ctrl K
ComunidadGuías…

make-password.php

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?php

$args     = array_slice($argv, 1);
$force    = in_array('--force', $args, true);
$operands = array_values(array_filter($args, static fn(string $a): bool => !str_starts_with($a, '--')));

if (empty($operands[0])) {
    die('Usage: php make-password.php <password> [--force]');
}
$password = $operands[0];

// The docs tell a self-hoster to run this on their own server, and it writes
// .env into whatever directory it is run from. A second run there — a test, a
// forgotten password — used to overwrite the live file in place: that is how a
// production checkout ended up holding a cleartext LAMB_TEST_PASSWORD and a
// login hash the running site never read (issues #597, #598). Refuse by
// default, so replacing a real .env has to be asked for.
if (!$force && file_exists('.env')) {
    fwrite(
        STDERR,
        '.env already exists in ' . getcwd() . PHP_EOL
        . 'Refusing to overwrite it. Move it aside, or re-run with --force.' . PHP_EOL
    );
    exit(1);
}

// Highlight a weak password rather than refusing it: communicate, don't block
// (a refusal would also break test fixtures that pass a short password). The
// warning goes to STDERR so STDOUT stays just the hash for callers to copy.
const MIN_PASSWORD_LENGTH = 12;
if (mb_strlen($password) < MIN_PASSWORD_LENGTH) {
    fwrite(
        STDERR,
        'Warning: that password is weak (under ' . MIN_PASSWORD_LENGTH
        . ' characters). Consider a longer passphrase.' . PHP_EOL
    );
}

$hash = base64_encode(password_hash($password, PASSWORD_DEFAULT));

$test_port = $_ENV['LAMB_TEST_PORT'] ?? '8747';
$site_url  = $_ENV['DDEV_PRIMARY_URL'] ?? "http://0.0.0.0:{$test_port}";
if (($_ENV['PWD'] ?? '') === '/srv/app') {
    // Inside the Docker dev container FrankenPHP serves the site locally.
    $site_url = 'http://localhost';
}

/**
 * One `.env` value, quoted so phpdotenv reads back exactly what was passed.
 *
 * Single quotes are this file's normal form and hold everything without
 * escaping — spaces, `"`, `\`, `$`, `#`, non-ASCII — because phpdotenv treats a
 * single-quoted value as literal. The two characters they cannot hold are a
 * single quote itself and a real newline: both end the value early, and
 * phpdotenv then refuses the *whole file* with InvalidFileException. Bootstrap\
 * load_dotenv() calls safeLoad(), which only swallows a missing file, so a
 * password containing an apostrophe left `composer serve` throwing on every
 * request — with LAMB_LOGIN_PASSWORD unreadable even though its own line was
 * well-formed.
 *
 * Those two fall back to double quotes, which phpdotenv does unescape. `$` is
 * escaped there as well: a double-quoted value is interpolated, so `${HOME}` in
 * a password would otherwise be substituted instead of stored.
 */
function env_value(string $value): string
{
    if (!str_contains($value, "'") && !str_contains($value, "\n") && !str_contains($value, "\r")) {
        return "'" . $value . "'";
    }

    $escaped = str_replace(['\\', '"', '$'], ['\\\\', '\\"', '\\$'], $value);
    $escaped = str_replace(["\r", "\n"], ['\\r', '\\n'], $escaped);

    return '"' . $escaped . '"';
}

$data  = 'SITE_URL=' . env_value($site_url) . PHP_EOL;
$data .= 'LAMB_TEST_PORT=' . env_value($test_port) . PHP_EOL;
$data .= 'LAMB_LOGIN_PASSWORD=' . env_value($hash) . PHP_EOL;
// The plaintext password is only useful to the acceptance suite (it logs in
// with $_ENV['LAMB_TEST_PASSWORD']). Keep it out of .env by default so a
// self-hoster's setup file never carries the cleartext secret; the test harness
// opts in via LAMB_WRITE_TEST_PASSWORD. Use getenv() rather than $_ENV here: CI
// runs this script under a variables_order that does not populate $_ENV from the
// environment, but getenv() reads the process environment regardless.
if (getenv('LAMB_WRITE_TEST_PASSWORD')) {
    $data .= 'LAMB_TEST_PASSWORD=' . env_value($password) . PHP_EOL;
}
// 0600 before the secret goes in, not the umask default: this file holds the
// login hash, and file_put_contents() alone left it whatever the umask allowed —
// 644 normally, 664 under 0002, and 666 under 0000, which is not unusual in
// containers. World-readable hands a local user the bcrypt hash to crack
// offline; world-*writable* lets them replace it and own the login outright.
// bootstrap_db() already applies the same reasoning to the data directory
// ("under a permissive umask 0777 would leave the database world-readable and
// replaceable"). Creating the file first and tightening it before writing means
// there is no moment where the hash sits in a readable one.
if (!file_exists('.env')) {
    touch('.env');
}
if (!chmod('.env', 0600)) {
    user_error('Could not restrict permissions on .env; check it is not world-readable', E_USER_WARNING);
}
$env_out = file_put_contents('.env', $data);
if (!$env_out) {
    user_error('Problem saving .env', E_USER_WARNING);
}

echo $hash;