✅ 系统已安装

如需重新安装,请先删除 data/installed.lock 文件。

进入登录页
'); } $step = (int)($_GET['step'] ?? $_POST['step'] ?? 1); $error = ''; if ($step > 1 && empty($_SESSION['install_step_done'])) { $step = 1; } /** * 环境检测 */ function check_environment() { $checks = []; $checks[] = [ 'name' => 'PHP 版本 ≥ 7.4', 'ok' => version_compare(PHP_VERSION, '7.4.0', '>='), 'value' => PHP_VERSION, 'tip' => '当前版本过低,请升级 PHP。', ]; $checks[] = [ 'name' => 'IMAP 扩展', 'ok' => function_exists('imap_open'), 'value' => function_exists('imap_open') ? '已开启' : '未开启', 'tip' => '需要在 PHP 配置中启用 php_imap 扩展,或联系主机商开启。', ]; $checks[] = [ 'name' => 'PDO SQLite 驱动', 'ok' => extension_loaded('pdo_sqlite'), 'value' => extension_loaded('pdo_sqlite') ? '已开启' : '未开启', 'tip' => '需要 pdo_sqlite 扩展来存储邮件。', ]; $checks[] = [ 'name' => 'mbstring 扩展', 'ok' => function_exists('mb_convert_encoding'), 'value' => function_exists('mb_convert_encoding') ? '已开启' : '未开启', 'tip' => '用于处理邮件编码转换。', ]; $checks[] = [ 'name' => 'Session 支持', 'ok' => function_exists('session_start'), 'value' => function_exists('session_start') ? '已开启' : '未开启', 'tip' => '用于登录状态保持。', ]; $checks[] = [ 'name' => 'cURL 扩展', 'ok' => function_exists('curl_init'), 'value' => function_exists('curl_init') ? '已开启' : '未开启', 'tip' => '用于邮件转发功能。未开启不影响收信。', 'warn' => true, ]; $writable = is_dir(DATA_DIR) ? is_writable(DATA_DIR) : is_writable(__DIR__); $checks[] = [ 'name' => '目录可写', 'ok' => $writable, 'value' => $writable ? '可写' : '不可写', 'tip' => '需要能创建 data/ 目录并写入文件。请设置目录权限为 755 或 777。', ]; $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'); $checks[] = [ 'name' => 'HTTPS(强烈建议)', 'ok' => $https, 'value' => $https ? '已开启' : '未开启', 'tip' => '未启用 HTTPS 时,登录密码和邮件内容会以明文传输。', 'warn' => true, ]; return $checks; } /** * 创建数据库 */ function do_install_database() { if (!is_dir(DATA_DIR)) { if (!mkdir(DATA_DIR, 0755, true)) throw new Exception('无法创建 data 目录'); } $htaccess = DATA_DIR . '/.htaccess'; if (!file_exists($htaccess)) { file_put_contents($htaccess, "Require all denied\nDeny from all\n"); } $db = new PDO('sqlite:' . DB_FILE); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->exec("CREATE TABLE IF NOT EXISTS mails ( id INTEGER PRIMARY KEY AUTOINCREMENT, account TEXT NOT NULL, account_label TEXT, uid TEXT NOT NULL, from_addr TEXT, subject TEXT, mail_date TEXT, mail_ts INTEGER DEFAULT 0, body TEXT, body_html TEXT, attachments TEXT, blocked INTEGER DEFAULT 0, is_starred INTEGER DEFAULT 0, is_important INTEGER DEFAULT 0, is_read INTEGER DEFAULT 1, created_at TEXT, UNIQUE(account, uid) )"); $db->exec("CREATE INDEX IF NOT EXISTS idx_account ON mails(account)"); $db->exec("CREATE INDEX IF NOT EXISTS idx_mail_ts ON mails(mail_ts)"); $db->exec("CREATE INDEX IF NOT EXISTS idx_blocked ON mails(blocked)"); $db->exec("CREATE INDEX IF NOT EXISTS idx_starred ON mails(is_starred)"); $db->exec("CREATE INDEX IF NOT EXISTS idx_read ON mails(is_read)"); $db->exec("CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, key_name TEXT UNIQUE NOT NULL, label TEXT NOT NULL, host TEXT NOT NULL, user TEXT NOT NULL, password TEXT NOT NULL, enabled INTEGER DEFAULT 1, last_fetch TEXT, created_at TEXT )"); $db->exec("CREATE TABLE IF NOT EXISTS block_rules ( id INTEGER PRIMARY KEY AUTOINCREMENT, keyword TEXT NOT NULL, enabled INTEGER DEFAULT 1, created_at TEXT )"); $db->exec("CREATE TABLE IF NOT EXISTS forward_rules ( id INTEGER PRIMARY KEY AUTOINCREMENT, keyword TEXT NOT NULL, channel TEXT NOT NULL, target TEXT NOT NULL, enabled INTEGER DEFAULT 1, created_at TEXT )"); $db->exec("CREATE TABLE IF NOT EXISTS blocked_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, account TEXT NOT NULL, uid TEXT NOT NULL, from_addr TEXT, subject TEXT, mail_date TEXT, matched TEXT, created_at TEXT, UNIQUE(account, uid) )"); $db->exec("CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT )"); // 默认拦截规则 $count = $db->query("SELECT COUNT(*) FROM block_rules")->fetchColumn(); if ($count == 0) { $stmt = $db->prepare("INSERT INTO block_rules (keyword, enabled, created_at) VALUES (?, 1, ?)"); foreach (['取消订阅', '退订', 'unsubscribe'] as $kw) { $stmt->execute([$kw, date('Y-m-d H:i:s')]); } } // 默认设置 $db->exec("INSERT OR IGNORE INTO settings (key, value) VALUES ('fetch_days', '7')"); $db->exec("INSERT OR IGNORE INTO settings (key, value) VALUES ('auto_delete_days', '0')"); // 附件目录 $attachDir = DATA_DIR . '/attachments'; if (!is_dir($attachDir)) mkdir($attachDir, 0755, true); // 用户库 $udb = new PDO('sqlite:' . USERS_DB); $udb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $udb->exec("CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, created_at TEXT )"); return true; } /** * 创建管理员 */ function do_create_admin($username, $password) { if (strlen($username) < 3) throw new Exception('用户名至少 3 个字符'); if (strlen($password) < 8) throw new Exception('密码至少 8 位'); $udb = new PDO('sqlite:' . USERS_DB); $udb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $check = $udb->prepare("SELECT id FROM users WHERE username = ?"); $check->execute([$username]); if ($check->fetchColumn()) throw new Exception('用户名已存在,请换一个'); $stmt = $udb->prepare("INSERT INTO users (username, password_hash, created_at) VALUES (?, ?, ?)"); $stmt->execute([$username, password_hash($password, PASSWORD_DEFAULT), date('Y-m-d H:i:s')]); return true; } /** * 添加邮箱 */ function do_add_account($key, $label, $host, $user, $pass) { $db = new PDO('sqlite:' . DB_FILE); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt = $db->prepare("INSERT INTO accounts (key_name, label, host, user, password, enabled, created_at) VALUES (?, ?, ?, ?, ?, 1, ?)"); $stmt->execute([$key, $label, $host, $user, $pass, date('Y-m-d H:i:s')]); return true; } /** * POST 处理 */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; try { if ($action === 'step1') { $checks = check_environment(); $blocked = array_filter($checks, fn($c) => !$c['ok'] && empty($c['warn'])); if ($blocked) { $error = '请先解决所有标红的检测项'; } else { $_SESSION['install_step_done'] = 1; header('Location: install.php?step=2'); exit; } } if ($action === 'step2') { do_install_database(); $_SESSION['install_step_done'] = 2; header('Location: install.php?step=3'); exit; } if ($action === 'step3') { $username = trim($_POST['username'] ?? ''); $password = $_POST['password'] ?? ''; $confirm = $_POST['confirm'] ?? ''; if ($password !== $confirm) throw new Exception('两次输入的密码不一致'); do_create_admin($username, $password); $_SESSION['admin_username'] = $username; $_SESSION['install_step_done'] = 3; header('Location: install.php?step=4'); exit; } if ($action === 'step4') { if (isset($_POST['skip'])) { $_SESSION['install_step_done'] = 4; header('Location: install.php?step=5'); exit; } $key = trim($_POST['key_name'] ?? ''); $label = trim($_POST['label'] ?? ''); $host = trim($_POST['host'] ?? ''); $user = trim($_POST['user'] ?? ''); $pass = $_POST['password'] ?? ''; if ($key === '' || $label === '' || $host === '' || $user === '' || $pass === '') { throw new Exception('请填写完整,或点击跳过'); } if (!preg_match('/^[a-z0-9_]+$/', $key)) { throw new Exception('标识只能包含小写字母、数字、下划线'); } $mbox = @imap_open($host, $user, $pass, 0, 1); if (!$mbox) throw new Exception('连接失败:' . imap_last_error()); imap_close($mbox); do_add_account($key, $label, $host, $user, $pass); $_SESSION['install_step_done'] = 4; $_SESSION['added_account'] = $label; header('Location: install.php?step=5'); exit; } if ($action === 'finish') { file_put_contents(LOCK_FILE, json_encode([ 'installed_at' => date('Y-m-d H:i:s'), 'version' => '1.0.0', ])); unset($_SESSION['install_step_done']); header('Location: install.php?step=done'); exit; } } catch (Exception $e) { $error = $e->getMessage(); } } /** * 完成页 */ if (isset($_GET['step']) && $_GET['step'] === 'done') { ?> 安装完成 - Zmail System
🎉

安装完成

Zmail System 已就绪。

  • 管理员账号:
  • 已添加邮箱:
⚠️ 重要:请立即删除 install.php
否则他人可以重新运行安装程序,覆盖你的数据库。

接下来建议:

进入登录页
!$c['ok'] && empty($c['warn'])); $can_proceed = empty($blocked); ?> 安装向导 - 第 <?= $step ?> 步 - Zmail System

