<?php
/**
* PassGram v3.0 - Installation Script
*
* This script initializes PassGram by:
* 1. Generating the Master Application Key (MAK)
* 2. Creating the first admin user
* 3. Creating the first group
* 4. Initializing the database
* 5. Generating an invite code for additional users
*/
require_once __DIR__ . '/autoload.php';
use PassGram\Core\Config;
use PassGram\Core\Database;
use PassGram\Security\Encryption;
use PassGram\Models\User;
use PassGram\Models\Group;
use PassGram\Models\Invite;
use PassGram\Helpers\Validator;
// ---------------------------------------------------------------------------
// Generate missing config files before Config is loaded.
// This lets the installer run on a completely fresh deployment where only
// autoload.php and install.php exist in the project root.
// ---------------------------------------------------------------------------
$configDir = __DIR__ . '/config';
if (!file_exists($configDir . '/config.php')) {
file_put_contents($configDir . '/config.php', <<<'CFGPHP'
<?php
/**
* PassGram v3.0 - Application Configuration
*
* Generated during installation.
* Edit base_url and set cookie_secure to true before deploying to production.
*/
return [
'name' => 'PassGram',
'version' => '3.0',
'base_url' => '', // e.g. https://your-domain.com (no trailing slash)
'debug' => false,
// Session settings
'session' => [
'timeout' => 3600, // seconds (60 minutes)
'name' => 'passgram_session',
'cookie_secure' => false, // set true when serving over HTTPS
'cookie_httponly' => true,
'cookie_samesite' => 'Strict',
],
// Input validation / security rules
'security' => [
'password_min_length' => 12,
'username_min_length' => 3,
'username_max_length' => 32,
'max_login_attempts' => 5,
'lockout_duration' => 900, // seconds (15 minutes)
'invite_expiry_days' => 7,
],
// Filesystem paths used at runtime
'paths' => [
'pgp' => dirname(__DIR__) . '/data/pgp',
],
];
CFGPHP);
}
if (!file_exists($configDir . '/security.php')) {
file_put_contents($configDir . '/security.php', <<<'SECPHP'
<?php
/**
* PassGram v3.0 - Security Configuration
*
* Generated during installation. Keep this file backed up in a secure location.
* If the master_application_key is ever lost, all encrypted data is unrecoverable.
*/
return [
// Master Application Key (MAK) — set by install.php during first run
'master_application_key' => 'REPLACE_WITH_GENERATED_KEY_DURING_INSTALLATION',
// AES-256-GCM settings used by the Encryption class
'encryption' => [
'algorithm' => 'aes-256-gcm',
'key_length' => 32, // bytes
'iv_length' => 12, // bytes (96-bit nonce, recommended for GCM)
'tag_length' => 16, // bytes (128-bit authentication tag)
],
// Argon2id parameters for key derivation (PGP private-key passphrase)
'argon2' => [
'memory_cost' => 65536, // KB (64 MB)
'time_cost' => 4, // iterations
'threads' => 2,
],
// bcrypt cost factor for password hashing
'bcrypt_cost' => 12,
];
SECPHP);
}
// Check if already installed
$config = Config::getInstance();
if ($config->isInstalled()) {
die("PassGram is already installed. Delete config/security.php to reinstall.");
}
// Handle POST submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
// Get form data
$adminUsername = trim($_POST['admin_username'] ?? '');
$adminEmail = trim($_POST['admin_email'] ?? '');
$adminPassword = $_POST['admin_password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
// Validate inputs
$validator = new Validator($config);
if (!$validator->username($adminUsername)) {
throw new Exception($validator->getFirstError());
}
if (!$validator->email($adminEmail)) {
throw new Exception($validator->getFirstError());
}
if (!$validator->password($adminPassword)) {
throw new Exception($validator->getFirstError());
}
if ($adminPassword !== $confirmPassword) {
throw new Exception('Passwords do not match');
}
// Generate Master Application Key
$masterKey = Encryption::generateKey();
$masterKeyHex = bin2hex($masterKey);
// Write security.php with the generated MAK
$generatedDate = date('Y-m-d H:i:s');
file_put_contents($configDir . '/security.php', <<<SECPHP
<?php
/**
* PassGram v3.0 - Security Configuration
*
* Generated: {$generatedDate}
* Keep this file backed up in a secure location.
* If the master_application_key is ever lost, all encrypted data is unrecoverable.
*/
return [
// Master Application Key (MAK) — generated by install.php
'master_application_key' => '{$masterKeyHex}',
// AES-256-GCM settings used by the Encryption class
'encryption' => [
'algorithm' => 'aes-256-gcm',
'key_length' => 32, // bytes
'iv_length' => 12, // bytes (96-bit nonce, recommended for GCM)
'tag_length' => 16, // bytes (128-bit authentication tag)
],
// Argon2id parameters for key derivation (PGP private-key passphrase)
'argon2' => [
'memory_cost' => 65536, // KB (64 MB)
'time_cost' => 4, // iterations
'threads' => 2,
],
// bcrypt cost factor for password hashing
'bcrypt_cost' => 12,
];
SECPHP);
// Initialize database
$encryption = new Encryption($masterKey, $config->get('security.encryption'));
$db = new Database($encryption, $config->database());
$db->initialize();
// Create models
$userModel = new User($db, $config, $validator);
$groupModel = new Group($db, $validator);
$inviteModel = new Invite($db, $config, $validator);
// Create admin user
$admin = $userModel->create([
'username' => $adminUsername,
'email' => $adminEmail,
'password' => $adminPassword,
]);
// Create default group
$group = $groupModel->create([
'name' => 'Default Group',
'description' => 'Initial group for PassGram users',
'created_by' => $admin['id'],
]);
// Add admin to group
$userModel->addToGroup($admin['id'], $group['id']);
// Generate invite code for additional users
$invite = $inviteModel->generate($group['id'], $admin['id']);
// Success!
$success = true;
$inviteCode = $invite['code'];
} catch (Exception $e) {
$error = $e->getMessage();
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PassGram v3.0 - Installation</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Courier New', Courier, monospace;
background: #008080;
color: #000;
padding: 20px;
}
.container {
max-width: 600px;
margin: 40px auto;
background: #C0C0C0;
border: 3px outset #fff;
box-shadow: 3px 3px 0 #000;
}
.title-bar {
background: #5D009D;
color: #fff;
padding: 4px 8px;
font-weight: bold;
border-bottom: 2px solid #000;
}
.content {
padding: 20px;
}
h1 {
font-size: 20px;
margin-bottom: 20px;
color: #000;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"],
input[type="email"],
input[type="password"] {
width: 100%;
padding: 4px;
border: 2px inset #808080;
background: #fff;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
}
.button {
background: #C0C0C0;
border: 2px outset #fff;
padding: 6px 20px;
cursor: pointer;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
font-weight: bold;
}
.button:active {
border-style: inset;
}
.error {
background: #ff0000;
color: #fff;
padding: 10px;
margin-bottom: 15px;
border: 2px solid #800000;
}
.success {
background: #00ff00;
color: #000;
padding: 20px;
border: 2px solid #008000;
}
.info {
background: #ffff00;
padding: 10px;
margin-bottom: 15px;
border: 2px solid #808000;
}
.code {
background: #000;
color: #0f0;
padding: 10px;
font-family: 'Courier New', Courier, monospace;
word-break: break-all;
margin: 10px 0;
}
.requirements {
background: #fff;
border: 2px inset #808080;
padding: 15px;
margin-bottom: 20px;
}
.requirements h2 {
font-size: 16px;
margin-bottom: 10px;
}
.requirements ul {
margin-left: 20px;
}
.requirements li {
margin-bottom: 5px;
}
</style>
</head>
<body>
<div class="container">
<div class="title-bar">PassGram v3.0 - Installation</div>
<div class="content">
<?php if (isset($success) && $success): ?>
<div class="success">
<h1>Installation Successful!</h1>
<p><strong>PassGram has been installed successfully.</strong></p>
<p style="margin-top: 15px;">Admin username: <strong><?php echo htmlspecialchars($adminUsername); ?></strong></p>
<p>Admin email: <strong><?php echo htmlspecialchars($adminEmail); ?></strong></p>
<p style="margin-top: 20px;"><strong>Invite Code for Additional Users:</strong></p>
<div class="code"><?php echo htmlspecialchars($inviteCode); ?></div>
<div class="info" style="margin-top: 20px;">
<strong>IMPORTANT SECURITY NOTES:</strong>
<ul style="margin-left: 20px; margin-top: 10px;">
<li>Delete this install.php file immediately</li>
<li>Backup config/security.php in a secure location</li>
<li>Save the invite code - you'll need it to register more users</li>
<li>Configure HTTPS on your server</li>
<li>Update config/config.php with your domain</li>
</ul>
</div>
<p style="margin-top: 20px;">
<a href="/login.php" class="button">Go to Login Page</a>
</p>
</div>
<?php else: ?>
<h1>PassGram Installation</h1>
<div class="requirements">
<h2>Server Requirements:</h2>
<ul>
<li>PHP <?php echo phpversion(); ?> <?php echo version_compare(phpversion(), '7.4.0', '>=') ? '✓' : '✗ (7.4+ required)'; ?></li>
<li>OpenSSL Extension <?php echo extension_loaded('openssl') ? '✓' : '✗ (required)'; ?></li>
<li>JSON Extension <?php echo extension_loaded('json') ? '✓' : '✗ (required)'; ?></li>
<li>Writable data/ directory <?php echo is_writable(__DIR__ . '/data') ? '✓' : '✗ (required)'; ?></li>
<li>Writable config/ directory <?php echo is_writable(__DIR__ . '/config') ? '✓' : '✗ (required)'; ?></li>
</ul>
</div>
<?php if (isset($error)): ?>
<div class="error">
<strong>Error:</strong> <?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<div class="info">
<strong>Note:</strong> This will create the first admin user and generate a secure Master Application Key. Keep this key secure - if lost, all data will be unrecoverable!
</div>
<form method="POST">
<div class="form-group">
<label for="admin_username">Admin Username:</label>
<input type="text" id="admin_username" name="admin_username" required
value="<?php echo htmlspecialchars($_POST['admin_username'] ?? ''); ?>">
<small>3-32 characters, letters, numbers, underscore only</small>
</div>
<div class="form-group">
<label for="admin_email">Admin Email:</label>
<input type="email" id="admin_email" name="admin_email" required
value="<?php echo htmlspecialchars($_POST['admin_email'] ?? ''); ?>">
</div>
<div class="form-group">
<label for="admin_password">Admin Password:</label>
<input type="password" id="admin_password" name="admin_password" required>
<small>Minimum 12 characters, must include uppercase, lowercase, numbers, and special characters</small>
</div>
<div class="form-group">
<label for="confirm_password">Confirm Password:</label>
<input type="password" id="confirm_password" name="confirm_password" required>
</div>
<div class="form-group">
<button type="submit" class="button">Install PassGram</button>
</div>
</form>
<?php endif; ?>
</div>
</div>
</body>
</html>