feat: Support backup for master (#9498)

This commit is contained in:
ssongliu 2025-07-16 17:53:04 +08:00 committed by GitHub
parent 700076f278
commit 7cbebc8236
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 159 additions and 19 deletions

View file

@ -6,6 +6,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/gin-gonic/gin"
)
@ -24,6 +25,12 @@ func Certificate() gin.HandlerFunc {
helper.InternalServer(c, fmt.Errorf("err certificate"))
return
}
masterProxyID := c.Request.Header.Get("Proxy-ID")
proxyID, err := cmd.RunDefaultWithStdoutBashC("cat /etc/1panel/.nodeProxyID")
if err == nil && len(proxyID) != 0 && proxyID != masterProxyID {
helper.InternalServer(c, fmt.Errorf("err proxy id"))
return
}
c.Next()
}
}

View file

@ -79,13 +79,15 @@ func (s sftpClient) Upload(src, target string) (bool, error) {
defer srcFile.Close()
targetDir, _ := path.Split(target)
if _, err = client.Stat(targetDir); err != nil {
if os.IsNotExist(err) {
if err = client.MkdirAll(targetDir); err != nil {
if len(targetDir) != 0 {
if _, err = client.Stat(targetDir); err != nil {
if os.IsNotExist(err) {
if err = client.MkdirAll(targetDir); err != nil {
return false, err
}
} else {
return false, err
}
} else {
return false, err
}
}
dstFile, err := client.Create(target)

View file

@ -827,7 +827,7 @@ func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules
if len(secret) != 0 {
commands = fmt.Sprintf("tar %s -zcf - %s | openssl enc -aes-256-cbc -salt -k '%s' -out %s", exStr, srcItem, secret, dst)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar -zcf %s %s %s", dst, exStr, srcItem)
global.LOG.Debug(commands)
@ -850,7 +850,7 @@ func (f FileOp) TarGzFilesWithCompressPro(list []string, dst, secret string) err
commands := ""
if len(secret) != 0 {
commands = fmt.Sprintf("tar -zcf - %s | openssl enc -aes-256-cbc -salt -k '%s' -out %s", strings.Join(filelist, " "), secret, dst)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar -zcf %s %s", dst, strings.Join(filelist, " "))
global.LOG.Debug(commands)
@ -869,7 +869,7 @@ func (f FileOp) TarGzExtractPro(src, dst string, secret string) error {
commands := ""
if len(secret) != 0 {
commands = fmt.Sprintf("openssl enc -d -aes-256-cbc -salt -k '%s' -in %s | tar -zxf - > /root/log", secret, src)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar zxvf %s", src)
global.LOG.Debug(commands)

View file

@ -47,7 +47,7 @@ func (t TarGzArchiver) Compress(sourcePaths []string, dstFile string, secret str
if len(secret) != 0 {
extraCmd := fmt.Sprintf("| openssl enc -aes-256-cbc -salt -k '%s' -out '%s'", secret, dstFile)
commands = fmt.Sprintf("tar -zcf - -C \"%s\" %s %s", aheadDir, itemDir, extraCmd)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar -zcf \"%s\" -C \"%s\" %s", dstFile, aheadDir, itemDir)
global.LOG.Debug(commands)

View file

@ -51,6 +51,7 @@ const (
TaskRsync = "TaskRsync"
TaskInstallCluster = "TaskInstallCluster"
TaskCreateCluster = "TaskCreateCluster"
TaskBackup = "TaskBackup"
)
const (

View file

@ -87,10 +87,12 @@ SubTaskFailed: "{{ .name }} failed: {{ .err }}"
TaskInstall: "Install"
TaskUpgrade: "Upgrade"
TaskSync: 'Synchronize'
TaskBackup: "Backup"
SuccessStatus: "{{ .name }} succeeded"
FailedStatus: "{{ .name }} failed {{ .err }}"
Start: "Start"
SubTask: "Subtask"
Skip: "Skip errors and continue..."
#script
ScriptLibrary: "Script Library"
@ -119,6 +121,11 @@ UploadUpgradeFile: "Distribute upgrade files"
RestartAfterUpgrade: "Start service after upgrade"
#add node
MasterData: "Master Node Data"
LoadSftpClient: "Load SFTP Client"
PackageMasterData: "Generate master node backup package"
UploadBackup: "Upload backup data"
MvBackup: "Move data to backup directory"
TaskAddNode: "Add node"
LoadNodeArch: "Get node architecture info"
LoadNodeArchDetail: "Detected master node architecture: {{ .local }}, child node architecture: {{ .node }}"

View file

@ -88,10 +88,12 @@ SubTaskFailed: "{{ .name }} 失敗: {{ .err }}"
TaskInstall: "インストール"
TaskUpgrade: "アップグレード"
TaskSync: '同期'
TaskBackup: "バックアップ"
SuccessStatus: "{{ .name }} 成功"
FailedStatus: "{{ .name }} 失敗 {{ .err }}"
Start: "開始"
SubTask: "サブタスク"
Skip: "エラーを無視して続行..."
#script
ScriptLibrary: "スクリプトライブラリ"
@ -120,6 +122,11 @@ UploadUpgradeFile: "アップグレードに必要なファイルを配布"
RestartAfterUpgrade: "アップグレード後にサービスを起動"
#add node
MasterData: "マスターノードデータ"
LoadSftpClient: "SFTPクライアントの取得"
PackageMasterData: "マスターノードのバックアップパッケージを生成"
UploadBackup: "バックアップデータをアップロード"
MvBackup: "データをバックアップディレクトリに移動"
TaskAddNode: "ノードを追加"
LoadNodeArch: "ノードアーキテクチャ情報を取得"
LoadNodeArchDetail: "検出されたマスターノードアーキテクチャ: {{ .local }}, 子ノードアーキテクチャ: {{ .node }}"

View file

@ -87,10 +87,12 @@ SubTaskFailed: "{{ .name }} 실패: {{ .err }}"
TaskInstall: "설치"
TaskUpgrade: "업그레이드"
TaskSync: '동기화'
TaskBackup: "백업"
SuccessStatus: "{{ .name }} 성공"
FailedStatus: "{{ .name }} 실패 {{ .err }}"
Start: "시작"
SubTask: "서브 작업"
Skip: "오류 무시하고 계속..."
#script
ScriptLibrary: "스크립트 라이브러리"
@ -119,6 +121,11 @@ UploadUpgradeFile: "업그레이드에 필요한 파일 전송"
RestartAfterUpgrade: "업그레이드 후 서비스 시작"
#add node
MasterData: "마스터 노드 데이터"
LoadSftpClient: "SFTP 클라이언트 가져오기"
PackageMasterData: "마스터 노드 백업 패키지 생성"
UploadBackup: "백업 데이터 업로드"
MvBackup: "데이터를 백업 디렉터리로 이동"
TaskAddNode: "노드 추가"
LoadNodeArch: "노드 아키텍처 정보 가져오기"
LoadNodeArchDetail: "감지된 마스터 노드 아키텍처: {{ .local }}, 자식 노드 아키텍처: {{ .node }}"

View file

@ -87,10 +87,12 @@ SubTaskFailed: "{{ .name }} gagal: {{ .err }}"
TaskInstall: "Pasang"
TaskUpgrade: "Kemas kini"
TaskSync: 'Selaraskan'
TaskBackup: "Sandaran"
SuccessStatus: "{{ .name }} berjaya"
FailedStatus: "{{ .name }} gagal {{ .err }}"
Start: "Mula"
SubTask: "Tugas Sub"
Skip: "Abaikan ralat dan teruskan..."
#script
ScriptLibrary: "Pustaka Skrip"
@ -119,6 +121,11 @@ UploadUpgradeFile: "Sebarkan fail naik taraf"
RestartAfterUpgrade: "Mulakan perkhidmatan selepas naik taraf"
#add node
MasterData: "Data Nod Master"
LoadSftpClient: "Dapatkan Klien SFTP"
PackageMasterData: "Hasilkan pakej sandaran nod master"
UploadBackup: "Muat naik data sandaran"
MvBackup: "Alihkan data ke direktori sandaran"
TaskAddNode: "Tambah nod"
LoadNodeArch: "Dapatkan maklumat seni bina nod"
LoadNodeArchDetail: "Mengesan seni bina nod induk: {{ .local }}, seni bina nod anak: {{ .node }}"

View file

@ -87,10 +87,12 @@ SubTaskFailed: "{{ .name }} falhou: {{ .err }}"
TaskInstall: "Instalar"
TaskUpgrade: "Atualizar"
TaskSync: 'Sincronizar'
TaskBackup: "Backup"
SuccessStatus: "{{ .name }} bem-sucedido"
FailedStatus: "{{ .name }} falhou {{ .err }}"
Start: "Iniciar"
SubTask: "Subtarefa"
Skip: "Ignorar erros e continuar..."
#script
ScriptLibrary: "Biblioteca de Scripts"
@ -119,6 +121,11 @@ UploadUpgradeFile: "Distribuir arquivos necessários para a atualização"
RestartAfterUpgrade: "Iniciar serviço após a atualização"
#add node
MasterData: "Dados do Nó Mestre"
LoadSftpClient: "Carregar Cliente SFTP"
PackageMasterData: "Gerar pacote de backup do nó mestre"
UploadBackup: "Carregar dados de backup"
MvBackup: "Mover dados para diretório de backup"
TaskAddNode: "Adicionar nó"
LoadNodeArch: "Obter informações de arquitetura do nó"
LoadNodeArchDetail: "Arquitetura do nó mestre detectada: {{ .local }}, arquitetura do nó filho: {{ .node }}"

View file

@ -87,10 +87,12 @@ SubTaskFailed: "{{ .name }} не удалось: {{ .err }}"
TaskInstall: "Установить"
TaskUpgrade: "Обновить"
TaskSync: 'Синхронизация'
TaskBackup: "Резервная копия"
SuccessStatus: "{{ .name }} успешно"
FailedStatus: "{{ .name }} не удалось {{ .err }}"
Start: "Начать"
SubTask: "Подзадача"
Skip: "Пропустить ошибки и продолжить..."
#script
ScriptLibrary: "Библиотека скриптов"
@ -119,6 +121,11 @@ UploadUpgradeFile: "Распределение файлов обновления
RestartAfterUpgrade: "Запуск службы после обновления"
#add node
MasterData: "Данные главного узла"
LoadSftpClient: "Загрузить SFTP-клиент"
PackageMasterData: "Создать архив резервной копии главного узла"
UploadBackup: "Загрузить резервные данные"
MvBackup: "Переместить данные в резервный каталог"
TaskAddNode: "Добавить узел"
LoadNodeArch: "Получить информацию об архитектуре узла"
LoadNodeArchDetail: "Обнаружена архитектура главного узла: {{ .local }}, архитектура дочернего узла: {{ .node }}"

View file

@ -86,10 +86,12 @@ SubTaskFailed: "{{ .name }} başarısız: {{ .err }}"
TaskInstall: "Kurulum"
TaskUpgrade: "Yükseltme"
TaskSync: 'Senkronize'
TaskBackup: "Yedekleme"
SuccessStatus: "{{ .name }} başarılı"
FailedStatus: "{{ .name }} başarısız {{ .err }}"
Start: "Başla"
SubTask: "Alt görev"
Skip: "Hataları atla ve devam et..."
#script
ScriptLibrary: "Betik Kütüphanesi"
@ -117,6 +119,11 @@ UploadUpgradeFile: "Yükseltme dosyalarını dağıt"
RestartAfterUpgrade: "Yükseltme sonrası servisi başlat"
#add node
MasterData: "Ana Düğüm Verisi"
LoadSftpClient: "SFTP İstemcisini Yükle"
PackageMasterData: "Ana düğüm yedek paketi oluştur"
UploadBackup: "Yedek verileri yükle"
MvBackup: "Verileri yedek dizinine taşı"
TaskAddNode: "Düğüm ekle"
LoadNodeArch: "Düğüm mimari bilgilerini al"
LoadNodeArchDetail: "Ana düğüm mimarisi tespit edildi: {{ .local }}, alt düğüm mimarisi: {{ .node }}"

View file

@ -87,10 +87,12 @@ SubTaskFailed: "{{ .name }} 失敗: {{ .err }}"
TaskInstall: "安裝"
TaskUpgrade: "升級"
TaskSync: '同步'
TaskBackup: "備份"
SuccessStatus: "{{ .name }} 成功"
FailedStatus: "{{ .name }} 失敗 {{ .err }}"
Start: "開始"
SubTask: "子任務"
Skip: "忽略錯誤並繼續..."
#script
ScriptLibrary: "腳本庫"
@ -120,6 +122,11 @@ UploadUpgradeFile: "發送升級所需文件"
RestartAfterUpgrade: "升級後啟動服務"
#add node
MasterData: "主節點數據"
LoadSftpClient: "獲取上傳客戶端"
PackageMasterData: "生成主節點備份壓縮包"
UploadBackup: "上傳備份數據"
MvBackup: "移動數據到備份目錄"
TaskAddNode: "新增節點"
LoadNodeArch: "取得節點架構資訊"
LoadNodeArchDetail: "檢測到主節點架構: {{ .local }},子節點架構: {{ .node }}"

View file

@ -87,10 +87,12 @@ SubTaskFailed: "{{ .name }} 失败: {{ .err }}"
TaskInstall: "安装"
TaskUpgrade: "升级"
TaskSync: "同步"
TaskBackup: "备份"
SuccessStatus: "{{ .name }} 成功"
FailedStatus: "{{ .name }} 失败 {{ .err }}"
Start: "开始"
SubTask: "子任务"
Skip: "忽略错误并继续..."
#script
ScriptLibrary: "脚本库"
@ -120,6 +122,9 @@ UploadUpgradeFile: "下发升级所需文件"
RestartAfterUpgrade: "升级后启动服务"
#add node
MasterData: "主节点数据"
LoadSftpClient: "获取上传客户端"
UploadBackup: "上传备份数据"
TaskAddNode: "添加节点"
LoadNodeArch: "获取节点架构信息"
LoadNodeArchDetail: "检测到主节点架构: {{ .local }},子节点架构: {{ .node }}"

View file

@ -83,13 +83,15 @@ func (s sftpClient) Upload(src, target string) (bool, error) {
targetFilePath := path.Join(s.bucket, target)
targetDir, _ := path.Split(targetFilePath)
if _, err = client.Stat(targetDir); err != nil {
if os.IsNotExist(err) {
if err = client.MkdirAll(targetDir); err != nil {
if len(targetDir) != 0 {
if _, err = client.Stat(targetDir); err != nil {
if os.IsNotExist(err) {
if err = client.MkdirAll(targetDir); err != nil {
return false, err
}
} else {
return false, err
}
} else {
return false, err
}
}
dstFile, err := client.Create(path.Join(s.bucket, target))

View file

@ -110,7 +110,7 @@ func HandleTar(sourceDir, targetDir, name, exclusionRules string, secret string)
if len(secret) != 0 {
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
commands = fmt.Sprintf("tar -zcf %s %s %s %s", " -"+excludeRules, path, extraCmd, targetDir+"/"+name)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar -zcf %s %s %s", targetDir+"/"+name, excludeRules, path)
global.LOG.Debug(commands)
@ -136,7 +136,7 @@ func HandleUnTar(sourceFile, targetDir string, secret string) error {
if len(secret) != 0 {
extraCmd := "openssl enc -d -aes-256-cbc -k '" + secret + "' -in " + sourceFile + " | "
commands = fmt.Sprintf("%s tar -zxvf - -C %s", extraCmd, targetDir+" > /dev/null 2>&1")
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar zxvfC %s %s", sourceFile, targetDir)
global.LOG.Debug(commands)

View file

@ -20,7 +20,8 @@ type ConnInfo struct {
}
type SSHClient struct {
Client *gossh.Client `json:"client"`
Client *gossh.Client `json:"client"`
SudoItem string `json:"sudoItem"`
}
func NewClient(c ConnInfo) (*SSHClient, error) {
@ -54,10 +55,16 @@ func NewClient(c ConnInfo) (*SSHClient, error) {
if nil != err {
return nil, err
}
return &SSHClient{Client: client}, nil
sshClient := &SSHClient{Client: client}
if _, err := sshClient.Run("sudo -n ls"); err == nil {
sshClient.SudoItem = "sudo"
}
return sshClient, nil
}
func (c *SSHClient) Run(shell string) (string, error) {
shell = c.SudoItem + " " + shell
shell = strings.ReplaceAll(shell, " && ", fmt.Sprintf(" && %s ", c.SudoItem))
session, err := c.Client.NewSession()
if err != nil {
return "", err
@ -84,6 +91,8 @@ func (c *SSHClient) IsRoot(user string) bool {
}
func (c *SSHClient) Runf(shell string, args ...interface{}) (string, error) {
shell = c.SudoItem + " " + shell
shell = strings.ReplaceAll(shell, " && ", fmt.Sprintf(" && %s ", c.SudoItem))
session, err := c.Client.NewSession()
if err != nil {
return "", err

View file

@ -36,7 +36,11 @@
@size-change="sizeChange"
@current-change="currentChange"
:size="mobile || paginationConfig.small ? 'small' : 'default'"
:layout="mobile ? 'total, prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
:layout="
mobile || paginationConfig.small
? 'total, prev, pager, next'
: 'total, sizes, prev, pager, next, jumper'
"
/>
</slot>
</div>

View file

@ -3371,6 +3371,12 @@ const message = {
},
node: {
master: 'Main Node',
masterBackup: 'Master Node Backup',
backupNode: 'Backup Node',
backupFrequency: 'Backup Frequency (hours)',
backupCopies: 'Backup Retention Copies',
masterBackupAlert:
'Master node backup is not currently configured. To ensure data security, please set up a backup node as soon as possible to facilitate manual switching to a new master node in case of failure.',
node: 'Node',
addr: 'Address',
nodeUnhealthy: 'Node status abnormal',

View file

@ -3240,6 +3240,12 @@ const message = {
},
node: {
master: '主ノード',
masterBackup: 'マスターノードバックアップ',
backupNode: 'バックアップノード',
backupFrequency: 'バックアップ頻度時間',
backupCopies: 'バックアップ保持数',
masterBackupAlert:
'現在マスターノードのバックアップが設定されていませんデータセキュリティを確保するため障害時に新しいマスターノードに手動で切り替えられるよう速やかにバックアップノードを設定してください',
node: 'ノード',
addr: 'アドレス',
nodeUnhealthy: 'ノード状態異常',

View file

@ -3182,6 +3182,12 @@ const message = {
},
node: {
master: ' 노드',
masterBackup: '마스터 노드 백업',
backupNode: '백업 노드',
backupFrequency: '백업 주기(시간)',
backupCopies: '백업 기록 보관 ',
masterBackupAlert:
'현재 마스터 노드 백업이 구성되지 않았습니다. 데이터 보안을 위해 장애 새로운 마스터 노드로 수동 전환이 가능하도록 가능한 빨리 백업 노드를 설정하십시오.',
node: '노드',
addr: '주소',
nodeUnhealthy: '노드 상태 이상',

View file

@ -3311,6 +3311,12 @@ const message = {
},
node: {
master: 'Nod Utama',
masterBackup: 'Sandaran Nod Master',
backupNode: 'Nod Sandaran',
backupFrequency: 'Kekerapan Sandaran (jam)',
backupCopies: 'Bilangan salinan sandaran yang disimpan',
masterBackupAlert:
'Sandaran nod master belum dikonfigurasikan. Untuk memastikan keselamatan data, sila sediakan nod sandaran secepat mungkin untuk memudahkan pertukaran manual ke nod master baru sekiranya berlaku kegagalan.',
node: 'Nod',
addr: 'Alamat',
nodeUnhealthy: 'Status nod tidak normal',

View file

@ -3319,6 +3319,12 @@ const message = {
},
node: {
master: ' Principal',
masterBackup: 'Backup do Mestre',
backupNode: ' de Backup',
backupFrequency: 'Frequência de Backup (horas)',
backupCopies: 'Número de cópias de backup a reter',
masterBackupAlert:
'O backup do mestre não está configurado atualmente. Para garantir a segurança dos dados, configure um de backup o mais rápido possível para facilitar a troca manual para um novo mestre em caso de falha.',
node: '',
addr: 'Endereço',
nodeUnhealthy: 'Estado do anormal',

View file

@ -3304,6 +3304,12 @@ const message = {
},
node: {
master: 'Главный узел',
masterBackup: 'Резервная копия главного узла',
backupNode: 'Резервный узел',
backupFrequency: 'Частота резервного копирования (часы)',
backupCopies: 'Количество сохраняемых резервных копий',
masterBackupAlert:
'Резервное копирование главного узла не настроено. Для обеспечения безопасности данных, пожалуйста, настройте резервный узел как можно скорее, чтобы можно было вручную переключиться на новый главный узел в случае сбоя.',
node: 'Узел',
addr: 'Адрес',
nodeUnhealthy: 'Некорректное состояние узла',

View file

@ -3406,6 +3406,12 @@ const message = {
},
node: {
master: 'Ana Düğüm',
masterBackup: 'Ana Düğüm Yedekleme',
backupNode: 'Yedek Düğüm',
backupFrequency: 'Yedekleme Sıklığı (saat)',
backupCopies: 'Saklanacak yedek kopya sayısı',
masterBackupAlert:
'Ana düğüm yedeklemesi şu anda yapılandırılmamış. Veri güvenliği için, lütfen arıza durumunda yeni bir ana düğüme manuel geçiş yapabilmek amacıyla en kısa sürede bir yedek düğüm ayarlayın.',
node: 'Düğüm',
addr: 'Adres',
nodeUnhealthy: 'Düğüm durumu anormal',

View file

@ -3138,6 +3138,12 @@ const message = {
},
node: {
master: '主節點',
masterBackup: '主節點備份',
backupNode: '備份節點',
backupFrequency: '備份頻率小時',
backupCopies: '備份記錄保留份數',
masterBackupAlert:
'當前未配置主節點備份為保障數據安全請盡快設置備份節點便於主節點故障時可人工切換新主節點',
node: '節點',
addr: '地址',
nodeUnhealthy: '節點狀態異常',

View file

@ -3108,6 +3108,12 @@ const message = {
},
node: {
master: '主节点',
masterBackup: '主节点备份',
backupNode: '备份节点',
backupFrequency: '备份频率小时',
backupCopies: '备份记录保留份数',
masterBackupAlert:
'当前未配置主节点备份为保障数据安全请尽快设置备份节点便于主节点故障时可人工切换新主节点',
node: '节点',
addr: '地址',
nodeUnhealthy: '节点状态异常',