-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
executable file
·225 lines (197 loc) · 7.89 KB
/
index.js
File metadata and controls
executable file
·225 lines (197 loc) · 7.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import { execSync, spawn } from 'child_process';
import readline from 'readline';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const RUNNERS_DIR = path.resolve('./');
const CACHE_DIR = path.resolve('./cache');
if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR);
}
function getNextRunnerNumber() {
let count = 1;
while (fs.existsSync(path.join(RUNNERS_DIR, `runner-${count}`))) {
count++;
}
return count;
}
function getRunnerDirectories() {
return fs.readdirSync(RUNNERS_DIR).filter(dir => dir.startsWith('runner-'));
}
function executeCommand(command, cwd) {
try {
execSync(command, { cwd, stdio: 'inherit' });
console.log(`✔ Command succeeded: ${command}`);
return true;
} catch (error) {
console.error(`✖ Command failed: ${command}`);
console.error(error.message);
return false;
}
}
async function startRunners() {
const dirs = getRunnerDirectories();
if (dirs.length === 0) {
console.log('Error: No runners found. Returning to main menu.');
return setImmediate(main);
}
dirs.forEach(dir => {
console.log(`Starting ${dir}...`);
spawn('./run.sh', { cwd: path.join(RUNNERS_DIR, dir), stdio: 'inherit' });
});
}
async function quitRunners() {
const dirs = getRunnerDirectories();
if (dirs.length === 0) {
console.log('Error: No runners found. Returning to main menu.');
return setImmediate(main);
}
executeCommand('pkill -f run.sh', RUNNERS_DIR);
}
function askQuestion(query) {
return new Promise(resolve => rl.question(query, answer => resolve(answer)));
}
function cleanupRunner(runnerDir) {
try {
fs.rmSync(runnerDir, { recursive: true, force: true });
console.log(`Cleaned up ${runnerDir}`);
} catch (err) {
console.error(`Failed to clean up ${runnerDir}: ${err.message}`);
}
}
async function addNewRunner() {
// Load defaults from runners.config.json if it exists
const configPath = path.resolve('./runners.config.json');
let configData = {};
const configExists = fs.existsSync(configPath);
if (configExists) {
try {
configData = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
} catch (err) {
console.error("Error parsing runners.config.json, using fallback defaults.");
}
}
const defaultVersion = configData.version || '2.322.0';
const defaultSha = configData.sha || '67d3b4dd6f1eec8ec43dda12c189cff68ec3ba1dfa054791cb446ddcfb39d2aa';
const defaultName = configData.runnerName || 'ManagedRunner';
const defaultToken = configData.token; // no fallback default
const defaultRepo = configData.repo; // no fallback default
const defaultPlatform = configData.platform || 'macOS';
const version = await askQuestion(`Enter version (${defaultVersion}): `) || defaultVersion;
const sha = await askQuestion(`Enter SHA (${defaultSha}): `) || defaultSha;
const tokenInput = await askQuestion(`Enter GitHub token${defaultToken ? ` (${defaultToken})` : ''}: `);
const token = tokenInput || defaultToken;
if (!token) {
console.log('Token is required');
return setImmediate(main);
}
const repoInput = await askQuestion(`Enter repo (owner/repo)${defaultRepo ? ` (${defaultRepo})` : ''}: `);
const repo = repoInput || defaultRepo;
if (!repo) {
console.log('Repo is required');
return setImmediate(main);
}
const runnerName = await askQuestion(`Enter runner name (${defaultName}): `) || defaultName;
// Ask for platform and architecture
const platformInput = await askQuestion(`Which platform are you running on? (macOS/Linux) (${defaultPlatform}): `);
const platform = platformInput || defaultPlatform;
let defaultArch;
if (platform.toLowerCase() === 'linux') {
defaultArch = configData.arch || 'x64';
} else {
// For macOS
defaultArch = configData.arch || 'ARM64';
}
let archPrompt;
if (platform.toLowerCase() === 'linux') {
archPrompt = `Select architecture (x64/ARM/ARM64) (${defaultArch}): `;
} else {
archPrompt = `Select architecture (x64/ARM64) (${defaultArch}): `;
}
const archInput = await askQuestion(archPrompt);
const arch = archInput || defaultArch;
const runnerNumber = getNextRunnerNumber();
const runnerDir = path.join(RUNNERS_DIR, `runner-${runnerNumber}`);
fs.mkdirSync(runnerDir);
// Construct filename based on platform and architecture.
// For macOS, use "osx" instead of "macos"
const platformPart = platform.toLowerCase() === 'macos' ? 'osx' : platform.toLowerCase();
const filename = `actions-runner-${platformPart}-${arch.toLowerCase()}-${version}.tar.gz`;
const cachedFile = path.join(CACHE_DIR, filename);
const url = `https://github.com/actions/runner/releases/download/v${version}/${filename}`;
const checksumCmd = `echo "${sha} ${filename}" | shasum -a 256 -c`;
console.log(`Setting up runner-${runnerNumber}...`);
if (!fs.existsSync(cachedFile)) {
console.log(`Downloading ${filename}...`);
if (!executeCommand(`curl -o ${cachedFile} -L ${url}`, CACHE_DIR)) {
cleanupRunner(runnerDir);
return setImmediate(main);
}
} else {
console.log(`Using cached ${filename}.`);
}
fs.copyFileSync(cachedFile, path.join(runnerDir, filename));
if (!executeCommand(checksumCmd, runnerDir)) {
console.error('❌ Failed to validate file checksum.')
cleanupRunner(runnerDir);
return setImmediate(main);
}
if (!executeCommand(`tar xzf ./${filename}`, runnerDir)) {
console.error('❌ Failed to extract file.')
cleanupRunner(runnerDir);
return setImmediate(main);
}
// Cleanup: remove the tarball after extraction
try {
fs.unlinkSync(path.join(runnerDir, filename));
console.log(`Cleaned up ${filename}`);
} catch (err) {
console.error(`Failed to remove ${filename}: ${err.message}`);
}
const configCmd = `./config.sh --url https://github.com/${repo} --token ${token} --unattended --name ${runnerName}-Runner-${runnerNumber} --labels self-hosted,${platform},${arch}`;
if (!executeCommand(configCmd, runnerDir)) {
console.error('❌ Failed to configure runner. Possible reasons: invalid token, invalid repo, or runner name already in use.')
cleanupRunner(runnerDir);
return setImmediate(main);
}
console.log(`✅ Runner-${runnerNumber} added successfully.`);
// If no runners.config.json exists, ask the user to save these defaults.
if (!configExists) {
const saveDefaults = await askQuestion('👉 Would you like to save these values as defaults in runners.config.json for future use? (yes/no): ');
if (saveDefaults.trim().toLowerCase() === 'yes' || saveDefaults.trim().toLowerCase() === 'y') {
const defaults = {
version,
sha,
token,
repo,
runnerName,
platform,
arch
};
try {
fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2));
console.log('Defaults saved to runners.config.json');
} catch (err) {
console.error('Failed to save defaults:', err.message);
}
}
}
setImmediate(main);
}
function main() {
console.log('\nWhat do you want to do?');
console.log('1. Start runners');
console.log('2. Quit runners');
console.log('3. Add new runner');
rl.question('Select an option: ', async answer => {
if (answer === '1') await startRunners();
else if (answer === '2') await quitRunners();
else if (answer === '3') await addNewRunner();
else main();
});
}
main();