Skip to content

Feature/增加配置导入导出功能#16

Closed
YewFence wants to merge 5 commits intomainfrom
feature/config-import-export
Closed

Feature/增加配置导入导出功能#16
YewFence wants to merge 5 commits intomainfrom
feature/config-import-export

Conversation

@YewFence
Copy link
Copy Markdown
Owner

@YewFence YewFence commented Mar 7, 2026

Summary by CodeRabbit

  • New Features

    • CLI: added config subcommands to list, export (age‑encrypted bundles with manifest), and import configurations; global --version and improved usage; interactive passphrase and overwrite prompts; timestamped default export names.
  • Bug Fixes

    • Better config path resolution and clearer error handling when loading .env or explicit config files.
  • Documentation

    • README: sudo cron examples, env var overrides, export/import recovery flow, and expanded cron/systemd guidance.
  • Tests

    • Added tests for config discovery, import planning, export/import edge cases, and atomic file writes.

YewFence and others added 2 commits March 7, 2026 18:42
实现配置文件的加密导出和导入功能,方便在新服务器上恢复备份环境:
- config list: 检测并列出所有可导出的配置文件(.env, rclone.conf, repository.config, .kopia-password)
- config export: 打包为 tar.gz 并使用 age scrypt 加密导出
- config import: 解密还原配置文件,支持覆盖确认和 --force 选项

重构 main.go 支持子命令分发,现有备份命令完全向后兼容。
提取 config.go 中的 resolveEnvPath() 供复用。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 新增 config list/export/import 子命令说明
- 更新 Go 版本项目结构,添加 config_bundle.go 模块
- 补充 sudo cron 运行时的配置文件路径指定方法
- 新增异地恢复引导章节,包含快速迁移和手动恢复两种方案
- 完善 Kopia 仓库连接、快照查看、数据恢复的操作步骤
@gitguardian
Copy link
Copy Markdown

gitguardian bot commented Mar 7, 2026

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 7, 2026

📝 Walkthrough

Walkthrough

Adds a new config CLI with list, export, and import to bundle and age‑encrypt YewResin configuration files; centralizes .env resolution and LoadConfig error handling; refactors main to dispatch subcommands and global flags; adds config bundle implementation, tests, dependencies, and README cron/export/import guidance.

Changes

