@ongtrieuhau861457/runner-tailscale-sync 1.260202.11920

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 ADDED
@@ -0,0 +1,310 @@
1
+ # runner-tailscale-sync
2
+
3
+ Đồng bộ runner-data giữa các runner trên GitHub Actions, Azure Pipeline qua Tailscale network.
4
+
5
+ ## ✨ Tính năng
6
+
7
+ - 🔄 Tự động đồng bộ `.runner-data` giữa các runner
8
+ - 🌐 Sử dụng Tailscale để kết nối an toàn giữa runners
9
+ - 🛑 Tự động stop services trên runner cũ khi runner mới bắt đầu
10
+ - 📦 Push data lên git repository
11
+ - 🎯 Hỗ trợ cả CLI và Library
12
+ - 🪟 Cross-platform (Windows + Linux)
13
+ - 📊 Logging chi tiết với version tracking
14
+
15
+ ## 📦 Cài đặt
16
+
17
+ ```bash
18
+ npm install runner-tailscale-sync
19
+
20
+ # Hoặc global
21
+ npm install -g runner-tailscale-sync
22
+ ```
23
+
24
+ ## 🚀 Sử dụng
25
+
26
+ ### CLI
27
+
28
+ ```bash
29
+ # Full sync workflow
30
+ TAILSCALE_ENABLE=1 runner-sync
31
+
32
+ # Chỉ khởi tạo Tailscale
33
+ runner-sync init
34
+
35
+ # Chỉ push git
36
+ runner-sync push
37
+
38
+ # Xem status
39
+ runner-sync status
40
+
41
+ # Custom working directory
42
+ runner-sync --cwd /path/to/project
43
+
44
+ # Verbose mode
45
+ runner-sync -v
46
+ ```
47
+
48
+ ### Library
49
+
50
+ ```javascript
51
+ const runnerSync = require('runner-tailscale-sync');
52
+
53
+ // Full sync
54
+ await runnerSync.sync({
55
+ cwd: '/path/to/project',
56
+ verbose: true,
57
+ });
58
+
59
+ // Chỉ init
60
+ await runnerSync.init();
61
+
62
+ // Chỉ push git
63
+ await runnerSync.push();
64
+
65
+ // Xem status
66
+ await runnerSync.status();
67
+ ```
68
+
69
+ ### Advanced Usage - Sử dụng modules riêng lẻ
70
+
71
+ ```javascript
72
+ const {
73
+ Config,
74
+ Logger,
75
+ syncOrchestrator,
76
+ runnerDetector,
77
+ dataSync,
78
+ tailscale
79
+ } = require('runner-tailscale-sync');
80
+
81
+ // Tạo config
82
+ const config = new Config({ cwd: process.cwd() });
83
+ const logger = new Logger({ packageName: 'my-tool', version: '1.0.0' });
84
+
85
+ // Detect previous runner
86
+ const detection = await runnerDetector.detectPreviousRunner(config, logger);
87
+
88
+ // Pull data
89
+ if (detection.previousRunner) {
90
+ await dataSync.pullData(config, detection.previousRunner, logger);
91
+ }
92
+
93
+ // Hoặc orchestrate toàn bộ
94
+ await syncOrchestrator.orchestrate(config, logger);
95
+ ```
96
+
97
+ ## ⚙️ Cấu hình
98
+
99
+ ### Environment Variables
100
+
101
+ ```bash
102
+ # Tailscale (required nếu TAILSCALE_ENABLE=1)
103
+ TAILSCALE_CLIENT_ID=your_client_id
104
+ TAILSCALE_CLIENT_SECRET=your_client_secret
105
+ TAILSCALE_TAGS=tag:ci
106
+ TAILSCALE_ENABLE=1
107
+
108
+ # Services to stop on previous runner
109
+ SERVICES_TO_STOP=cloudflared,pocketbase,http-server
110
+
111
+ # Git
112
+ GIT_PUSH_ENABLED=1
113
+ GIT_BRANCH=main
114
+
115
+ # Working directory
116
+ TOOL_CWD=/path/to/project
117
+ ```
118
+
119
+ ### .env File
120
+
121
+ ```env
122
+ TAILSCALE_CLIENT_ID=tskey-client-xxxxx
123
+ TAILSCALE_CLIENT_SECRET=tskey-xxxxx
124
+ TAILSCALE_TAGS=tag:ci
125
+ TAILSCALE_ENABLE=1
126
+ SERVICES_TO_STOP=cloudflared,pocketbase
127
+ GIT_PUSH_ENABLED=1
128
+ GIT_BRANCH=main
129
+ ```
130
+
131
+ ## 📂 Cấu trúc dữ liệu
132
+
133
+ Tất cả dữ liệu được lưu trong `.runner-data/`:
134
+
135
+ ```
136
+ .runner-data/
137
+ ├── logs/ # Log files
138
+ ├── pid/ # PID files
139
+ ├── data-services/ # Service data
140
+ └── tmp/ # Temporary files
141
+ ```
142
+
143
+ ## 🔄 Quy trình hoạt động
144
+
145
+ 1. **Runner01** khởi động → Join Tailscale → Chạy 55 phút → Dữ liệu được lưu trong `.runner-data/`
146
+ 2. **Runner02** bắt đầu:
147
+ - Join Tailscale network
148
+ - Detect Runner01 (cùng tag, đang active)
149
+ - Pull `.runner-data/` từ Runner01
150
+ - Stop services trên Runner01 (cloudflared, pocketbase, etc.)
151
+ - Chạy services trên Runner02
152
+ - Push `.runner-data/` lên git repository
153
+ 3. **Runner01** → **Runner02** xoay vòng liên tục
154
+
155
+ ## 🎯 Use Cases
156
+
157
+ ### GitHub Actions
158
+
159
+ ```yaml
160
+ - name: Setup Tailscale Sync
161
+ env:
162
+ TAILSCALE_CLIENT_ID: ${{ secrets.TAILSCALE_CLIENT_ID }}
163
+ TAILSCALE_CLIENT_SECRET: ${{ secrets.TAILSCALE_CLIENT_SECRET }}
164
+ TAILSCALE_ENABLE: 1
165
+ run: |
166
+ npm install -g runner-tailscale-sync
167
+ runner-sync
168
+ ```
169
+
170
+ ### Azure DevOps
171
+
172
+ ```yaml
173
+ - script: |
174
+ npm install -g runner-tailscale-sync
175
+ runner-sync
176
+ env:
177
+ TAILSCALE_CLIENT_ID: $(TAILSCALE_CLIENT_ID)
178
+ TAILSCALE_CLIENT_SECRET: $(TAILSCALE_CLIENT_SECRET)
179
+ TAILSCALE_ENABLE: 1
180
+ displayName: 'Sync Runner Data'
181
+ ```
182
+
183
+ ### Self-hosted Runner
184
+
185
+ ```bash
186
+ # Install
187
+ npm install -g runner-tailscale-sync
188
+
189
+ # Add to runner startup script
190
+ export TAILSCALE_ENABLE=1
191
+ export TAILSCALE_CLIENT_ID=your_client_id
192
+ export TAILSCALE_CLIENT_SECRET=your_secret
193
+
194
+ runner-sync
195
+ ```
196
+
197
+ ## 🛠️ Development
198
+
199
+ ### Scripts
200
+
201
+ ```bash
202
+ # Generate new version (VN timezone: 1.yyMMdd.1HHmm)
203
+ npm run version
204
+
205
+ # Build validation
206
+ npm run build
207
+
208
+ # Publish to npm
209
+ npm run publish
210
+
211
+ # Dry run publish
212
+ node scripts/publish.js --dry-run
213
+ ```
214
+
215
+ ### Testing Locally
216
+
217
+ ```bash
218
+ # Link globally
219
+ npm link
220
+
221
+ # Test CLI
222
+ runner-sync --help
223
+ runner-sync status
224
+
225
+ # Test as library
226
+ node -e "require('./src/index.js').status().then(console.log)"
227
+ ```
228
+
229
+ ## 📝 Version Format
230
+
231
+ Version theo giờ Việt Nam (UTC+7): `1.yyMMdd.1HHmm`
232
+
233
+ Ví dụ:
234
+ - Build lúc 15:30 ngày 02/02/2025 → `1.250202.11530`
235
+ - Build lúc 09:45 ngày 15/03/2025 → `1.250315.10945`
236
+
237
+ Đảm bảo semver compliance và tự động tăng theo thời gian.
238
+
239
+ ## 🔧 Yêu cầu hệ thống
240
+
241
+ - Node.js >= 20
242
+ - Git (cho tính năng push)
243
+ - Tailscale (sẽ tự động cài trên Linux)
244
+ - rsync hoặc scp (cho data sync)
245
+
246
+ ### Windows
247
+
248
+ Trên Windows, cần cài thêm:
249
+ - [Tailscale for Windows](https://tailscale.com/download/windows)
250
+ - Git for Windows (có sẵn ssh/scp)
251
+ - Hoặc cài rsync qua: `choco install rsync` hoặc WSL
252
+
253
+ Cấu hình đường dẫn trong `.env`:
254
+ ```env
255
+ SSH_PATH=C:\Program Files\Git\usr\bin\ssh.exe
256
+ RSYNC_PATH=C:\Program Files\rsync\rsync.exe
257
+ ```
258
+
259
+ ## 🐛 Troubleshooting
260
+
261
+ ### Tailscale không kết nối được
262
+
263
+ ```bash
264
+ # Kiểm tra Tailscale status
265
+ tailscale status
266
+
267
+ # Login manually
268
+ tailscale login
269
+
270
+ # Check logs
271
+ runner-sync status -v
272
+ ```
273
+
274
+ ### Sync thất bại
275
+
276
+ ```bash
277
+ # Kiểm tra SSH connection
278
+ ssh runner01-ip echo "OK"
279
+
280
+ # Test rsync
281
+ rsync -avz runner01-ip:.runner-data/ .runner-data/
282
+
283
+ # Verbose mode
284
+ runner-sync -v
285
+ ```
286
+
287
+ ### Git push bị conflict
288
+
289
+ ```bash
290
+ # Pull latest trước
291
+ cd /path/to/repo
292
+ git pull origin main
293
+
294
+ # Hoặc disable git push
295
+ export GIT_PUSH_ENABLED=0
296
+ runner-sync
297
+ ```
298
+
299
+ ## 📄 License
300
+
301
+ MIT
302
+
303
+ ## 🤝 Contributing
304
+
305
+ Pull requests are welcome!
306
+
307
+ ## 📧 Support
308
+
309
+ - Issues: [GitHub Issues](https://github.com/yourname/runner-tailscale-sync/issues)
310
+ - Email: your-email@example.com
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/runner-sync.js
4
+ * CLI entry point
5
+ */
6
+
7
+ const path = require("path");
8
+ const Config = require("../src/utils/config");
9
+ const Logger = require("../src/utils/logger");
10
+ const { parseArgs, printHelp } = require("../src/cli/parser");
11
+ const pkg = require("../package.json");
12
+
13
+ // Parse arguments
14
+ const { command, options } = parseArgs(process.argv);
15
+
16
+ // Handle help
17
+ if (options.help) {
18
+ printHelp();
19
+ process.exit(0);
20
+ }
21
+
22
+ // Handle version
23
+ if (command === "version") {
24
+ console.log(`${pkg.name} v${pkg.version}`);
25
+ process.exit(0);
26
+ }
27
+
28
+ // Create config
29
+ const config = new Config(options);
30
+
31
+ // Create logger
32
+ const logger = new Logger({
33
+ packageName: pkg.name,
34
+ version: pkg.version,
35
+ command,
36
+ verbose: options.verbose,
37
+ quiet: options.quiet,
38
+ });
39
+
40
+ // Print banner
41
+ logger.printBanner();
42
+
43
+ // Run command
44
+ (async () => {
45
+ try {
46
+ let commandModule;
47
+
48
+ switch (command) {
49
+ case "init":
50
+ commandModule = require("../src/cli/commands/init");
51
+ break;
52
+ case "sync":
53
+ commandModule = require("../src/cli/commands/sync");
54
+ break;
55
+ case "push":
56
+ commandModule = require("../src/cli/commands/push");
57
+ break;
58
+ case "status":
59
+ commandModule = require("../src/cli/commands/status");
60
+ break;
61
+ default:
62
+ logger.error(`Unknown command: ${command}`);
63
+ printHelp();
64
+ process.exit(1);
65
+ }
66
+
67
+ const result = await commandModule.run(config, logger);
68
+
69
+ if (result.success) {
70
+ process.exit(0);
71
+ } else {
72
+ process.exit(1);
73
+ }
74
+ } catch (err) {
75
+ logger.error(err.message);
76
+
77
+ if (options.verbose && err.stack) {
78
+ logger.debug(err.stack);
79
+ }
80
+
81
+ // Exit with appropriate code
82
+ const exitCode = err.exitCode || 1;
83
+ process.exit(exitCode);
84
+ }
85
+ })();
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@ongtrieuhau861457/runner-tailscale-sync",
3
+ "version": "1.260202.11920",
4
+ "description": "Đồng bộ runner-data giữa các runner trên GitHub Actions, Azure Pipeline qua Tailscale network",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "runner-sync": "./bin/runner-sync.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=20.0.0"
11
+ },
12
+ "keywords": [
13
+ "runner",
14
+ "sync",
15
+ "tailscale",
16
+ "github-actions",
17
+ "azure-devops",
18
+ "ci-cd"
19
+ ],
20
+ "author": "Your Name",
21
+ "license": "MIT",
22
+ "scripts": {
23
+ "version": "node scripts/version.js",
24
+ "build": "node scripts/build.js",
25
+ "publish": "node scripts/publish.js"
26
+ },
27
+ "dependencies": {},
28
+ "devDependencies": {},
29
+ "files": [
30
+ "bin/",
31
+ "src/",
32
+ "README.md"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/ongtrieuhau861457-hue/runner-tailscale-sync.git"
37
+ },
38
+ "publishConfig": {
39
+ "registry": "https://registry.npmjs.org/"
40
+ }
41
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * adapters/fs.js
3
+ * File system operations với atomic write, ensure dir
4
+ */
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+
9
+ /**
10
+ * Ensure directory exists
11
+ */
12
+ function ensureDir(dirPath) {
13
+ if (!fs.existsSync(dirPath)) {
14
+ fs.mkdirSync(dirPath, { recursive: true });
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Ensure multiple directories exist
20
+ */
21
+ function ensureDirs(dirPaths) {
22
+ dirPaths.forEach(ensureDir);
23
+ }
24
+
25
+ /**
26
+ * Read JSON file
27
+ */
28
+ function readJson(filePath) {
29
+ if (!fs.existsSync(filePath)) {
30
+ return null;
31
+ }
32
+
33
+ try {
34
+ const content = fs.readFileSync(filePath, "utf8");
35
+ return JSON.parse(content);
36
+ } catch (err) {
37
+ throw new Error(`Failed to read JSON from ${filePath}: ${err.message}`);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Write JSON file (atomic)
43
+ */
44
+ function writeJson(filePath, data) {
45
+ const content = JSON.stringify(data, null, 2);
46
+ const tmpPath = `${filePath}.tmp`;
47
+
48
+ try {
49
+ // Write to temp file first
50
+ fs.writeFileSync(tmpPath, content, "utf8");
51
+
52
+ // Rename to actual file (atomic on most filesystems)
53
+ fs.renameSync(tmpPath, filePath);
54
+ } catch (err) {
55
+ // Clean up temp file if exists
56
+ if (fs.existsSync(tmpPath)) {
57
+ fs.unlinkSync(tmpPath);
58
+ }
59
+ throw new Error(`Failed to write JSON to ${filePath}: ${err.message}`);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Write text file (atomic)
65
+ */
66
+ function writeFile(filePath, content) {
67
+ const tmpPath = `${filePath}.tmp`;
68
+
69
+ try {
70
+ fs.writeFileSync(tmpPath, content, "utf8");
71
+ fs.renameSync(tmpPath, filePath);
72
+ } catch (err) {
73
+ if (fs.existsSync(tmpPath)) {
74
+ fs.unlinkSync(tmpPath);
75
+ }
76
+ throw new Error(`Failed to write file ${filePath}: ${err.message}`);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Read text file
82
+ */
83
+ function readFile(filePath) {
84
+ if (!fs.existsSync(filePath)) {
85
+ return null;
86
+ }
87
+
88
+ try {
89
+ return fs.readFileSync(filePath, "utf8");
90
+ } catch (err) {
91
+ throw new Error(`Failed to read file ${filePath}: ${err.message}`);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Check if path exists
97
+ */
98
+ function exists(filePath) {
99
+ return fs.existsSync(filePath);
100
+ }
101
+
102
+ /**
103
+ * Delete file or directory recursively
104
+ */
105
+ function remove(targetPath) {
106
+ if (!fs.existsSync(targetPath)) return;
107
+
108
+ if (fs.statSync(targetPath).isDirectory()) {
109
+ fs.rmSync(targetPath, { recursive: true, force: true });
110
+ } else {
111
+ fs.unlinkSync(targetPath);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Get directory size (recursive)
117
+ */
118
+ function getDirSize(dirPath) {
119
+ let size = 0;
120
+
121
+ if (!fs.existsSync(dirPath)) return 0;
122
+
123
+ const files = fs.readdirSync(dirPath);
124
+ for (const file of files) {
125
+ const filePath = path.join(dirPath, file);
126
+ const stats = fs.statSync(filePath);
127
+
128
+ if (stats.isDirectory()) {
129
+ size += getDirSize(filePath);
130
+ } else {
131
+ size += stats.size;
132
+ }
133
+ }
134
+
135
+ return size;
136
+ }
137
+
138
+ /**
139
+ * Format bytes to human readable
140
+ */
141
+ function formatBytes(bytes) {
142
+ if (bytes === 0) return "0 B";
143
+ const k = 1024;
144
+ const sizes = ["B", "KB", "MB", "GB"];
145
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
146
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
147
+ }
148
+
149
+ module.exports = {
150
+ ensureDir,
151
+ ensureDirs,
152
+ readJson,
153
+ writeJson,
154
+ writeFile,
155
+ readFile,
156
+ exists,
157
+ remove,
158
+ getDirSize,
159
+ formatBytes,
160
+ };