mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
Add DKIM auto-provisioning, OS/panel self-update with self-healing
- AccountManager: auto-generate DKIM keypair + inject SPF/DKIM/DMARC DNS records on account create - AccountManager: rotateDKIM() method for key rotation with new selector - New dkim.php endpoint: list/view/rotate/provision DKIM keys per domain - schema.sql: add dkim_keys table - install.sh: install opendkim, wire into Postfix milter, fix dotfile copy (. vs *), fix config.ini permissions (root:www-data 640), copy VERSION to web root, add opendkim to service restart - api/index.php: fix NOVACPX_ROOT path (was 2 levels too high), fix CORS ports (8880-8883), VERSION fallback to /opt/novacpx-src - api/.htaccess: route all /api/* requests through index.php - system.php: check-os-update, apply-os-update (self-healing: auto-restart downed services, restore web root if panel ports go down), check-novacpx-update, apply-novacpx-update (PHP syntax validation before deploy, backup + restore on failure) - admin.js: Updates page now shows both NovaCPX panel updates and OS package upgrades in one section; sidebar badge shows combined count Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* DKIM key management endpoint
|
||||
* Actions: list, rotate, view
|
||||
*/
|
||||
|
||||
require_once NOVACPX_LIB . '/AccountManager.php';
|
||||
require_once NOVACPX_LIB . '/DNSManager.php';
|
||||
|
||||
$db = DB::getInstance();
|
||||
$user = $currentUser;
|
||||
|
||||
switch ($action) {
|
||||
|
||||
case 'list':
|
||||
// Admin: all domains. User/reseller: their own accounts
|
||||
if ($user['role'] === 'admin') {
|
||||
$keys = $db->fetchAll(
|
||||
"SELECT dk.*, a.username FROM dkim_keys dk JOIN accounts a ON dk.account_id = a.id ORDER BY dk.domain"
|
||||
);
|
||||
} else {
|
||||
$keys = $db->fetchAll(
|
||||
"SELECT dk.* FROM dkim_keys dk
|
||||
JOIN accounts a ON dk.account_id = a.id
|
||||
WHERE a.user_id = ?
|
||||
ORDER BY dk.domain",
|
||||
[$user['id']]
|
||||
);
|
||||
}
|
||||
foreach ($keys as &$k) { unset($k['private_key_path']); }
|
||||
Response::json(['keys' => $keys]);
|
||||
break;
|
||||
|
||||
case 'view':
|
||||
$domain = trim($body['domain'] ?? '');
|
||||
if (!$domain) Response::error('domain required', 400);
|
||||
|
||||
$row = $db->fetchOne("SELECT * FROM dkim_keys WHERE domain = ?", [$domain]);
|
||||
if (!$row) Response::error('No DKIM key found for domain', 404);
|
||||
|
||||
// Access control
|
||||
if ($user['role'] !== 'admin') {
|
||||
$acct = $db->fetchOne("SELECT id FROM accounts WHERE id = ? AND user_id = ?", [$row['account_id'], $user['id']]);
|
||||
if (!$acct) Response::error('Forbidden', 403);
|
||||
}
|
||||
|
||||
unset($row['private_key_path']);
|
||||
// Also return the full DNS TXT value
|
||||
$row['dns_record_name'] = "mail._domainkey.{$domain}";
|
||||
$row['dns_record_value'] = "v=DKIM1; k=rsa; p={$row['public_key']}";
|
||||
Response::json(['key' => $row]);
|
||||
break;
|
||||
|
||||
case 'rotate':
|
||||
$domain = trim($body['domain'] ?? '');
|
||||
if (!$domain) Response::error('domain required', 400);
|
||||
|
||||
$acct = $db->fetchOne("SELECT a.id, a.user_id FROM accounts a JOIN domains d ON d.account_id = a.id WHERE d.domain = ? AND d.type = 'main'", [$domain]);
|
||||
if (!$acct) Response::error('Domain not found', 404);
|
||||
|
||||
if ($user['role'] !== 'admin' && (int)$acct['user_id'] !== (int)$user['id']) {
|
||||
Response::error('Forbidden', 403);
|
||||
}
|
||||
|
||||
$selector = AccountManager::rotateDKIM((int)$acct['id'], $domain);
|
||||
Response::json(['ok' => true, 'selector' => $selector, 'message' => "DKIM rotated. New selector: {$selector}._domainkey.{$domain}"]);
|
||||
break;
|
||||
|
||||
case 'provision':
|
||||
// Re-provision SPF/DKIM/DMARC for a domain (admin only or own account)
|
||||
$domain = trim($body['domain'] ?? '');
|
||||
if (!$domain) Response::error('domain required', 400);
|
||||
|
||||
$acct = $db->fetchOne("SELECT a.id, a.user_id FROM accounts a JOIN domains d ON d.account_id = a.id WHERE d.domain = ?", [$domain]);
|
||||
if (!$acct) Response::error('Domain not found', 404);
|
||||
|
||||
if ($user['role'] !== 'admin' && (int)$acct['user_id'] !== (int)$user['id']) {
|
||||
Response::error('Forbidden', 403);
|
||||
}
|
||||
|
||||
AccountManager::provisionEmailDNS((int)$acct['id'], $domain);
|
||||
Response::json(['ok' => true, 'message' => "SPF, DKIM, and DMARC records provisioned for {$domain}"]);
|
||||
break;
|
||||
|
||||
default:
|
||||
Response::error("Unknown action: $action", 404);
|
||||
}
|
||||
Reference in New Issue
Block a user