Cohort / File(s) Summary
CLI Routing & Entrypoint
go/main.go
Adds global flag processing, subcommand dispatch for config, utilities to locate/forward --config, introduces runBackup() and routes config subcommands to the new handler.
Config Path Resolution
go/config.go
Adds resolveEnvPath() (program-dir fallback) and updates LoadConfig() to use it; refines error handling for missing vs. inaccessible .env files.
Config Export/Import Feature
go/config_bundle.go
New module implementing config discovery, manifested tar.gz bundling, age encryption/decryption, passphrase prompts, atomic writes/rollback, and CLI handlers for `config list
Tests for Config & CLI
go/config_bundle_test.go, go/main_test.go
Adds tests exercising detection, import-plan resolution, atomic writes, default output naming, conflict/rollback behavior, and argument forwarding/findCommand logic.
Dependencies / Tooling
go/go.mod
Bumps Go version to 1.24.0 and adds filippo.io/age, github.com/joho/godotenv, golang.org/x/term (plus indirect crypto/sys modules).
Documentation
README.md
Expands usage with config subcommands; adds sudo/cron examples, environment override notes (KOPIA_CONFIG_FILE, RCLONE_CONFIG), and detailed export/import recovery steps.

Sequence Diagrams

sequenceDiagram
    actor User
    participant CLI as CLI Handler
    participant FS as File System
    participant Archive as Tar/Gzip
    participant Age as Age Encryption

    User->>CLI: config export [--out file]
    CLI->>FS: detect config files (.env, rclone, kopia, ...)
    FS-->>CLI: files + metadata
    CLI->>User: prompt for passphrase
    User-->>CLI: passphrase
    CLI->>FS: read files
    FS-->>CLI: file contents
    CLI->>Archive: create tar.gz (manifest + files)
    Archive-->>CLI: tar.gz buffer
    CLI->>Age: encryptWithAge(passphrase, tar.gz)
    Age-->>CLI: encrypted bundle
    CLI->>FS: write output file (0600)
    FS-->>User: export complete
Loading
sequenceDiagram
    actor User
    participant CLI as CLI Handler
    participant FS as File System
    participant Age as Age Decryption
    participant Archive as Tar/Gzip

    User->>CLI: config import encrypted_file
    CLI->>FS: read encrypted file
    FS-->>CLI: ciphertext
    CLI->>User: prompt for passphrase
    User-->>CLI: passphrase
    CLI->>Age: decryptWithAge(passphrase, ciphertext)
    Age-->>CLI: decrypted tar.gz
    CLI->>Archive: extractTarGz(tar.gz)
    Archive-->>CLI: manifest + files
    CLI->>User: show conflicts and targets
    User-->>CLI: confirm overwrite
    CLI->>FS: write files atomically (0600)
    FS-->>User: import complete
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I nibble bytes and bundle bits so neat,
Age wraps secrets in a snug little sheet.
Export, import — safe through day and night,
Cron hums softly as I hop out of sight.
🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding configuration import/export functionality, which is reflected across multiple files (config_bundle.go, main.go, config.go, tests, documentation).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/config-import-export

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求的核心目标是增强 YewResin 工具的配置管理能力,通过引入配置文件的加密导出和导入功能,极大地简化了用户在不同服务器之间迁移或恢复备份配置的流程。这使得整个备份系统的配置更加便携和安全,尤其是在需要快速部署或灾难恢复的场景下,用户可以一键迁移关键配置,而无需手动复制和配置多个文件。

Highlights

  • 新增配置管理子命令: 引入了 config 子命令,支持配置文件的 list(列出)、export(导出)和 import(导入)功能,极大地简化了配置的迁移和管理。
  • 安全加密导出/导入: 实现了配置文件的自动探测、打包(tar.gz)和使用 age 工具进行密码加密/解密,确保配置在传输和存储过程中的安全性。
  • 文档更新与恢复指南: 更新了 README.md 文档,详细说明了新的配置导入导出功能,并提供了全面的异地恢复和服务器迁移指南。
  • 核心代码结构优化: 重构了 main.go,引入了子命令分发机制,将原有的备份逻辑封装到独立函数中,提升了代码的模块化和可扩展性。
  • 依赖更新: 升级了 Go 版本至 1.24.0,并添加了 filippo.io/agegolang.org/x/term 等新的 Go 模块依赖,以支持加密和终端交互功能。
Changelog
  • README.md
    • 新增了 config listconfig exportconfig import 命令的说明。
    • 更新了 go/ 目录结构描述,加入了 config_bundle.go
    • 添加了关于 sudo cron 运行的提示,以及详细的异地恢复引导,特别是 Go 版本的快速迁移(配置导出/导入)和手动恢复步骤。
  • go/config.go
    • .env 文件路径解析逻辑提取到新的 resolveEnvPath 函数中,提高了代码复用性。
    • LoadConfig 函数现在调用 resolveEnvPath 来确定配置文件路径。
  • go/config_bundle.go
    • 新增文件,实现了配置文件的探测(.env, rclone.conf, repository.config, repository.config.kopia-password)。
    • 提供了 createTarGzextractTarGz 函数用于创建和解压 tar.gz 归档。
    • 集成了 filippo.io/age 库,实现了配置归档的密码加密和解密功能。
    • 包含了命令行交互逻辑,用于处理 config list, config export, config import 子命令。
  • go/go.mod
    • Go 版本从 1.23 更新到 1.24.0。
    • 新增了 filippo.io/agegolang.org/x/term 模块依赖。
  • go/go.sum
    • 更新了模块依赖的校验和,以反映 go.mod 的变化。
  • go/main.go
    • 修改了 main 函数,增加了全局 --version 参数处理。
    • 实现了子命令分发机制,将 config 子命令的逻辑路由到 runConfigCmd
    • 将原有的备份主流程封装到 runBackup 函数中。
    • 更新了 flag.Usage 帮助信息,以包含新的 config 子命令说明。
Activity
  • 此拉取请求目前没有提供任何评论或审查活动。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This PR introduces a highly practical configuration import/export feature, significantly simplifying server migration and disaster recovery. The use of age for encryption is a secure and modern choice. However, a critical path traversal vulnerability has been identified within the import functionality. The tool currently trusts file paths provided in the archive's manifest without strict validation, which could allow an attacker to overwrite arbitrary files on the system. Additionally, there are opportunities to enhance error handling in go/config_bundle.go for os.UserHomeDir and godotenv.Read, and to improve the robustness of command-line argument parsing in go/main.go for subcommands and flags. To ensure the security and robustness of this valuable feature, it is crucial to implement strict path validation during the import process and address the identified error handling and parsing improvements.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b904a2347

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
go/config_bundle.go (1)

540-554: Consider atomic file writes for reliability.

The current implementation writes directly to the target path. If the write fails mid-way (e.g., disk full), the file could be left in a corrupted state. For config files, this is a low-risk edge case but worth considering.

♻️ Optional: Atomic write pattern
+		// 写入临时文件后重命名(原子操作)
+		tmpPath := f.OriginalPath + ".tmp"
+		if err := os.WriteFile(tmpPath, data, 0600); err != nil {
+			fmt.Fprintf(os.Stderr, "  失败: 写入 %s: %v\n", tmpPath, err)
+			continue
+		}
+		if err := os.Rename(tmpPath, f.OriginalPath); err != nil {
+			os.Remove(tmpPath)
+			fmt.Fprintf(os.Stderr, "  失败: 重命名 %s: %v\n", f.OriginalPath, err)
+			continue
+		}
-		// 写入文件(0600 权限,保护敏感信息)
-		if err := os.WriteFile(f.OriginalPath, data, 0600); err != nil {
-			fmt.Fprintf(os.Stderr, "  失败: 写入 %s: %v\n", f.OriginalPath, err)
-			continue
-		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@go/config_bundle.go` around lines 540 - 554, Replace the direct os.WriteFile
call with an atomic-write pattern: create a temp file in the same directory (use
os.CreateTemp with dir := filepath.Dir(f.OriginalPath)), write data to it, call
file.Sync() and file.Close(), set permissions to 0600 (os.Chmod if needed), then
os.Rename the temp file to f.OriginalPath; keep the os.MkdirAll(dir, 0755) step
and handle/ log errors similarly and increment restored only after a successful
rename.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 604-608: Add a language specifier to the fenced code block that
starts with the line "user@hostname:/opt/docker_file" in README.md; change the
opening triple-backticks to include a language (e.g., ```text) so the block
becomes ```text followed by the existing lines and the closing ``` unchanged to
satisfy markdownlint MD040.

---

Nitpick comments:
In `@go/config_bundle.go`:
- Around line 540-554: Replace the direct os.WriteFile call with an atomic-write
pattern: create a temp file in the same directory (use os.CreateTemp with dir :=
filepath.Dir(f.OriginalPath)), write data to it, call file.Sync() and
file.Close(), set permissions to 0600 (os.Chmod if needed), then os.Rename the
temp file to f.OriginalPath; keep the os.MkdirAll(dir, 0755) step and handle/
log errors similarly and increment restored only after a successful rename.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89e565fe-10b1-4f1b-bb49-4241f44c302a

📥 Commits

Reviewing files that changed from the base of the PR and between 720bbb7 and 9b904a2.

⛔ Files ignored due to path filters (1)
  • go/go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • README.md
  • go/config.go
  • go/config_bundle.go
  • go/go.mod
  • go/main.go

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@go/config_bundle.go`:
- Around line 562-573: The current use of os.Create on *outputFile will silently
truncate an existing file; change the logic before creation to detect an
existing path and avoid overwriting: use os.Stat or attempt os.OpenFile with
flags os.O_WRONLY|os.O_CREATE|os.O_EXCL when creating outFile (instead of
os.Create) so creation fails if the file already exists, and handle that error
by printing a clear message and exiting (or implement a --force overwrite flag
if desired); update the creation code that sets outFile and its error handling
to reference *outputFile and outFile accordingly.
- Around line 72-88: The function detectConfigFiles currently ignores a missing
env file even when the user explicitly passed a --config path; update the
missing-file branch to return an error when the resolved envPath came from an
explicit configPath. Concretely, in detectConfigFiles (and around
resolveEnvPath/envPath handling) change the os.Stat error handling so that if
statErr indicates file-not-exist and the original configPath argument is
non-empty (meaning the user explicitly requested that path), you return a clear
fmt.Errorf describing the missing file instead of falling back to defaults;
preserve the existing behavior only for the implicit/default (empty) configPath
case and keep the existing non-IsNotExist error handling unchanged.
- Around line 629-692: The import currently continues on missing archive entries
or write failures (in the loop using importPlan, fileContents, and
writeFileAtomic), which can leave configs partially restored; instead validate
all entries up front and abort if any fileContents[f.ArchiveName] is missing
(before creating directories), and if any writeFileAtomic fails, stop processing
immediately, remove any files already restored (track restored target paths) and
exit non-zero with a clear error message; update the code around
resolveImportPlan/importPlan handling and the restore loop (where
writeFileAtomic is called) to implement the pre-check and rollback-on-error
behavior and surface a single failure message rather than continuing.

In `@go/main.go`:
- Around line 47-55: prepareConfigCommandArgs currently prepends "--config"
before the subcommand, breaking runConfigCmd which expects args[0] to be the
subcommand; change prepareConfigCommandArgs so when a global --config is found
it preserves subcommandArgs[0] as the first element and inserts the "--config",
configValue after it (e.g. keep subcommandArgs[0], then "--config", configValue,
then the rest of subcommandArgs), and handle the case where subcommandArgs is
empty by returning the original slice; update go/main_test.go to expect
[]string{"list", "--config", "custom.env"} when forwarding the global config.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3c1c4799-b8be-4444-af13-a5c41ea216da

📥 Commits

Reviewing files that changed from the base of the PR and between 9b904a2 and 7fa5739.

📒 Files selected for processing (5)
  • README.md
  • go/config_bundle.go
  • go/config_bundle_test.go
  • go/main.go
  • go/main_test.go

YewFence added 2 commits March 7, 2026 19:36
避免显式错误配置被静默忽略,并防止重复导出覆盖已有归档。\n\n在导入中增加完整性校验和失败回滚,确保恢复过程不会留下半套配置。
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@go/config_bundle.go`:
- Around line 638-641: The loop over existingFiles calls os.Stat(f.OriginalPath)
but ignores the error causing a potential nil dereference when calling
info.Size(); update the loop in go/config_bundle.go to check the error returned
by os.Stat (for example: info, err := os.Stat(...)), and if err != nil handle it
safely (skip the file, log a warning with f.OriginalPath, or print a placeholder
size) before calling info.Size(); ensure the fix references existingFiles,
f.OriginalPath, os.Stat, info and humanSize so the display always guards against
a nil info.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 00dda5cf-8a21-44ba-b772-7d1b77fb53ef

📥 Commits

Reviewing files that changed from the base of the PR and between 66ff099 and 2fe8717.

📒 Files selected for processing (2)
  • go/config_bundle.go
  • go/config_bundle_test.go

Comment on lines +638 to +641
for _, f := range existingFiles {
info, _ := os.Stat(f.OriginalPath)
fmt.Printf(" %-35s %s (%s)\n", f.Description+":", f.OriginalPath, humanSize(info.Size()))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential nil pointer dereference if file disappears.

Between filtering files at lines 626-630 and displaying them here, the file could be deleted (TOCTOU). The info, _ ignores the error, and if os.Stat fails, info will be nil, causing a panic at info.Size().

🛡️ Suggested fix to handle the stat error
 	for _, f := range existingFiles {
-		info, _ := os.Stat(f.OriginalPath)
-		fmt.Printf("  %-35s %s (%s)\n", f.Description+":", f.OriginalPath, humanSize(info.Size()))
+		sizeStr := "unknown"
+		if info, err := os.Stat(f.OriginalPath); err == nil {
+			sizeStr = humanSize(info.Size())
+		}
+		fmt.Printf("  %-35s %s (%s)\n", f.Description+":", f.OriginalPath, sizeStr)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for _, f := range existingFiles {
info, _ := os.Stat(f.OriginalPath)
fmt.Printf(" %-35s %s (%s)\n", f.Description+":", f.OriginalPath, humanSize(info.Size()))
}
for _, f := range existingFiles {
sizeStr := "unknown"
if info, err := os.Stat(f.OriginalPath); err == nil {
sizeStr = humanSize(info.Size())
}
fmt.Printf(" %-35s %s (%s)\n", f.Description+":", f.OriginalPath, sizeStr)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@go/config_bundle.go` around lines 638 - 641, The loop over existingFiles
calls os.Stat(f.OriginalPath) but ignores the error causing a potential nil
dereference when calling info.Size(); update the loop in go/config_bundle.go to
check the error returned by os.Stat (for example: info, err := os.Stat(...)),
and if err != nil handle it safely (skip the file, log a warning with
f.OriginalPath, or print a placeholder size) before calling info.Size(); ensure
the fix references existingFiles, f.OriginalPath, os.Stat, info and humanSize so
the display always guards against a nil info.

@YewFence
Copy link
Copy Markdown
Owner Author

YewFence commented Mar 8, 2026

算了,感觉重复造轮子了,不如写一个sh和手动复制重新连接命令,更新一下README就好了

@YewFence YewFence closed this Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant