mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-31 17:39:35 +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:
1
.github/workflows/claude-issue-bot.yml
vendored
1
.github/workflows/claude-issue-bot.yml
vendored
@@ -16,6 +16,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
claude_args: "--max-turns 25"
|
||||
prompt: |
|
||||
|
||||
17
.vscode/launch.json
vendored
17
.vscode/launch.json
vendored
@@ -17,5 +17,22 @@
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Run 3x-ui (Postgres)",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"XUI_DEBUG": "true",
|
||||
"XUI_LOG_FOLDER": "x-ui",
|
||||
"XUI_BIN_FOLDER": "x-ui",
|
||||
"XUI_DB_TYPE": "postgres",
|
||||
"XUI_DB_DSN": "postgres://xui:xuipass@127.0.0.1:5432/xui?sslmode=disable",
|
||||
"PATH": "C:\\Program Files\\PostgreSQL\\18\\bin;${env:PATH}"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -121,6 +121,19 @@ func GetDBDSN() string {
|
||||
return strings.TrimSpace(os.Getenv("XUI_DB_DSN"))
|
||||
}
|
||||
|
||||
// GetEnvFilePaths returns the candidate service environment file paths (the file
|
||||
// systemd loads via EnvironmentFile) across the supported distro families.
|
||||
func GetEnvFilePaths() []string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return nil
|
||||
}
|
||||
return []string{
|
||||
"/etc/default/x-ui",
|
||||
"/etc/conf.d/x-ui",
|
||||
"/etc/sysconfig/x-ui",
|
||||
}
|
||||
}
|
||||
|
||||
// GetLogFolder returns the path to the log folder based on environment variables or platform defaults.
|
||||
func GetLogFolder() string {
|
||||
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
|
||||
|
||||
1
frontend/src/env.d.ts
vendored
1
frontend/src/env.d.ts
vendored
@@ -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>
|
||||
|
||||
43
install.sh
43
install.sh
@@ -218,6 +218,41 @@ EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
ensure_pg_client() {
|
||||
if command -v pg_dump > /dev/null 2>&1 && command -v pg_restore > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
echo -e "${yellow}Installing PostgreSQL client tools (pg_dump/pg_restore) for in-panel backup...${plain}" >&2
|
||||
case "${release}" in
|
||||
ubuntu | debian | armbian)
|
||||
apt-get update >&2 && apt-get install -y -q postgresql-client >&2 || return 1
|
||||
;;
|
||||
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
|
||||
dnf install -y -q postgresql >&2 || return 1
|
||||
;;
|
||||
centos)
|
||||
if [[ "${VERSION_ID}" =~ ^7 ]]; then
|
||||
yum install -y postgresql >&2 || return 1
|
||||
else
|
||||
dnf install -y -q postgresql >&2 || return 1
|
||||
fi
|
||||
;;
|
||||
arch | manjaro | parch)
|
||||
pacman -Sy --noconfirm postgresql >&2 || return 1
|
||||
;;
|
||||
opensuse-tumbleweed | opensuse-leap)
|
||||
zypper -q install -y postgresql >&2 || return 1
|
||||
;;
|
||||
alpine)
|
||||
apk add --no-cache postgresql-client >&2 || return 1
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
command -v pg_dump > /dev/null 2>&1 && command -v pg_restore > /dev/null 2>&1
|
||||
}
|
||||
|
||||
install_acme() {
|
||||
echo -e "${green}Installing acme.sh for SSL certificate management...${plain}"
|
||||
cd ~ || return 1
|
||||
@@ -941,6 +976,7 @@ EOF
|
||||
umask 022
|
||||
export XUI_DB_TYPE=postgres
|
||||
export XUI_DB_DSN="${xui_dsn}"
|
||||
ensure_pg_client || echo -e "${yellow}⚠ Could not install pg_dump/pg_restore. In-panel database backup/restore will be unavailable until you install the postgresql-client package.${plain}"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -989,6 +1025,13 @@ EOF
|
||||
echo -e "${yellow}⚠ SSL Certificate: Skipped — panel is HTTP-only. Use a reverse proxy or SSH tunnel.${plain}"
|
||||
fi
|
||||
|
||||
if [[ "$db_choice" == "2" ]]; then
|
||||
echo ""
|
||||
echo -e "${green}PostgreSQL backup & restore is built into the panel:${plain}"
|
||||
echo -e " ${blue}${SSL_SCHEME}://${SSL_HOST}:${config_port}/${config_webBasePath}${plain} → Backup & Restore"
|
||||
echo -e "${yellow} Back Up downloads a pg_dump .dump file; Restore reloads it via pg_restore.${plain}"
|
||||
fi
|
||||
|
||||
if [[ "$db_choice" == "2" && "$pg_local_installed" == "1" ]]; then
|
||||
echo ""
|
||||
echo -e "${green}═══════════════════════════════════════════${plain}"
|
||||
|
||||
18
main.go
18
main.go
@@ -432,9 +432,27 @@ func migrateDb() {
|
||||
fmt.Println("Migration done!")
|
||||
}
|
||||
|
||||
// loadServiceEnvFile loads the systemd EnvironmentFile so CLI subcommands like
|
||||
// "x-ui setting" hit the same database backend as the panel. godotenv.Load does
|
||||
// not override variables already in the environment, so it is a no-op for the
|
||||
// systemd-managed service.
|
||||
func loadServiceEnvFile() {
|
||||
for _, path := range config.GetEnvFilePaths() {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
continue
|
||||
}
|
||||
if err := godotenv.Load(path); err != nil {
|
||||
log.Printf("warning: failed to load env file %s: %v", path, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// main is the entry point of the 3x-ui application.
|
||||
// It parses command-line arguments to run the web server, migrate database, or update settings.
|
||||
func main() {
|
||||
loadServiceEnvFile()
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
runWebServer()
|
||||
return
|
||||
|
||||
@@ -81,6 +81,7 @@ func serveDistPage(c *gin.Context, name string) {
|
||||
if name != "login.html" {
|
||||
escapedVer := jsEscape.Replace(config.GetVersion())
|
||||
script += `;window.X_UI_CUR_VER="` + escapedVer + `"`
|
||||
script += `;window.X_UI_DB_TYPE="` + config.GetDBKind() + `"`
|
||||
}
|
||||
script += `;</script>`
|
||||
inject := []byte(script)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/logger"
|
||||
"github.com/mhsanaei/3x-ui/v3/web/entity"
|
||||
"github.com/mhsanaei/3x-ui/v3/web/global"
|
||||
@@ -279,6 +280,9 @@ func (a *ServerController) getDb(c *gin.Context) {
|
||||
}
|
||||
|
||||
filename := "x-ui.db"
|
||||
if database.IsPostgres() {
|
||||
filename = "x-ui.dump"
|
||||
}
|
||||
if !filenameRegex.MatchString(filename) {
|
||||
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("invalid filename"))
|
||||
return
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -1071,6 +1072,9 @@ func (s *ServerService) GetConfigJson() (any, error) {
|
||||
}
|
||||
|
||||
func (s *ServerService) GetDb() ([]byte, error) {
|
||||
if database.IsPostgres() {
|
||||
return s.exportPostgresDB()
|
||||
}
|
||||
// Update by manually trigger a checkpoint operation
|
||||
err := database.Checkpoint()
|
||||
if err != nil {
|
||||
@@ -1093,6 +1097,9 @@ func (s *ServerService) GetDb() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (s *ServerService) ImportDB(file multipart.File) error {
|
||||
if database.IsPostgres() {
|
||||
return s.importPostgresDB(file)
|
||||
}
|
||||
// Check if the file is a SQLite database
|
||||
isValidDb, err := database.IsSQLiteDB(file)
|
||||
if err != nil {
|
||||
@@ -1221,6 +1228,137 @@ func (s *ServerService) ImportDB(file multipart.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// pgConnEnv turns the configured PostgreSQL DSN into the PG* environment used by
|
||||
// pg_dump/pg_restore, keeping the password out of the process argument list.
|
||||
func pgConnEnv(dsn string) (env []string, dbname string, err error) {
|
||||
u, err := url.Parse(strings.TrimSpace(dsn))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if u.Scheme != "postgres" && u.Scheme != "postgresql" {
|
||||
return nil, "", common.NewErrorf("unsupported DSN scheme %q", u.Scheme)
|
||||
}
|
||||
dbname = strings.TrimPrefix(u.Path, "/")
|
||||
if dbname == "" {
|
||||
return nil, "", common.NewError("PostgreSQL DSN is missing a database name")
|
||||
}
|
||||
host := u.Hostname()
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
port := u.Port()
|
||||
if port == "" {
|
||||
port = "5432"
|
||||
}
|
||||
env = append(os.Environ(), "PGHOST="+host, "PGPORT="+port, "PGDATABASE="+dbname)
|
||||
if user := u.User.Username(); user != "" {
|
||||
env = append(env, "PGUSER="+user)
|
||||
}
|
||||
if pass, ok := u.User.Password(); ok {
|
||||
env = append(env, "PGPASSWORD="+pass)
|
||||
}
|
||||
if sslmode := u.Query().Get("sslmode"); sslmode != "" {
|
||||
env = append(env, "PGSSLMODE="+sslmode)
|
||||
}
|
||||
return env, dbname, nil
|
||||
}
|
||||
|
||||
func (s *ServerService) exportPostgresDB() ([]byte, error) {
|
||||
bin, err := exec.LookPath("pg_dump")
|
||||
if err != nil {
|
||||
return nil, common.NewError("pg_dump not found on the server; install the postgresql-client package to back up a PostgreSQL database")
|
||||
}
|
||||
env, dbname, err := pgConnEnv(config.GetDBDSN())
|
||||
if err != nil {
|
||||
return nil, common.NewErrorf("invalid PostgreSQL DSN: %v", err)
|
||||
}
|
||||
cmd := exec.Command(bin, "--format=custom", "--no-owner", "--no-privileges", "--dbname", dbname)
|
||||
cmd.Env = env
|
||||
var out, stderr bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, common.NewErrorf("pg_dump failed: %v: %s", err, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s *ServerService) importPostgresDB(file multipart.File) error {
|
||||
header := make([]byte, 5)
|
||||
if _, err := file.ReadAt(header, 0); err != nil {
|
||||
return common.NewErrorf("Error reading dump file: %v", err)
|
||||
}
|
||||
if string(header) != "PGDMP" {
|
||||
return common.NewError("Invalid file: expected a PostgreSQL custom-format dump (.dump) created by this panel's Back Up")
|
||||
}
|
||||
if _, err := file.Seek(0, 0); err != nil {
|
||||
return common.NewErrorf("Error resetting file reader: %v", err)
|
||||
}
|
||||
|
||||
bin, err := exec.LookPath("pg_restore")
|
||||
if err != nil {
|
||||
return common.NewError("pg_restore not found on the server; install the postgresql-client package to restore a PostgreSQL database")
|
||||
}
|
||||
env, dbname, err := pgConnEnv(config.GetDBDSN())
|
||||
if err != nil {
|
||||
return common.NewErrorf("invalid PostgreSQL DSN: %v", err)
|
||||
}
|
||||
|
||||
tempFile, err := os.CreateTemp("", "x-ui-pg-restore-*.dump")
|
||||
if err != nil {
|
||||
return common.NewErrorf("Error creating temporary dump file: %v", err)
|
||||
}
|
||||
tempPath := tempFile.Name()
|
||||
defer os.Remove(tempPath)
|
||||
if _, err := io.Copy(tempFile, file); err != nil {
|
||||
tempFile.Close()
|
||||
return common.NewErrorf("Error saving dump: %v", err)
|
||||
}
|
||||
if err := tempFile.Close(); err != nil {
|
||||
return common.NewErrorf("Error closing temporary dump file: %v", err)
|
||||
}
|
||||
|
||||
xrayStopped := true
|
||||
defer func() {
|
||||
if xrayStopped {
|
||||
if errR := s.RestartXrayService(); errR != nil {
|
||||
logger.Warningf("Failed to restart Xray after DB restore error: %v", errR)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if errStop := s.StopXrayService(); errStop != nil {
|
||||
logger.Warningf("Failed to stop Xray before DB restore: %v", errStop)
|
||||
}
|
||||
|
||||
if errClose := database.CloseDB(); errClose != nil {
|
||||
logger.Warningf("Failed to close existing DB before restore: %v", errClose)
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin,
|
||||
"--clean", "--if-exists", "--no-owner", "--no-privileges",
|
||||
"--single-transaction", "--dbname", dbname, tempPath,
|
||||
)
|
||||
cmd.Env = env
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
runErr := cmd.Run()
|
||||
|
||||
if errInit := database.InitDB(config.GetDBPath()); errInit != nil {
|
||||
return common.NewErrorf("Restore finished but reopening the database failed: %v", errInit)
|
||||
}
|
||||
s.inboundService.MigrateDB()
|
||||
|
||||
if runErr != nil {
|
||||
return common.NewErrorf("pg_restore failed (database left unchanged): %v: %s", runErr, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
xrayStopped = false
|
||||
if err := s.RestartXrayService(); err != nil {
|
||||
return common.NewErrorf("Restored DB but failed to start Xray: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValidGeofileName validates that the filename is safe for geofile operations.
|
||||
// It checks for path traversal attempts and ensures the filename contains only safe characters.
|
||||
func (s *ServerService) IsValidGeofileName(filename string) bool {
|
||||
|
||||
@@ -3533,35 +3533,32 @@ func (t *Tgbot) sendBackup(chatId int64) {
|
||||
output := t.I18nBot("tgbot.messages.backupTime", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
||||
t.SendMsgToTgbot(chatId, output)
|
||||
|
||||
// Update by manually trigger a checkpoint operation
|
||||
err := database.Checkpoint()
|
||||
if err != nil {
|
||||
logger.Error("Error in trigger a checkpoint operation: ", err)
|
||||
}
|
||||
|
||||
// Send database backup
|
||||
file, err := os.Open(config.GetDBPath())
|
||||
// Send database backup (SQLite file, or a pg_dump archive on PostgreSQL)
|
||||
dbData, err := t.serverService.GetDb()
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
dbFilename := "x-ui.db"
|
||||
if database.IsPostgres() {
|
||||
dbFilename = "x-ui.dump"
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
document := tu.Document(
|
||||
tu.ID(chatId),
|
||||
tu.File(file),
|
||||
tu.FileFromBytes(dbData, dbFilename),
|
||||
)
|
||||
_, err = bot.SendDocument(ctx, document)
|
||||
cancel()
|
||||
if err != nil {
|
||||
logger.Error("Error in uploading backup: ", err)
|
||||
}
|
||||
} else {
|
||||
logger.Error("Error in opening db file for backup: ", err)
|
||||
logger.Error("Error in getting db backup: ", err)
|
||||
}
|
||||
|
||||
// Small delay between file sends
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// Send config.json backup
|
||||
file, err = os.Open(xray.GetConfigPath())
|
||||
file, err := os.Open(xray.GetConfigPath())
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "حدث خطأ أثناء استيراد قاعدة البيانات",
|
||||
"readDatabaseError": "حدث خطأ أثناء قراءة قاعدة البيانات",
|
||||
"getDatabaseError": "حدث خطأ أثناء استرجاع قاعدة البيانات",
|
||||
"getConfigError": "حدث خطأ أثناء استرجاع ملف الإعدادات"
|
||||
"getConfigError": "حدث خطأ أثناء استرجاع ملف الإعدادات",
|
||||
"backupPostgresNote": "تعمل هذه اللوحة على PostgreSQL. يقوم «النسخ الاحتياطي» بتنزيل أرشيف pg_dump (.dump)، و«الاستعادة» تعيد تحميله عبر pg_restore. يجب أن تكون أدوات عميل PostgreSQL (pg_dump و pg_restore) مثبَّتة على الخادم.",
|
||||
"exportDatabasePgDesc": "انقر لتنزيل نسخة PostgreSQL (.dump) من قاعدة بياناتك الحالية إلى جهازك.",
|
||||
"importDatabasePgDesc": "انقر لاختيار ورفع ملف .dump لاستعادة قاعدة بيانات PostgreSQL. سيؤدي هذا إلى استبدال جميع البيانات الحالية."
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "الواردات",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "An error occurred while importing the database.",
|
||||
"readDatabaseError": "An error occurred while reading the database.",
|
||||
"getDatabaseError": "An error occurred while retrieving the database.",
|
||||
"getConfigError": "An error occurred while retrieving the config file."
|
||||
"getConfigError": "An error occurred while retrieving the config file.",
|
||||
"backupPostgresNote": "This panel runs on PostgreSQL. Back Up downloads a pg_dump archive (.dump) and Restore loads it back with pg_restore. The server needs the PostgreSQL client tools (pg_dump and pg_restore) installed.",
|
||||
"exportDatabasePgDesc": "Click to download a PostgreSQL dump (.dump) of your current database to your device.",
|
||||
"importDatabasePgDesc": "Click to select and upload a .dump file to restore your PostgreSQL database. This replaces all current data."
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "Inbounds",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "Ocurrió un error al importar la base de datos",
|
||||
"readDatabaseError": "Ocurrió un error al leer la base de datos",
|
||||
"getDatabaseError": "Ocurrió un error al obtener la base de datos",
|
||||
"getConfigError": "Ocurrió un error al obtener el archivo de configuración"
|
||||
"getConfigError": "Ocurrió un error al obtener el archivo de configuración",
|
||||
"backupPostgresNote": "Este panel funciona con PostgreSQL. «Copia de seguridad» descarga un archivo pg_dump (.dump) y «Restaurar» lo vuelve a cargar con pg_restore. El servidor necesita tener instaladas las herramientas cliente de PostgreSQL (pg_dump y pg_restore).",
|
||||
"exportDatabasePgDesc": "Haz clic para descargar un volcado de PostgreSQL (.dump) de tu base de datos actual en tu dispositivo.",
|
||||
"importDatabasePgDesc": "Haz clic para seleccionar y subir un archivo .dump y restaurar tu base de datos PostgreSQL. Esto reemplaza todos los datos actuales."
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "Entradas",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "خطا در وارد کردن پایگاه داده",
|
||||
"readDatabaseError": "خطا در خواندن پایگاه داده",
|
||||
"getDatabaseError": "خطا در دریافت پایگاه داده",
|
||||
"getConfigError": "خطا در دریافت فایل پیکربندی"
|
||||
"getConfigError": "خطا در دریافت فایل پیکربندی",
|
||||
"backupPostgresNote": "این پنل روی PostgreSQL اجرا میشود. «پشتیبانگیری» یک آرشیو pg_dump (.dump) دانلود میکند و «بازیابی» آن را با pg_restore بازمیگرداند. سرور باید ابزارهای کلاینت PostgreSQL (pg_dump و pg_restore) را نصب داشته باشد.",
|
||||
"exportDatabasePgDesc": "برای دانلود یک دامپ PostgreSQL (.dump) از پایگاه داده فعلی روی دستگاهتان کلیک کنید.",
|
||||
"importDatabasePgDesc": "برای انتخاب و بارگذاری یک فایل .dump جهت بازیابی پایگاه داده PostgreSQL کلیک کنید. این کار همه دادههای فعلی را جایگزین میکند."
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "ورودیها",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "Terjadi kesalahan saat mengimpor database",
|
||||
"readDatabaseError": "Terjadi kesalahan saat membaca database",
|
||||
"getDatabaseError": "Terjadi kesalahan saat mengambil database",
|
||||
"getConfigError": "Terjadi kesalahan saat mengambil file konfigurasi"
|
||||
"getConfigError": "Terjadi kesalahan saat mengambil file konfigurasi",
|
||||
"backupPostgresNote": "Panel ini berjalan di PostgreSQL. «Cadangkan» mengunduh arsip pg_dump (.dump) dan «Pulihkan» memuatnya kembali dengan pg_restore. Server memerlukan alat klien PostgreSQL (pg_dump dan pg_restore) terpasang.",
|
||||
"exportDatabasePgDesc": "Klik untuk mengunduh dump PostgreSQL (.dump) dari basis data Anda saat ini ke perangkat Anda.",
|
||||
"importDatabasePgDesc": "Klik untuk memilih dan mengunggah berkas .dump guna memulihkan basis data PostgreSQL Anda. Ini menggantikan semua data saat ini."
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "Inbound",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "データベースのインポート中にエラーが発生しました",
|
||||
"readDatabaseError": "データベースの読み取り中にエラーが発生しました",
|
||||
"getDatabaseError": "データベースの取得中にエラーが発生しました",
|
||||
"getConfigError": "設定ファイルの取得中にエラーが発生しました"
|
||||
"getConfigError": "設定ファイルの取得中にエラーが発生しました",
|
||||
"backupPostgresNote": "このパネルは PostgreSQL で動作しています。「バックアップ」は pg_dump アーカイブ (.dump) をダウンロードし、「復元」は pg_restore で読み込み直します。サーバーに PostgreSQL クライアントツール (pg_dump と pg_restore) がインストールされている必要があります。",
|
||||
"exportDatabasePgDesc": "現在のデータベースの PostgreSQL ダンプ (.dump) を端末にダウンロードするにはクリックしてください。",
|
||||
"importDatabasePgDesc": "PostgreSQL データベースを復元するために .dump ファイルを選択してアップロードするにはクリックしてください。現在のすべてのデータが置き換えられます。"
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "インバウンド",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "Ocorreu um erro ao importar o banco de dados",
|
||||
"readDatabaseError": "Ocorreu um erro ao ler o banco de dados",
|
||||
"getDatabaseError": "Ocorreu um erro ao recuperar o banco de dados",
|
||||
"getConfigError": "Ocorreu um erro ao recuperar o arquivo de configuração"
|
||||
"getConfigError": "Ocorreu um erro ao recuperar o arquivo de configuração",
|
||||
"backupPostgresNote": "Este painel é executado em PostgreSQL. «Backup» baixa um arquivo pg_dump (.dump) e «Restaurar» o recarrega com pg_restore. O servidor precisa ter as ferramentas cliente do PostgreSQL (pg_dump e pg_restore) instaladas.",
|
||||
"exportDatabasePgDesc": "Clique para baixar um dump do PostgreSQL (.dump) do seu banco de dados atual para o seu dispositivo.",
|
||||
"importDatabasePgDesc": "Clique para selecionar e enviar um arquivo .dump para restaurar seu banco de dados PostgreSQL. Isso substitui todos os dados atuais."
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "Entradas",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "Произошла ошибка при импорте базы данных",
|
||||
"readDatabaseError": "Произошла ошибка при чтении базы данных",
|
||||
"getDatabaseError": "Произошла ошибка при получении базы данных",
|
||||
"getConfigError": "Произошла ошибка при получении конфигурационного файла"
|
||||
"getConfigError": "Произошла ошибка при получении конфигурационного файла",
|
||||
"backupPostgresNote": "Эта панель работает на PostgreSQL. «Резервная копия» скачивает архив pg_dump (.dump), а «Восстановление» загружает его обратно через pg_restore. На сервере должны быть установлены клиентские инструменты PostgreSQL (pg_dump и pg_restore).",
|
||||
"exportDatabasePgDesc": "Нажмите, чтобы скачать дамп PostgreSQL (.dump) текущей базы данных на ваше устройство.",
|
||||
"importDatabasePgDesc": "Нажмите, чтобы выбрать и загрузить файл .dump для восстановления базы данных PostgreSQL. Это заменит все текущие данные."
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "Входящие",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "Veritabanı içe aktarılırken bir hata oluştu",
|
||||
"readDatabaseError": "Veritabanı okunurken bir hata oluştu",
|
||||
"getDatabaseError": "Veritabanı alınırken bir hata oluştu",
|
||||
"getConfigError": "Yapılandırma dosyası alınırken bir hata oluştu"
|
||||
"getConfigError": "Yapılandırma dosyası alınırken bir hata oluştu",
|
||||
"backupPostgresNote": "Bu panel PostgreSQL üzerinde çalışıyor. «Yedekle» bir pg_dump arşivi (.dump) indirir, «Geri Yükle» ise onu pg_restore ile geri yükler. Sunucuda PostgreSQL istemci araçlarının (pg_dump ve pg_restore) kurulu olması gerekir.",
|
||||
"exportDatabasePgDesc": "Mevcut veritabanınızın PostgreSQL dökümünü (.dump) cihazınıza indirmek için tıklayın.",
|
||||
"importDatabasePgDesc": "PostgreSQL veritabanınızı geri yüklemek için bir .dump dosyası seçip yüklemek üzere tıklayın. Bu, tüm mevcut verilerin yerini alır."
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "Gelenler",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "Виникла помилка під час імпорту бази даних",
|
||||
"readDatabaseError": "Виникла помилка під час читання бази даних",
|
||||
"getDatabaseError": "Виникла помилка під час отримання бази даних",
|
||||
"getConfigError": "Виникла помилка під час отримання файлу конфігурації"
|
||||
"getConfigError": "Виникла помилка під час отримання файлу конфігурації",
|
||||
"backupPostgresNote": "Ця панель працює на PostgreSQL. «Резервна копія» завантажує архів pg_dump (.dump), а «Відновлення» завантажує його назад через pg_restore. На сервері мають бути встановлені клієнтські інструменти PostgreSQL (pg_dump і pg_restore).",
|
||||
"exportDatabasePgDesc": "Натисніть, щоб завантажити дамп PostgreSQL (.dump) вашої поточної бази даних на ваш пристрій.",
|
||||
"importDatabasePgDesc": "Натисніть, щоб вибрати та завантажити файл .dump для відновлення бази даних PostgreSQL. Це замінить усі поточні дані."
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "Вхідні",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "Lỗi xảy ra khi nhập cơ sở dữ liệu",
|
||||
"readDatabaseError": "Lỗi xảy ra khi đọc cơ sở dữ liệu",
|
||||
"getDatabaseError": "Lỗi xảy ra khi truy xuất cơ sở dữ liệu",
|
||||
"getConfigError": "Lỗi xảy ra khi truy xuất tệp cấu hình"
|
||||
"getConfigError": "Lỗi xảy ra khi truy xuất tệp cấu hình",
|
||||
"backupPostgresNote": "Bảng điều khiển này chạy trên PostgreSQL. «Sao lưu» tải xuống một tệp lưu trữ pg_dump (.dump) và «Khôi phục» nạp lại bằng pg_restore. Máy chủ cần cài đặt các công cụ máy khách PostgreSQL (pg_dump và pg_restore).",
|
||||
"exportDatabasePgDesc": "Nhấn để tải xuống bản kết xuất PostgreSQL (.dump) của cơ sở dữ liệu hiện tại về thiết bị của bạn.",
|
||||
"importDatabasePgDesc": "Nhấn để chọn và tải lên một tệp .dump nhằm khôi phục cơ sở dữ liệu PostgreSQL của bạn. Thao tác này sẽ thay thế toàn bộ dữ liệu hiện tại."
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "Inbound",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "导入数据库时出错",
|
||||
"readDatabaseError": "读取数据库时出错",
|
||||
"getDatabaseError": "检索数据库时出错",
|
||||
"getConfigError": "检索配置文件时出错"
|
||||
"getConfigError": "检索配置文件时出错",
|
||||
"backupPostgresNote": "此面板运行在 PostgreSQL 上。「备份」会下载一个 pg_dump 归档(.dump),「恢复」会通过 pg_restore 重新载入。服务器需要安装 PostgreSQL 客户端工具(pg_dump 和 pg_restore)。",
|
||||
"exportDatabasePgDesc": "点击将当前数据库的 PostgreSQL 转储(.dump)下载到您的设备。",
|
||||
"importDatabasePgDesc": "点击选择并上传 .dump 文件以恢复您的 PostgreSQL 数据库。此操作将替换所有当前数据。"
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "入站",
|
||||
|
||||
@@ -246,7 +246,10 @@
|
||||
"importDatabaseError": "匯入資料庫時發生錯誤",
|
||||
"readDatabaseError": "讀取資料庫時發生錯誤",
|
||||
"getDatabaseError": "檢索資料庫時發生錯誤",
|
||||
"getConfigError": "檢索設定檔時發生錯誤"
|
||||
"getConfigError": "檢索設定檔時發生錯誤",
|
||||
"backupPostgresNote": "此面板執行於 PostgreSQL 上。「備份」會下載一個 pg_dump 封存檔(.dump),「還原」會透過 pg_restore 重新載入。伺服器需要安裝 PostgreSQL 用戶端工具(pg_dump 與 pg_restore)。",
|
||||
"exportDatabasePgDesc": "點擊將目前資料庫的 PostgreSQL 傾印(.dump)下載到您的裝置。",
|
||||
"importDatabasePgDesc": "點擊選擇並上傳 .dump 檔案以還原您的 PostgreSQL 資料庫。此操作將取代所有目前的資料。"
|
||||
},
|
||||
"inbounds": {
|
||||
"title": "入站",
|
||||
|
||||
Reference in New Issue
Block a user