@shellus/way 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -40
- package/dist/cli.js +91 -67
- package/package.json +7 -3
- package/repositories.yaml.example +6 -6
- package/rules.yaml.example +4 -5
- package/README.local.md +0 -44
package/README.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
推荐在所有地方使用策略备份:开发环境、个人电脑、生产服务器。
|
|
6
6
|
|
|
7
|
+
## 版本说明
|
|
8
|
+
|
|
9
|
+
- **v0.4.0+**: TypeScript 版本,使用 systemd,凭证直接写在 YAML
|
|
10
|
+
- **v0.3.x**: TypeScript 版本,使用 crontab,支持 .env
|
|
11
|
+
- **v0.2.x**: Bash 版本
|
|
12
|
+
|
|
7
13
|
## 设计原理
|
|
8
14
|
|
|
9
15
|
### 1. 备份目的
|
|
@@ -40,6 +46,11 @@
|
|
|
40
46
|
|
|
41
47
|
假设本机被入侵,攻击者不应能通过本机凭证定位或删除备份数据。
|
|
42
48
|
|
|
49
|
+
**v0.4.0 安全增强**:
|
|
50
|
+
- 使用 systemd 替代 crontab(避免在 crontab 留下痕迹)
|
|
51
|
+
- 移除 .env 文件支持(避免被 `find .env` 发现)
|
|
52
|
+
- 凭证直接写在 `~/.way/repositories.yaml`(权限 600)
|
|
53
|
+
|
|
43
54
|
### 9. 多层冗余原则
|
|
44
55
|
|
|
45
56
|
单一备份链路存在单点故障风险。应在多个维度建立冗余:
|
|
@@ -55,52 +66,95 @@
|
|
|
55
66
|
## 依赖
|
|
56
67
|
|
|
57
68
|
- [restic](https://restic.net/) - 备份引擎
|
|
58
|
-
- Node.js >= 18
|
|
69
|
+
- Node.js >= 18
|
|
59
70
|
|
|
60
|
-
|
|
71
|
+
## 安装
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm install -g @shellus/way
|
|
75
|
+
```
|
|
61
76
|
|
|
62
77
|
## 快速开始
|
|
63
78
|
|
|
64
|
-
###
|
|
79
|
+
### 1. 初始化配置
|
|
65
80
|
|
|
66
81
|
```bash
|
|
67
|
-
npm install -g @shellus/way
|
|
68
|
-
|
|
69
|
-
# 初始化配置
|
|
70
82
|
mkdir -p ~/.way
|
|
71
83
|
cp $(npm root -g)/@shellus/way/repositories.yaml.example ~/.way/repositories.yaml
|
|
72
84
|
cp $(npm root -g)/@shellus/way/rules.yaml.example ~/.way/rules.yaml
|
|
73
85
|
```
|
|
74
86
|
|
|
75
|
-
|
|
87
|
+
编辑 `~/.way/repositories.yaml` 填入实际凭证,设置权限:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
chmod 600 ~/.way/repositories.yaml
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. 初始化 restic 仓库
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# 本地仓库
|
|
97
|
+
way init
|
|
98
|
+
|
|
99
|
+
# S3 仓库(指定其他仓库)
|
|
100
|
+
way --remote=s3 init
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3. 配置定时备份
|
|
76
104
|
|
|
77
105
|
```bash
|
|
78
|
-
|
|
79
|
-
|
|
106
|
+
way systemd install
|
|
107
|
+
way systemd status
|
|
80
108
|
```
|
|
81
109
|
|
|
82
|
-
###
|
|
110
|
+
### 4. 手动执行备份
|
|
83
111
|
|
|
84
112
|
```bash
|
|
113
|
+
way run # 备份所有项目
|
|
114
|
+
way run data # 只备份 data 项目
|
|
115
|
+
way snapshots # 查看快照
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 命令参考
|
|
85
119
|
|
|
86
|
-
|
|
120
|
+
```bash
|
|
121
|
+
# 备份命令
|
|
87
122
|
way run # 执行备份(读取 rules.yaml 的项目和排除规则)
|
|
88
123
|
way run data # 只备份 data 项目
|
|
89
124
|
way gc # 按 retention 策略清理旧快照
|
|
90
|
-
|
|
91
|
-
|
|
125
|
+
|
|
126
|
+
# systemd 定时任务
|
|
127
|
+
way systemd install # 安装 systemd 定时任务
|
|
128
|
+
way systemd show # 显示 systemd 配置
|
|
129
|
+
way systemd status # 查看定时任务状态
|
|
130
|
+
way systemd uninstall # 卸载定时任务
|
|
92
131
|
|
|
93
132
|
# 透传 restic(way 只设置环境变量)
|
|
94
133
|
way snapshots # → restic snapshots
|
|
95
134
|
way restore abc123 # → restic restore abc123
|
|
96
135
|
way check # → restic check
|
|
97
136
|
way stats # → restic stats
|
|
98
|
-
way prune # → restic prune(直接透传)
|
|
99
137
|
|
|
100
138
|
# 指定 repository(默认用 repositories.yaml 中的 default)
|
|
101
139
|
way --remote=oss snapshots
|
|
102
140
|
```
|
|
103
141
|
|
|
142
|
+
## 生命周期
|
|
143
|
+
|
|
144
|
+
```mermaid
|
|
145
|
+
graph LR
|
|
146
|
+
A[way run] --> B[读取配置]
|
|
147
|
+
B --> C[遍历项目]
|
|
148
|
+
C --> D[执行 restic backup]
|
|
149
|
+
D --> E[推送 Uptime Kuma]
|
|
150
|
+
|
|
151
|
+
F[systemd timer] --> G[定时触发]
|
|
152
|
+
G --> A
|
|
153
|
+
|
|
154
|
+
H[way gc] --> I[restic forget]
|
|
155
|
+
I --> J[restic prune]
|
|
156
|
+
```
|
|
157
|
+
|
|
104
158
|
---
|
|
105
159
|
|
|
106
160
|
## 配置说明
|
|
@@ -123,7 +177,7 @@ WAY_DIR=/path/to/config way snapshots
|
|
|
123
177
|
备份规则配置,参考 [`rules.yaml`](rules.yaml):
|
|
124
178
|
|
|
125
179
|
- **projects**: 备份项目、路径、专属排除规则
|
|
126
|
-
- **schedule**:
|
|
180
|
+
- **schedule**: 备份时间(systemd timer OnCalendar 格式)
|
|
127
181
|
- **retention**: 快照保留策略
|
|
128
182
|
- **global_excludes**: 全局排除规则
|
|
129
183
|
|
|
@@ -142,43 +196,45 @@ restic 使用 Go 的 filepath.Match 语法:
|
|
|
142
196
|
|
|
143
197
|
备份目的地配置,参考 [`repositories.yaml`](repositories.yaml)。
|
|
144
198
|
|
|
145
|
-
|
|
199
|
+
**v0.4.0 变更**:凭证直接写明文,不再支持 `${VAR}` 环境变量语法。
|
|
146
200
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
201
|
+
```yaml
|
|
202
|
+
repositories:
|
|
203
|
+
local:
|
|
204
|
+
type: local
|
|
205
|
+
path: /backup/repo
|
|
206
|
+
credentials:
|
|
207
|
+
password: your-password # 直接明文
|
|
153
208
|
```
|
|
154
209
|
|
|
155
|
-
|
|
210
|
+
建议设置文件权限:`chmod 600 ~/.way/repositories.yaml`
|
|
156
211
|
|
|
157
|
-
|
|
158
|
-
credentials:
|
|
159
|
-
password: ${RESTIC_PASSWORD}
|
|
160
|
-
access_key_id: ${AWS_ACCESS_KEY_ID}
|
|
161
|
-
```
|
|
212
|
+
## 配置备份
|
|
162
213
|
|
|
163
|
-
|
|
214
|
+
`~/.way/` 目录包含所有配置和凭证,建议整体备份到安全位置:
|
|
164
215
|
|
|
165
|
-
|
|
216
|
+
```bash
|
|
217
|
+
# 打包配置目录
|
|
218
|
+
tar czf way-config-$(date +%Y%m%d).tar.gz -C ~ .way
|
|
219
|
+
|
|
220
|
+
# 加密备份(推荐)
|
|
221
|
+
gpg -c way-config-$(date +%Y%m%d).tar.gz
|
|
222
|
+
```
|
|
166
223
|
|
|
167
|
-
|
|
224
|
+
**如果本机不可访问且遗忘了配置和凭证,所有备份数据将永久无法恢复。**
|
|
168
225
|
|
|
169
|
-
|
|
226
|
+
必须在本机之外保存 `~/.way/` 备份:
|
|
170
227
|
|
|
171
228
|
- 密码管理器(推荐)
|
|
172
|
-
-
|
|
173
|
-
- 离线存储(U
|
|
229
|
+
- 加密云存储
|
|
230
|
+
- 离线存储(U 盘、纸质打印关键凭证)
|
|
174
231
|
|
|
175
|
-
|
|
232
|
+
关键信息:
|
|
176
233
|
|
|
177
|
-
| 项目 |
|
|
234
|
+
| 项目 | 位置 |
|
|
178
235
|
|------|------|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
| 仓库地址 | `repositories.yaml` 中的 `url` |
|
|
236
|
+
| 备份规则 | `~/.way/rules.yaml` |
|
|
237
|
+
| 仓库配置和凭证 | `~/.way/repositories.yaml` |
|
|
182
238
|
|
|
183
239
|
## 数据恢复
|
|
184
240
|
|
|
@@ -222,4 +278,10 @@ way --remote=oss restore <snapshot-id> --target /tmp/restore
|
|
|
222
278
|
|
|
223
279
|
## 开发
|
|
224
280
|
|
|
225
|
-
参见 [
|
|
281
|
+
参见 [CONTRIBUTING.md](CONTRIBUTING.md) 了解:
|
|
282
|
+
- 项目结构和技术栈
|
|
283
|
+
- 开发环境搭建
|
|
284
|
+
- 变更规范和测试要求
|
|
285
|
+
- 发布流程
|
|
286
|
+
|
|
287
|
+
内部开发参考:[CLAUDE.md](CLAUDE.md)
|
package/dist/cli.js
CHANGED
|
@@ -7,43 +7,14 @@ import { Command } from "commander";
|
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
9
|
import yaml from "js-yaml";
|
|
10
|
-
import dotenv from "dotenv";
|
|
11
|
-
|
|
12
|
-
// src/core/env.ts
|
|
13
|
-
function expandEnv(value) {
|
|
14
|
-
if (!value) return "";
|
|
15
|
-
const match = value.match(/^\$\{(.+)\}$/);
|
|
16
|
-
if (match) {
|
|
17
|
-
return process.env[match[1]] || "";
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// src/core/config.ts
|
|
23
10
|
function loadConfig(wayDir, remoteName) {
|
|
24
|
-
const envPath = path.join(wayDir, ".env");
|
|
25
|
-
if (fs.existsSync(envPath)) {
|
|
26
|
-
dotenv.config({ path: envPath });
|
|
27
|
-
}
|
|
28
11
|
const repoFile = path.join(wayDir, "repositories.yaml");
|
|
29
12
|
const repoConfig = yaml.load(fs.readFileSync(repoFile, "utf8"));
|
|
30
13
|
const repoName = remoteName === "default" ? repoConfig.default : remoteName;
|
|
31
14
|
const repository = repoConfig.repositories[repoName];
|
|
32
15
|
if (!repository) throw new Error(`Repository not found: ${repoName}`);
|
|
33
|
-
if (repository.credentials.password) {
|
|
34
|
-
repository.credentials.password = expandEnv(repository.credentials.password);
|
|
35
|
-
}
|
|
36
|
-
if (repository.credentials.access_key_id) {
|
|
37
|
-
repository.credentials.access_key_id = expandEnv(repository.credentials.access_key_id);
|
|
38
|
-
}
|
|
39
|
-
if (repository.credentials.secret_access_key) {
|
|
40
|
-
repository.credentials.secret_access_key = expandEnv(repository.credentials.secret_access_key);
|
|
41
|
-
}
|
|
42
16
|
const rulesFile = path.join(wayDir, "rules.yaml");
|
|
43
17
|
const rules = yaml.load(fs.readFileSync(rulesFile, "utf8"));
|
|
44
|
-
if (rules.uptime_kuma?.push_url) {
|
|
45
|
-
rules.uptime_kuma.push_url = expandEnv(rules.uptime_kuma.push_url);
|
|
46
|
-
}
|
|
47
18
|
return { repository, rules };
|
|
48
19
|
}
|
|
49
20
|
|
|
@@ -82,7 +53,16 @@ function buildS3Options(repo) {
|
|
|
82
53
|
return options;
|
|
83
54
|
}
|
|
84
55
|
async function execRestic(args, env, s3Options = []) {
|
|
85
|
-
|
|
56
|
+
try {
|
|
57
|
+
await execa("restic", [...s3Options, ...args], { env: { ...process.env, ...env }, stdio: "inherit" });
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error.code === "ENOENT") {
|
|
60
|
+
console.error("Error: restic not found. Please install restic first.");
|
|
61
|
+
console.error("Visit: https://restic.net/");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
86
66
|
}
|
|
87
67
|
|
|
88
68
|
// src/commands/run.ts
|
|
@@ -161,68 +141,112 @@ async function gc(options) {
|
|
|
161
141
|
await execRestic(args, env, s3Options);
|
|
162
142
|
}
|
|
163
143
|
|
|
164
|
-
// src/commands/
|
|
144
|
+
// src/commands/systemd.ts
|
|
165
145
|
import { execSync } from "child_process";
|
|
166
146
|
import fs2 from "fs";
|
|
167
|
-
|
|
147
|
+
import path2 from "path";
|
|
148
|
+
function cronToSystemd(cron) {
|
|
149
|
+
const parts = cron.trim().split(/\s+/);
|
|
150
|
+
if (parts.length !== 5) return "daily";
|
|
151
|
+
const [minute, hour, day, month, weekday] = parts;
|
|
152
|
+
if (day === "*" && month === "*" && weekday === "*") {
|
|
153
|
+
return `*-*-* ${hour.padStart(2, "0")}:${minute.padStart(2, "0")}:00`;
|
|
154
|
+
}
|
|
155
|
+
return "daily";
|
|
156
|
+
}
|
|
157
|
+
async function systemd(options) {
|
|
168
158
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
169
159
|
const config = loadConfig(wayDir, options.remote);
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
160
|
+
const serviceContent = `[Unit]
|
|
161
|
+
Description=Way Backup Service
|
|
162
|
+
After=network.target
|
|
163
|
+
|
|
164
|
+
[Service]
|
|
165
|
+
Type=oneshot
|
|
166
|
+
ExecStart=/usr/local/bin/way run
|
|
167
|
+
Environment="WAY_DIR=${wayDir}"
|
|
168
|
+
`;
|
|
169
|
+
const timerContent = `[Unit]
|
|
170
|
+
Description=Way Backup Timer
|
|
171
|
+
|
|
172
|
+
[Timer]
|
|
173
|
+
OnCalendar=${cronToSystemd(config.rules.schedule?.backup?.[0] || "daily")}
|
|
174
|
+
Persistent=true
|
|
175
|
+
|
|
176
|
+
[Install]
|
|
177
|
+
WantedBy=timers.target
|
|
178
|
+
`;
|
|
187
179
|
if (options.action === "show") {
|
|
188
|
-
console.log(
|
|
180
|
+
console.log("=== way-backup.service ===");
|
|
181
|
+
console.log(serviceContent);
|
|
182
|
+
console.log("\n=== way-backup.timer ===");
|
|
183
|
+
console.log(timerContent);
|
|
189
184
|
return;
|
|
190
185
|
}
|
|
186
|
+
const systemdDir = `${process.env.HOME}/.config/systemd/user`;
|
|
187
|
+
const servicePath = path2.join(systemdDir, "way-backup.service");
|
|
188
|
+
const timerPath = path2.join(systemdDir, "way-backup.timer");
|
|
191
189
|
if (options.action === "install") {
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
fs2.mkdirSync(systemdDir, { recursive: true });
|
|
191
|
+
fs2.writeFileSync(servicePath, serviceContent);
|
|
192
|
+
fs2.writeFileSync(timerPath, timerContent);
|
|
193
|
+
execSync("systemctl --user daemon-reload");
|
|
194
|
+
execSync("systemctl --user enable way-backup.timer");
|
|
195
|
+
execSync("systemctl --user start way-backup.timer");
|
|
196
|
+
console.log("Systemd timer installed and started");
|
|
197
|
+
execSync("systemctl --user status way-backup.timer", { stdio: "inherit" });
|
|
198
|
+
}
|
|
199
|
+
if (options.action === "uninstall") {
|
|
200
|
+
try {
|
|
201
|
+
execSync("systemctl --user stop way-backup.timer", { stdio: "ignore" });
|
|
202
|
+
} catch {
|
|
194
203
|
}
|
|
195
|
-
let existing = "";
|
|
196
204
|
try {
|
|
197
|
-
|
|
205
|
+
execSync("systemctl --user disable way-backup.timer", { stdio: "ignore" });
|
|
198
206
|
} catch {
|
|
199
207
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
execSync(
|
|
208
|
+
if (fs2.existsSync(servicePath)) fs2.unlinkSync(servicePath);
|
|
209
|
+
if (fs2.existsSync(timerPath)) fs2.unlinkSync(timerPath);
|
|
210
|
+
execSync("systemctl --user daemon-reload");
|
|
211
|
+
console.log("Systemd timer uninstalled");
|
|
212
|
+
}
|
|
213
|
+
if (options.action === "status") {
|
|
214
|
+
execSync("systemctl --user status way-backup.timer", { stdio: "inherit" });
|
|
207
215
|
}
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
// src/cli.ts
|
|
211
219
|
var program = new Command();
|
|
212
|
-
program.name("way").version("0.
|
|
213
|
-
|
|
220
|
+
program.name("way").version("0.4.0").description("\u7B56\u7565\u5907\u4EFD\u5DE5\u5177 - \u57FA\u4E8E restic \u7684\u7B56\u7565\u5C01\u88C5").option("--remote <name>", "\u6307\u5B9A\u4ED3\u5E93", "default").addHelpText("after", `
|
|
221
|
+
\u793A\u4F8B:
|
|
222
|
+
$ way run \u6267\u884C\u6240\u6709\u9879\u76EE\u5907\u4EFD
|
|
223
|
+
$ way run data \u53EA\u5907\u4EFD data \u9879\u76EE
|
|
224
|
+
$ way run --dry-run \u6A21\u62DF\u5907\u4EFD\uFF08\u4E0D\u5B9E\u9645\u5199\u5165\uFF09
|
|
225
|
+
$ way gc \u6E05\u7406\u65E7\u5FEB\u7167
|
|
226
|
+
$ way systemd install \u5B89\u88C5\u5B9A\u65F6\u4EFB\u52A1
|
|
227
|
+
$ way snapshots \u67E5\u770B\u5FEB\u7167\u5217\u8868
|
|
228
|
+
$ way --remote=s3 snapshots \u4F7F\u7528 s3 \u4ED3\u5E93
|
|
229
|
+
|
|
230
|
+
\u6587\u6863: https://github.com/shellus/way
|
|
231
|
+
`);
|
|
232
|
+
program.command("run [project]").description("\u6267\u884C\u5907\u4EFD").allowUnknownOption().allowExcessArguments().action(async (project, options, command) => {
|
|
214
233
|
const remote = command.parent.opts().remote;
|
|
215
|
-
const
|
|
216
|
-
|
|
234
|
+
const allArgs = command.parent.args.slice(1);
|
|
235
|
+
if (project?.startsWith("--")) {
|
|
236
|
+
await run({ remote, project: void 0, extraArgs: allArgs });
|
|
237
|
+
} else {
|
|
238
|
+
const extraArgs = project ? allArgs.slice(1) : allArgs;
|
|
239
|
+
await run({ remote, project, extraArgs });
|
|
240
|
+
}
|
|
217
241
|
});
|
|
218
242
|
program.command("gc").description("\u6E05\u7406\u65E7\u5FEB\u7167").allowUnknownOption().allowExcessArguments().action(async function() {
|
|
219
243
|
const remote = this.parent.opts().remote;
|
|
220
244
|
const extraArgs = this.parent.args.slice(1);
|
|
221
245
|
await gc({ remote, extraArgs });
|
|
222
246
|
});
|
|
223
|
-
program.command("
|
|
247
|
+
program.command("systemd <action>").description("\u7BA1\u7406 systemd \u5B9A\u65F6\u4EFB\u52A1 (show|install|uninstall|status)").action(async (action, options, command) => {
|
|
224
248
|
const remote = command.parent.opts().remote;
|
|
225
|
-
await
|
|
249
|
+
await systemd({ remote, action });
|
|
226
250
|
});
|
|
227
251
|
program.command("env").description("\u663E\u793A\u73AF\u5883\u53D8\u91CF").action(() => {
|
|
228
252
|
const env = Object.entries(process.env).sort(([a], [b]) => a.localeCompare(b));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shellus/way",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "将备份作为持续运营的项目,而非一次性任务。基于 restic 的策略封装。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,12 @@
|
|
|
22
22
|
"type": "git",
|
|
23
23
|
"url": "git+https://github.com/shellus/way.git"
|
|
24
24
|
},
|
|
25
|
-
"keywords": [
|
|
25
|
+
"keywords": [
|
|
26
|
+
"backup",
|
|
27
|
+
"restic",
|
|
28
|
+
"snapshot",
|
|
29
|
+
"systemd"
|
|
30
|
+
],
|
|
26
31
|
"author": "shellus",
|
|
27
32
|
"license": "MIT",
|
|
28
33
|
"bugs": {
|
|
@@ -37,7 +42,6 @@
|
|
|
37
42
|
},
|
|
38
43
|
"dependencies": {
|
|
39
44
|
"commander": "^14.0.3",
|
|
40
|
-
"dotenv": "^17.3.1",
|
|
41
45
|
"execa": "^9.6.1",
|
|
42
46
|
"js-yaml": "^4.1.1"
|
|
43
47
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# 备份目的地配置 (restic repositories)
|
|
2
|
-
#
|
|
2
|
+
# 凭证直接填写明文(不再支持环境变量)
|
|
3
3
|
# 复制为 repositories.yaml 并填入实际值
|
|
4
4
|
|
|
5
5
|
default: local
|
|
@@ -9,7 +9,7 @@ repositories:
|
|
|
9
9
|
type: local
|
|
10
10
|
path: /tmp/way-backup
|
|
11
11
|
credentials:
|
|
12
|
-
password:
|
|
12
|
+
password: test123
|
|
13
13
|
|
|
14
14
|
s3:
|
|
15
15
|
type: s3
|
|
@@ -18,13 +18,13 @@ repositories:
|
|
|
18
18
|
options:
|
|
19
19
|
bucket_lookup: dns
|
|
20
20
|
credentials:
|
|
21
|
-
password:
|
|
22
|
-
access_key_id:
|
|
23
|
-
secret_access_key:
|
|
21
|
+
password: your-restic-password
|
|
22
|
+
access_key_id: your-access-key
|
|
23
|
+
secret_access_key: your-secret-key
|
|
24
24
|
|
|
25
25
|
sftp:
|
|
26
26
|
type: sftp
|
|
27
27
|
host: backup.example.com
|
|
28
28
|
path: /backup/myhost
|
|
29
29
|
credentials:
|
|
30
|
-
password:
|
|
30
|
+
password: your-restic-password
|
package/rules.yaml.example
CHANGED
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
# Uptime Kuma 通知(备份完成后推送状态)
|
|
6
6
|
uptime_kuma:
|
|
7
|
-
push_url: "
|
|
7
|
+
push_url: "https://uptime.example.com/api/push/xxxxx"
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
# 备份时间(cron 格式,会自动转换为 systemd OnCalendar)
|
|
10
10
|
schedule:
|
|
11
11
|
backup:
|
|
12
|
-
- "0 12 * * *" # 每天中午
|
|
13
|
-
|
|
14
|
-
prune: "0 4 * * 0" # 每周日凌晨4点清理旧快照
|
|
12
|
+
- "0 12 * * *" # 每天中午 12:00
|
|
13
|
+
prune: "0 4 * * 0" # 每周日凌晨 4:00 清理
|
|
15
14
|
check: "" # 完整性检查,留空不执行
|
|
16
15
|
|
|
17
16
|
# 快照保留策略
|
package/README.local.md
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# 本机环境配置
|
|
2
|
-
|
|
3
|
-
## 存储后端
|
|
4
|
-
|
|
5
|
-
腾讯云 COS(成都区域):
|
|
6
|
-
- Endpoint: `cos.ap-chengdu.myqcloud.com`
|
|
7
|
-
- Bucket: `xa-backup-1251087920`
|
|
8
|
-
- 需要 `s3.bucket-lookup=dns` 选项
|
|
9
|
-
|
|
10
|
-
## 备份项目
|
|
11
|
-
|
|
12
|
-
| 项目 | 路径 | 说明 |
|
|
13
|
-
|------|------|------|
|
|
14
|
-
| data | /data | Docker 服务、项目源码、开发工具配置 |
|
|
15
|
-
| home | /root/.ssh, /root/.claude 等 | SSH 密钥、Claude 配置、Shell 配置 |
|
|
16
|
-
| system | /etc/fstab | 系统挂载配置 |
|
|
17
|
-
|
|
18
|
-
## 排除规则备忘
|
|
19
|
-
|
|
20
|
-
playwright-mcp 浏览器缓存目录名是 `browser-profile`(不是 `chrome-profile`)。
|
|
21
|
-
|
|
22
|
-
## 定时任务
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
1 12 * * * way run # 12:01 午休后
|
|
26
|
-
30 15 * * * way run # 15:30 下午
|
|
27
|
-
30 18 * * * way run # 18:30 下班前
|
|
28
|
-
0 4 * * 0 way gc # 每周日凌晨清理
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
安装:`way cron install`
|
|
32
|
-
|
|
33
|
-
## 验证命令
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
# 查看快照
|
|
37
|
-
way snapshots
|
|
38
|
-
|
|
39
|
-
# 检查排除规则
|
|
40
|
-
way ls <snapshot-id> | grep -E 'node_modules|Cache'
|
|
41
|
-
|
|
42
|
-
# 测试备份
|
|
43
|
-
way run --dry-run
|
|
44
|
-
```
|