mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-12 15:46:06 +00:00
feat(postgres): in-panel backup/restore and consistent CLI backend
Two PostgreSQL gaps on the panel:
1. x-ui setting and other CLI subcommands read XUI_DB_TYPE/XUI_DB_DSN from
the process environment, which systemd injects via EnvironmentFile but a
plain shell invocation does not. On a PostgreSQL install the CLI silently
fell back to SQLite, so changes made from the management menu never
reached the panel's database. Load the systemd EnvironmentFile
(/etc/default/x-ui and distro equivalents) at startup; godotenv.Load does
not override existing vars, so it stays a no-op for the managed service.
2. DB backup/restore (panel endpoints and the Telegram bot) only handled the
SQLite file, so on PostgreSQL Back Up returned a stale/absent x-ui.db and
Restore silently did nothing. Add pg_dump/pg_restore based backup/restore:
- GetDb/ImportDB run pg_dump (custom format) / pg_restore, passing
credentials via the PG* environment instead of argv.
- getDb downloads x-ui.dump on Postgres, x-ui.db on SQLite.
- Telegram backup sends the matching file via GetDb.
- BackupModal shows a Postgres note and accepts .dump; the dist page
injects window.X_UI_DB_TYPE; new strings translated for all locales.
- install.sh installs postgresql-client for the external-DSN path and
points the user to in-panel Backup & Restore.
Closes #4658
This commit is contained in:
Vendored
+1
@@ -26,6 +26,7 @@ interface SubPageData {
|
||||
interface Window {
|
||||
X_UI_BASE_PATH?: string;
|
||||
X_UI_CUR_VER?: string;
|
||||
X_UI_DB_TYPE?: string;
|
||||
__SUB_PAGE_DATA__?: SubPageData;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ interface BackupModalProps {
|
||||
|
||||
export default function BackupModal({ open, basePath: _basePath, onClose, onBusy }: BackupModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const isPostgres = window.X_UI_DB_TYPE === 'postgres';
|
||||
|
||||
function exportDb() {
|
||||
window.location.href = (window.X_UI_BASE_PATH || '') + 'panel/api/server/getDb';
|
||||
@@ -27,7 +28,7 @@ export default function BackupModal({ open, basePath: _basePath, onClose, onBusy
|
||||
function importDb() {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = '.db';
|
||||
fileInput.accept = isPostgres ? '.dump' : '.db';
|
||||
fileInput.addEventListener('change', async (e) => {
|
||||
const dbFile = (e.target as HTMLInputElement).files?.[0];
|
||||
if (!dbFile) return;
|
||||
@@ -65,11 +66,18 @@ export default function BackupModal({ open, basePath: _basePath, onClose, onBusy
|
||||
footer={null}
|
||||
onCancel={onClose}
|
||||
>
|
||||
{isPostgres && (
|
||||
<div className="backup-description" style={{ marginBottom: 16 }}>
|
||||
{t('pages.index.backupPostgresNote')}
|
||||
</div>
|
||||
)}
|
||||
<div className="backup-list">
|
||||
<div className="backup-item">
|
||||
<div className="backup-meta">
|
||||
<div className="backup-title">{t('pages.index.exportDatabase')}</div>
|
||||
<div className="backup-description">{t('pages.index.exportDatabaseDesc')}</div>
|
||||
<div className="backup-description">
|
||||
{isPostgres ? t('pages.index.exportDatabasePgDesc') : t('pages.index.exportDatabaseDesc')}
|
||||
</div>
|
||||
</div>
|
||||
<Button type="primary" onClick={exportDb} icon={<DownloadOutlined />} />
|
||||
</div>
|
||||
@@ -77,7 +85,9 @@ export default function BackupModal({ open, basePath: _basePath, onClose, onBusy
|
||||
<div className="backup-item">
|
||||
<div className="backup-meta">
|
||||
<div className="backup-title">{t('pages.index.importDatabase')}</div>
|
||||
<div className="backup-description">{t('pages.index.importDatabaseDesc')}</div>
|
||||
<div className="backup-description">
|
||||
{isPostgres ? t('pages.index.importDatabasePgDesc') : t('pages.index.importDatabaseDesc')}
|
||||
</div>
|
||||
</div>
|
||||
<Button type="primary" onClick={importDb} icon={<UploadOutlined />} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user