'PassGram', 'version' => '5.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', 'gnupg' => dirname(__DIR__) . '/data/gnupg', // GnuPG keyring directory ], // GnuPG multi-recipient group sharing (set by setup.php) 'gnupg_enabled' => false, ]; CFGPHP); } if (!file_exists($configDir . '/security.php')) { file_put_contents($configDir . '/security.php', <<<'SECPHP' '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 { $adminUsername = trim($_POST['admin_username'] ?? ''); $adminEmail = trim($_POST['admin_email'] ?? ''); $adminPassword = $_POST['admin_password'] ?? ''; $confirmPassword = $_POST['confirm_password'] ?? ''; $enableGnuPG = isset($_POST['enable_gnupg']) && $gnupgAvailable; // 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', << '{$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); // Write config.php with GnuPG setting $gnupgEnabledStr = $enableGnuPG ? 'true' : 'false'; file_put_contents($configDir . '/config.php', << 'PassGram', 'version' => '5.0', 'base_url' => '', // e.g. https://your-domain.com (no trailing slash) 'debug' => false, // Session settings 'session' => [ 'timeout' => 3600, 'name' => 'passgram_session', 'cookie_secure' => false, '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, 'invite_expiry_days' => 7, ], // Filesystem paths used at runtime 'paths' => [ 'pgp' => dirname(__DIR__) . '/data/pgp', 'gnupg' => dirname(__DIR__) . '/data/gnupg', ], // GnuPG multi-recipient group sharing // When true, every new user receives a GnuPG key pair at registration // and group credential shares are encrypted once for all members. 'gnupg_enabled' => {$gnupgEnabledStr}, ]; CFGPHP); // 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, ]); // If GnuPG is enabled, generate a key pair for the admin user $adminGnuPGKey = null; if ($enableGnuPG) { // Admin's GnuPG passphrase = their login password for initial setup. // They can rotate it later via the key-management UI. $gnupgHome = __DIR__ . '/data/gnupg'; $gnupg = new GnuPGEncryption($gnupgHome); $adminGnuPGKey = $gnupg->generateKeyPair( $adminUsername, $adminEmail, $adminPassword // passphrase = login password at setup time ); $userModel->setGnuPGFingerprint($admin['id'], $adminGnuPGKey['fingerprint']); } // 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']); $inviteCode = $invite['code']; $success = true; } catch (\Exception $e) { $error = $e->getMessage(); } } ?> PassGram v5.0 - Setup
PassGram v5.0 - Setup

Setup Complete!

PassGram v5.0 has been installed successfully.

Admin username:

Admin email:

GnuPG Mode: Enabled

Admin GnuPG fingerprint:

Your GnuPG passphrase is the same as your login password (set during this setup). You can rotate it from the key-management page after logging in.

Invite Code for Additional Users:

IMPORTANT SECURITY NOTES:
  • Delete this setup.php file immediately
  • Backup config/security.php in a secure location
  • Save the invite code — you'll need it to register more users
  • Configure HTTPS on your server
  • Update config/config.php with your domain
  • The GnuPG keyring lives in data/gnupg/ — keep that directory backed up

Go to Login Page

PassGram Setup

Server Requirements:

  • PHP =')):?> ✗ (7.4+ required)
  • OpenSSL Extension ✗ (required)
  • JSON Extension ✗ (required)
  • Writable data/ directory ✗ (required)
  • Writable config/ directory ✗ (required)
  • GnuPG Extension (php-gnupg) — optional, for group sharing ◯ Not available —
Error:
Note: 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!

GnuPG Group Sharing

The php-gnupg extension is available on this server. Enabling GnuPG group sharing allows a credential to be encrypted once for an entire group, so any member can decrypt it with their own private key passphrase — no per-user re-encryption needed.

When enabled, every new user is issued a GnuPG key pair at registration. The keyring is stored in data/gnupg/ on the server.

The php-gnupg extension is not installed on this server. To enable GnuPG group sharing, install the extension (apt install php-gnupg or equivalent) and re-run setup.

PassGram will use the standard OpenSSL-based per-user encryption for credential sharing.

3–32 characters, letters, numbers, underscore only
Minimum 12 characters, must include uppercase, lowercase, numbers, and special characters