📬 Zmail System 安装向导

第 1 步:环境检测

✅ 所有必需项均已通过
❌ 有 项未通过,请先解决

第 2 步:创建数据库

系统将在 data/ 目录下创建:

SQLite 是文件型数据库,不需要额外申请,适合虚拟主机。

第 3 步:创建管理员账号

至少 3 个字符
建议包含大小写字母和数字

第 4 步:添加第一个邮箱

这一步可以跳过,安装完成后再添加。

[ 'label' => 'Gmail', 'host' => '{imap.gmail.com:993/imap/ssl}INBOX', 'tip' => '需在 Google 账户生成「应用专用密码」。', ], 'qq' => [ 'label' => 'QQ 邮箱', 'host' => '{imap.qq.com:993/imap/ssl}INBOX', 'tip' => '开启 IMAP 服务后获得 16 位授权码,用户名填 QQ 号。', ], 'outlook' => [ 'label' => 'Outlook', 'host' => '{outlook.office365.com:993/imap/ssl}INBOX', 'tip' => '需使用应用密码;部分账户默认关闭 IMAP。', ], '163' => [ 'label' => '163 邮箱', 'host' => '{imap.163.com:993/imap/ssl}INBOX', 'tip' => '开启 IMAP 后生成授权码。', ], 'custom' => [ 'label' => '自定义', 'host' => '', 'tip' => '格式:{imap.服务器:993/imap/ssl}INBOX', ], ]; ?>

第 5 步:确认安装

✅ 所有配置已完成
点击下方按钮后,系统会生成安装锁文件,install.php 将无法再次访问。