ramm 0.0.62 → 0.0.64
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/dist/bun.sh +23 -0
- package/dist/ramm.js +617 -0
- package/dist/types/base/base.d.ts +35 -0
- package/dist/types/base/tee.d.ts +2 -0
- package/dist/types/build.d.ts +7 -0
- package/dist/types/context.d.ts +16 -0
- package/dist/types/cron.d.ts +6 -0
- package/dist/types/files.d.ts +4 -0
- package/dist/types/init.d.ts +2 -0
- package/dist/types/nft.d.ts +8 -0
- package/dist/types/packages.d.ts +13 -0
- package/dist/types/path.d.ts +1 -0
- package/dist/types/podman.d.ts +25 -0
- package/dist/types/print.d.ts +3 -0
- package/dist/types/ramm.d.ts +13 -0
- package/dist/types/ssh.d.ts +12 -0
- package/dist/types/systemd.d.ts +10 -0
- package/package.json +4 -3
package/dist/bun.sh
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
if ! command -v unzip &> /dev/null; then
|
|
4
|
+
. /etc/os-release
|
|
5
|
+
|
|
6
|
+
if [[ "$ID_LIKE" == *"rhel"* ]]; then
|
|
7
|
+
sudo dnf install -y unzip
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
if [[ "$ID" == "ubuntu" ]]; then
|
|
11
|
+
sudo apt update
|
|
12
|
+
sudo apt install -y unzip
|
|
13
|
+
fi
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if ! command -v bun &> /dev/null; then
|
|
17
|
+
echo "Installing bun..."
|
|
18
|
+
|
|
19
|
+
curl -fsSL https://bun.sh/install | bash
|
|
20
|
+
ln -s "$HOME/.bun/bin/bun" /usr/local/bin/bun
|
|
21
|
+
else
|
|
22
|
+
echo "Already installed bun."
|
|
23
|
+
fi
|
package/dist/ramm.js
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/base/base.ts
|
|
3
|
+
var {spawn } = globalThis.Bun;
|
|
4
|
+
|
|
5
|
+
// src/print.ts
|
|
6
|
+
var printInternal = (left, right) => {
|
|
7
|
+
console.info(`\x1B[32m${left}:\x1B[0m \x1B[38;5;85m${right}\x1B[0m`);
|
|
8
|
+
};
|
|
9
|
+
var printCommand = (command) => {
|
|
10
|
+
printInternal("Command", command);
|
|
11
|
+
};
|
|
12
|
+
var printFunction = (func) => {
|
|
13
|
+
printInternal("Function", func);
|
|
14
|
+
};
|
|
15
|
+
var printBlock = (name) => {
|
|
16
|
+
console.info(`\x1B[34mBlock:\x1B[0m \x1B[38;5;81m${name}\x1B[0m`);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/context.ts
|
|
20
|
+
class Context {
|
|
21
|
+
user;
|
|
22
|
+
domain;
|
|
23
|
+
userspace = false;
|
|
24
|
+
sudo = false;
|
|
25
|
+
sshKey;
|
|
26
|
+
params = {};
|
|
27
|
+
constructor({
|
|
28
|
+
user,
|
|
29
|
+
address,
|
|
30
|
+
userspace = false,
|
|
31
|
+
sudo = false,
|
|
32
|
+
sshKey
|
|
33
|
+
}) {
|
|
34
|
+
this.user = user;
|
|
35
|
+
this.domain = address;
|
|
36
|
+
this.sudo = sudo;
|
|
37
|
+
this.userspace = userspace;
|
|
38
|
+
this.sshKey = sshKey;
|
|
39
|
+
}
|
|
40
|
+
getAddress() {
|
|
41
|
+
return `${this.user}@${this.domain}`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/base/tee.ts
|
|
46
|
+
var tee = async (read, write, prefix) => {
|
|
47
|
+
const reader = read.getReader();
|
|
48
|
+
let leftover = "";
|
|
49
|
+
let output = "";
|
|
50
|
+
const decoder = new TextDecoder("utf-8");
|
|
51
|
+
while (true) {
|
|
52
|
+
const { value, done } = await reader.read();
|
|
53
|
+
if (done) {
|
|
54
|
+
if (leftover)
|
|
55
|
+
write(prefix + leftover + `
|
|
56
|
+
`);
|
|
57
|
+
return output;
|
|
58
|
+
}
|
|
59
|
+
const decodedOutput = decoder.decode(value, { stream: true });
|
|
60
|
+
output += decodedOutput;
|
|
61
|
+
const lines = (leftover + decodedOutput).split(`
|
|
62
|
+
`);
|
|
63
|
+
leftover = lines.pop() ?? "";
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
write(prefix + line + `
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var teeStdout = (read, prefix) => {
|
|
71
|
+
return tee(read, (text) => process.stdout.write(text), prefix);
|
|
72
|
+
};
|
|
73
|
+
var teeStderr = (read, prefix) => {
|
|
74
|
+
return tee(read, (text) => process.stderr.write(text), prefix);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// src/base/base.ts
|
|
78
|
+
var defaultContext = new Context({
|
|
79
|
+
user: "root",
|
|
80
|
+
address: "0.0.0.0",
|
|
81
|
+
userspace: false,
|
|
82
|
+
sudo: false
|
|
83
|
+
});
|
|
84
|
+
var execCommandRaw = async (command, { store = {}, signal, env, cwd, prefix = "" } = {}, ctx) => {
|
|
85
|
+
const finalCommand = ctx?.sudo ? `sudo ${command}` : command;
|
|
86
|
+
const spawnResult = spawn(["bash", "-c", finalCommand], {
|
|
87
|
+
stdin: "inherit",
|
|
88
|
+
stdout: "pipe",
|
|
89
|
+
stderr: "pipe",
|
|
90
|
+
signal,
|
|
91
|
+
cwd,
|
|
92
|
+
env
|
|
93
|
+
});
|
|
94
|
+
store.spawnResult = spawnResult;
|
|
95
|
+
const [stdout, stderr] = await Promise.all([
|
|
96
|
+
teeStdout(spawnResult.stdout, prefix),
|
|
97
|
+
teeStderr(spawnResult.stderr, prefix)
|
|
98
|
+
]);
|
|
99
|
+
await spawnResult.exited;
|
|
100
|
+
return {
|
|
101
|
+
stderr,
|
|
102
|
+
stdout,
|
|
103
|
+
spawnResult
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
var execCommandMayError = async (command, props, context) => {
|
|
107
|
+
printCommand(command);
|
|
108
|
+
return execCommandRaw(command, props, context);
|
|
109
|
+
};
|
|
110
|
+
var execCommand = async (command, props, context) => {
|
|
111
|
+
const callSiteError = new Error(command);
|
|
112
|
+
const result = await execCommandMayError(command, props, context);
|
|
113
|
+
if (result.spawnResult.exitCode !== 0) {
|
|
114
|
+
console.error(`<${command}> exitCode: ${result.spawnResult.exitCode}`);
|
|
115
|
+
callSiteError.stack = callSiteError.stack?.split(`
|
|
116
|
+
`).slice(1).join(`
|
|
117
|
+
`);
|
|
118
|
+
throw callSiteError;
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
};
|
|
122
|
+
var copyFilesOverSsh = async (from, to, context) => {
|
|
123
|
+
const sshKeyPart = context.sshKey ? ` -e "ssh -i ${context.sshKey}"` : "";
|
|
124
|
+
await execCommand(`rsync -avz${sshKeyPart} ${from} ${context.getAddress()}:${to}`);
|
|
125
|
+
};
|
|
126
|
+
var execCommandOverSsh = async (command, context) => {
|
|
127
|
+
const sshKeyPart = context.sshKey ? ` -i ${context.sshKey}` : "";
|
|
128
|
+
const escapedCommand = command.replace(/'/g, "'\\''");
|
|
129
|
+
return await execCommand(`ssh${sshKeyPart} ${context.getAddress()} '${escapedCommand}'`);
|
|
130
|
+
};
|
|
131
|
+
// src/init.ts
|
|
132
|
+
var installBunOverSsh = async (context) => {
|
|
133
|
+
const bunPath = new URL(import.meta.resolve("./bun.sh")).pathname;
|
|
134
|
+
await copyFilesOverSsh(bunPath, "./bun.sh", context);
|
|
135
|
+
await execCommandOverSsh("./bun.sh", context);
|
|
136
|
+
};
|
|
137
|
+
// src/packages.ts
|
|
138
|
+
var dnfOs = ["rocky", "fedora", "alma"];
|
|
139
|
+
var aptOs = ["ubuntu"];
|
|
140
|
+
var getManagerByOs = (osName) => {
|
|
141
|
+
if (aptOs.includes(osName)) {
|
|
142
|
+
return "apt";
|
|
143
|
+
} else if (dnfOs.includes(osName)) {
|
|
144
|
+
return "dnf";
|
|
145
|
+
} else {
|
|
146
|
+
throw new Error(`Unsupported OS: ${osName}`);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var getManagerConfig = (manager, packageConfig) => {
|
|
150
|
+
if (packageConfig.managers && manager in packageConfig.managers) {
|
|
151
|
+
const managerEnt = packageConfig.managers[manager];
|
|
152
|
+
return managerEnt;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
name: packageConfig.name,
|
|
156
|
+
command: packageConfig.command
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
var getInstallCommand = (manager, config) => {
|
|
160
|
+
if (manager === "apt") {
|
|
161
|
+
return `apt install -y ${config.name}`;
|
|
162
|
+
} else if (manager === "dnf") {
|
|
163
|
+
return `dnf install -y ${config.name}`;
|
|
164
|
+
} else {
|
|
165
|
+
throw new Error("Unknow manager");
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
var packages = {
|
|
169
|
+
nftables: {
|
|
170
|
+
name: "nftables",
|
|
171
|
+
command: "nft"
|
|
172
|
+
},
|
|
173
|
+
podman: {
|
|
174
|
+
name: "podman",
|
|
175
|
+
command: "podman"
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
var installSystemPackage = async (packageEnt, context) => {
|
|
179
|
+
const finalPackageEnt = typeof packageEnt === "string" ? packages[packageEnt] : packageEnt;
|
|
180
|
+
if (!finalPackageEnt) {
|
|
181
|
+
const errorMessage = `No package ent for: ${packageEnt}`;
|
|
182
|
+
throw new Error(errorMessage);
|
|
183
|
+
}
|
|
184
|
+
const { stdout: osRaw } = await execCommand("cat /etc/os-release | grep ^ID= | cut -d'=' -f2", {}, context);
|
|
185
|
+
const osName = osRaw.trim().replace(/"/g, "");
|
|
186
|
+
const manager = getManagerByOs(osName);
|
|
187
|
+
const managerConfig = getManagerConfig(manager, finalPackageEnt);
|
|
188
|
+
const checkResult = await execCommandMayError(`command -v ${managerConfig.command}`, {}, context);
|
|
189
|
+
if (checkResult.spawnResult.exitCode === 0) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const installCommand = getInstallCommand(manager, managerConfig);
|
|
193
|
+
await execCommand(installCommand, {}, context);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// src/files.ts
|
|
197
|
+
import { appendFile, exists } from "fs/promises";
|
|
198
|
+
var {file, write } = globalThis.Bun;
|
|
199
|
+
|
|
200
|
+
// src/path.ts
|
|
201
|
+
import { homedir } from "os";
|
|
202
|
+
import { join } from "path";
|
|
203
|
+
function normalizePath(rawFilePath) {
|
|
204
|
+
let finalFilePath = rawFilePath.trim();
|
|
205
|
+
if (finalFilePath.startsWith("~/")) {
|
|
206
|
+
finalFilePath = join(homedir(), finalFilePath.slice(2));
|
|
207
|
+
}
|
|
208
|
+
return finalFilePath;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/files.ts
|
|
212
|
+
var normalizeFileContent = (str) => {
|
|
213
|
+
if (str === "") {
|
|
214
|
+
return str;
|
|
215
|
+
}
|
|
216
|
+
if (str.at(-1) !== `
|
|
217
|
+
`) {
|
|
218
|
+
return str + `
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
return str;
|
|
222
|
+
};
|
|
223
|
+
var createDir = async (str, context = defaultContext) => {
|
|
224
|
+
const dirname = str.split("/").slice(0, -1).join("/");
|
|
225
|
+
const exist = await exists(dirname);
|
|
226
|
+
if (exist) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
await execCommand(`mkdir -p ${dirname}`, {}, context);
|
|
230
|
+
};
|
|
231
|
+
var checkStrInFile = async (filePath, str) => {
|
|
232
|
+
const file2 = Bun.file(filePath);
|
|
233
|
+
if (!await file2.exists()) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
const fileText = await file2.text();
|
|
237
|
+
if (fileText.includes(str)) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
};
|
|
242
|
+
var createFileIfNeed = async (rawFilePath, context = defaultContext) => {
|
|
243
|
+
const filePath = normalizePath(rawFilePath);
|
|
244
|
+
await createDir(filePath, context);
|
|
245
|
+
if (!await file(filePath).exists()) {
|
|
246
|
+
await execCommand(`touch ${filePath}`, {}, context);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var writeFileStrUniq = async (rawFilePath, str, context = defaultContext) => {
|
|
250
|
+
const filePath = normalizePath(rawFilePath);
|
|
251
|
+
await createFileIfNeed(filePath, context);
|
|
252
|
+
if (await checkStrInFile(filePath, str)) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
await appendFile(filePath, normalizeFileContent(str));
|
|
256
|
+
};
|
|
257
|
+
var writeFile = async (pathToFile, str, context = defaultContext) => {
|
|
258
|
+
const normalizedPath = normalizePath(pathToFile);
|
|
259
|
+
await createFileIfNeed(normalizedPath, context);
|
|
260
|
+
const fileText = await file(normalizedPath).text();
|
|
261
|
+
if (fileText === str) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
await write(normalizedPath, str);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// src/systemd.ts
|
|
268
|
+
var systemctlWordLangth = "systemctl ".length;
|
|
269
|
+
var formatUserspace = (command, context = defaultContext) => {
|
|
270
|
+
const userPart = context.userspace ? " --user " : "";
|
|
271
|
+
return "systemctl" + userPart + " " + command.slice(systemctlWordLangth);
|
|
272
|
+
};
|
|
273
|
+
var reloadSystemd = async (context = defaultContext) => {
|
|
274
|
+
await execCommand(formatUserspace("systemctl daemon-reload", context));
|
|
275
|
+
};
|
|
276
|
+
var startSystemdUnit = async (unitName, context = defaultContext) => {
|
|
277
|
+
await execCommand(formatUserspace(`systemctl start ${unitName}`, context));
|
|
278
|
+
};
|
|
279
|
+
var enableSystemdUnit = async (unitName, context = defaultContext) => {
|
|
280
|
+
await execCommand(formatUserspace(`systemctl enable ${unitName}`, context));
|
|
281
|
+
};
|
|
282
|
+
var restartSystemdUnit = async (name, context = defaultContext) => {
|
|
283
|
+
await execCommand(formatUserspace(`systemctl restart ${name}`, context));
|
|
284
|
+
};
|
|
285
|
+
var stopSystemdUnit = async (name, context = defaultContext) => {
|
|
286
|
+
await execCommand(formatUserspace(`systemctl stop ${name}`, context));
|
|
287
|
+
};
|
|
288
|
+
var checkSystemdUnit = async (serviceName, context = defaultContext) => {
|
|
289
|
+
const { spawnResult } = await execCommandMayError(formatUserspace(`systemctl is-active ${serviceName}`, context));
|
|
290
|
+
return spawnResult.exitCode === 0;
|
|
291
|
+
};
|
|
292
|
+
var createSystemdUnit = async (unitName, content, context = defaultContext) => {
|
|
293
|
+
const pathToSeviceTarget = getSystemdPathToUnit(unitName, context);
|
|
294
|
+
await writeFile(pathToSeviceTarget, content);
|
|
295
|
+
await reloadSystemd(context);
|
|
296
|
+
};
|
|
297
|
+
var getSystemdPathToUnit = (serviceName, context = defaultContext) => {
|
|
298
|
+
if (context.userspace) {
|
|
299
|
+
return `~/.config/systemd/user/${serviceName}`;
|
|
300
|
+
}
|
|
301
|
+
return `/etc/systemd/system/${serviceName}`;
|
|
302
|
+
};
|
|
303
|
+
var createSystemdService = async (serviceName, content, context = defaultContext) => {
|
|
304
|
+
await createSystemdUnit(serviceName, content, context);
|
|
305
|
+
await enableSystemdUnit(serviceName, context);
|
|
306
|
+
await startSystemdUnit(serviceName, context);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// src/nft.ts
|
|
310
|
+
var createNftTable = ({
|
|
311
|
+
allowedIpV4,
|
|
312
|
+
allowedPorts = []
|
|
313
|
+
}) => {
|
|
314
|
+
const nftTable = `table inet ramm {
|
|
315
|
+
set allowed_ipv4 {
|
|
316
|
+
type ipv4_addr
|
|
317
|
+
flags dynamic
|
|
318
|
+
elements = { ${allowedIpV4.join(`
|
|
319
|
+
`)} }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
chain local_chain_base {
|
|
323
|
+
iif "lo" accept
|
|
324
|
+
ct state established,related accept
|
|
325
|
+
ip saddr @allowed_ipv4 tcp dport 22 accept
|
|
326
|
+
tcp dport 22 ct state new limit rate over 700/minute burst 5 packets drop
|
|
327
|
+
tcp dport 22 accept
|
|
328
|
+
${allowedPorts.map((port) => {
|
|
329
|
+
return `tcp dport ${port} accept`;
|
|
330
|
+
}).join(`
|
|
331
|
+
`)}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
chain local_chain {
|
|
335
|
+
jump local_chain_base
|
|
336
|
+
drop
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
chain prerouting {
|
|
340
|
+
type filter hook prerouting priority mangle; policy accept;
|
|
341
|
+
fib daddr type local jump local_chain
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
`;
|
|
345
|
+
return nftTable;
|
|
346
|
+
};
|
|
347
|
+
var setupNftable = async ({
|
|
348
|
+
allowedIpV4,
|
|
349
|
+
allowedPorts,
|
|
350
|
+
context = defaultContext
|
|
351
|
+
}) => {
|
|
352
|
+
await installSystemPackage("nftables", context);
|
|
353
|
+
const listTable = await execCommandMayError("nft list table inet ramm", {}, context);
|
|
354
|
+
if (listTable.spawnResult.exitCode === 0) {
|
|
355
|
+
await execCommand("nft delete table inet ramm", {}, context);
|
|
356
|
+
}
|
|
357
|
+
const nftTable = createNftTable({ allowedIpV4, allowedPorts });
|
|
358
|
+
await execCommand(`nft -f - <<EOF
|
|
359
|
+
${nftTable}
|
|
360
|
+
EOF`, {}, context);
|
|
361
|
+
await safeNftTable(context);
|
|
362
|
+
};
|
|
363
|
+
var safeNftTable = async (context = defaultContext) => {
|
|
364
|
+
await execCommand("nft list ruleset > /etc/nftables.conf", {}, context);
|
|
365
|
+
await execCommand("systemctl enable nftables", {}, context);
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// src/podman.ts
|
|
369
|
+
var installPodman = async (context = defaultContext) => {
|
|
370
|
+
const check = await execCommandMayError("command -v podman", {}, context);
|
|
371
|
+
if (check.spawnResult.exitCode !== 0) {
|
|
372
|
+
await installSystemPackage("podman", context);
|
|
373
|
+
}
|
|
374
|
+
await createNetwork(context);
|
|
375
|
+
};
|
|
376
|
+
var createNetwork = async (context = defaultContext) => {
|
|
377
|
+
const netwroks = await execCommand("podman network inspect $(podman network ls -q) -f '{{.NetworkInterface}}'", {}, context);
|
|
378
|
+
const podmanNetworks = await execCommand("podman network ls", {}, context);
|
|
379
|
+
if (podmanNetworks.stdout.includes("ramm")) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (netwroks.stdout.includes("podman_ramm")) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
await execCommand("podman network create --interface-name=podman_ramm ramm", {}, context);
|
|
386
|
+
};
|
|
387
|
+
var getCreateCommand = async (name, context = defaultContext) => {
|
|
388
|
+
const result = await execCommandMayError(`podman inspect --format '{{.Config.CreateCommand}}' ${name}`, {}, context);
|
|
389
|
+
const text = result.stdout.trim();
|
|
390
|
+
if (text.startsWith("[") && text.endsWith("]")) {
|
|
391
|
+
return text.slice(1, -1);
|
|
392
|
+
}
|
|
393
|
+
return text;
|
|
394
|
+
};
|
|
395
|
+
var loginPodman = async (address, login, password, context = defaultContext) => {
|
|
396
|
+
return await execCommand(`echo "${password}" | podman login --username "${login}" --password-stdin ${address}`, {}, context);
|
|
397
|
+
};
|
|
398
|
+
var createPodmanCommand = ({
|
|
399
|
+
name,
|
|
400
|
+
replace = true,
|
|
401
|
+
background = true,
|
|
402
|
+
networks = ["ramm"],
|
|
403
|
+
envs = [],
|
|
404
|
+
volumes = [],
|
|
405
|
+
command
|
|
406
|
+
}) => {
|
|
407
|
+
const values = [];
|
|
408
|
+
values.push("podman", "run");
|
|
409
|
+
if (name) {
|
|
410
|
+
values.push(`--name ${name}`);
|
|
411
|
+
}
|
|
412
|
+
if (replace) {
|
|
413
|
+
values.push("--replace");
|
|
414
|
+
}
|
|
415
|
+
if (background) {
|
|
416
|
+
values.push("-d");
|
|
417
|
+
}
|
|
418
|
+
for (const network of networks) {
|
|
419
|
+
values.push(`--network ${network}`);
|
|
420
|
+
}
|
|
421
|
+
for (const env of envs) {
|
|
422
|
+
values.push(`-e ${env.name}=${env.value}`);
|
|
423
|
+
}
|
|
424
|
+
for (const volume of volumes) {
|
|
425
|
+
values.push(`-v ${volume.from}:${volume.to}`);
|
|
426
|
+
}
|
|
427
|
+
values.push(command);
|
|
428
|
+
const str = values.join(" ");
|
|
429
|
+
return str;
|
|
430
|
+
};
|
|
431
|
+
var runPodmanContainer = async (name, command, context = defaultContext) => {
|
|
432
|
+
if (await getCreateCommand(name, context) !== command) {
|
|
433
|
+
await execCommand(`podman rm -f ${name}`, {}, context);
|
|
434
|
+
await execCommand(command, {}, context);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
console.info("Podman container is already running");
|
|
438
|
+
};
|
|
439
|
+
var runPodmanContainerService = async (name, command, context = defaultContext) => {
|
|
440
|
+
const serviceName = `${name}.service`;
|
|
441
|
+
const filepath = getSystemdPathToUnit(serviceName);
|
|
442
|
+
if (await checkSystemdUnit(serviceName, context)) {
|
|
443
|
+
await stopSystemdUnit(serviceName, context);
|
|
444
|
+
}
|
|
445
|
+
await runPodmanContainer(name, command, context);
|
|
446
|
+
await execCommand(`podman generate systemd --name --new ${name} > ${filepath}`, {}, context);
|
|
447
|
+
await reloadSystemd(context);
|
|
448
|
+
await startSystemdUnit(serviceName, context);
|
|
449
|
+
await enableSystemdUnit(serviceName, context);
|
|
450
|
+
};
|
|
451
|
+
var addNftPodmanRule = async (context = defaultContext) => {
|
|
452
|
+
const podmanNetworksResult = await execCommand("podman network inspect $(podman network ls -q) -f '{{.NetworkInterface}}'", {}, context);
|
|
453
|
+
const podmanNetworks = podmanNetworksResult.stdout.trim().split(`
|
|
454
|
+
`);
|
|
455
|
+
await execCommand(`nft add set inet ramm podman_interfaces '{ type ifname; flags dynamic; elements = { ${podmanNetworks.join(", ")} }; }'`, {}, context);
|
|
456
|
+
await execCommand("nft insert rule inet ramm prerouting iifname @podman_interfaces accept", {}, context);
|
|
457
|
+
await safeNftTable(context);
|
|
458
|
+
};
|
|
459
|
+
// src/ssh.ts
|
|
460
|
+
var {file: file2 } = globalThis.Bun;
|
|
461
|
+
var addKeyToHostConfig = async (pathToHost, address, pathToKey, context = defaultContext) => {
|
|
462
|
+
const text = `Host ${address}
|
|
463
|
+
IdentityFile ${pathToKey}
|
|
464
|
+
`;
|
|
465
|
+
await writeFileStrUniq(pathToHost, text, context);
|
|
466
|
+
};
|
|
467
|
+
var getServerFingerprint = async (context) => {
|
|
468
|
+
const { stdout } = await execCommandOverSsh('ssh-keyscan -t ed25519 localhost | grep -v "^#"', context);
|
|
469
|
+
return stdout.replace("localhost", context.domain);
|
|
470
|
+
};
|
|
471
|
+
var saveSshFingerptint = async (filePath, context) => {
|
|
472
|
+
const normalizedPath = normalizePath(filePath);
|
|
473
|
+
const fingerprint = await getServerFingerprint(context);
|
|
474
|
+
await writeFile(normalizedPath, fingerprint, context);
|
|
475
|
+
};
|
|
476
|
+
async function createSshKey(filePath, comment, context = defaultContext) {
|
|
477
|
+
printFunction(`${createSshKey.name} ${filePath}`);
|
|
478
|
+
const normalizedPathToKey = normalizePath(filePath);
|
|
479
|
+
const name = normalizedPathToKey.split("/").at(-1);
|
|
480
|
+
const pathToKeyPub = `${normalizedPathToKey}.pub`;
|
|
481
|
+
const pathToDir = normalizedPathToKey.split("/").slice(0, -1).join("/");
|
|
482
|
+
const pathToKeyFile = file2(normalizedPathToKey);
|
|
483
|
+
const pathToKeyPubFile = file2(pathToKeyPub);
|
|
484
|
+
if (await pathToKeyFile.exists() && await pathToKeyPubFile.exists()) {
|
|
485
|
+
return await pathToKeyPubFile.text();
|
|
486
|
+
}
|
|
487
|
+
await execCommand(`mkdir -p ${pathToDir}`, {}, context);
|
|
488
|
+
await execCommand(`ssh-keygen -t ed25519 -f ${normalizedPathToKey} -N "" -C "${comment || name}"`, {}, context);
|
|
489
|
+
await execCommand(`chmod 600 ${normalizedPathToKey}`, {}, context);
|
|
490
|
+
const pubKey = await Bun.file(pathToKeyPub).text();
|
|
491
|
+
return pubKey;
|
|
492
|
+
}
|
|
493
|
+
var addSshKeyToAuthorizedOverSsh = async (pubKey, context) => {
|
|
494
|
+
printFunction(`${addSshKeyToAuthorizedOverSsh.name} ${pubKey}`);
|
|
495
|
+
const { stdout: keys } = await execCommandOverSsh("cat .ssh/authorized_keys", context);
|
|
496
|
+
if (keys.includes(pubKey)) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
await execCommandOverSsh(`echo "${pubKey}" >> .ssh/authorized_keys`, context);
|
|
500
|
+
};
|
|
501
|
+
var createAndAddSshKey = async (filePath, comment, context) => {
|
|
502
|
+
const normalizedPath = normalizePath(filePath);
|
|
503
|
+
const pubKey = await createSshKey(normalizedPath, comment, context);
|
|
504
|
+
console.log(`Key is: ${pubKey}`);
|
|
505
|
+
await addSshKeyToAuthorizedOverSsh(pubKey, context);
|
|
506
|
+
};
|
|
507
|
+
var addSshKeyToUse = async ({
|
|
508
|
+
key,
|
|
509
|
+
fingerprint,
|
|
510
|
+
filePath,
|
|
511
|
+
server,
|
|
512
|
+
context = defaultContext
|
|
513
|
+
}) => {
|
|
514
|
+
printFunction(`${addSshKeyToUse.name} ${filePath}`);
|
|
515
|
+
const normalizedFilePath = normalizePath(filePath);
|
|
516
|
+
await writeFile(normalizedFilePath, key, context);
|
|
517
|
+
await execCommand(`chmod 0600 ${normalizedFilePath}`, {}, context);
|
|
518
|
+
await writeFileStrUniq("~/.ssh/known_hosts", fingerprint, context);
|
|
519
|
+
await addKeyToHostConfig("~/.ssh/config", server, normalizedFilePath, context);
|
|
520
|
+
};
|
|
521
|
+
// src/cron.ts
|
|
522
|
+
var createCron = async ({
|
|
523
|
+
time,
|
|
524
|
+
pathToFile,
|
|
525
|
+
context = defaultContext
|
|
526
|
+
}) => {
|
|
527
|
+
const pathToFileNorm = normalizePath(pathToFile);
|
|
528
|
+
const constructedLine = `${time} ${pathToFileNorm}`;
|
|
529
|
+
const tempFile = `/tmp/ramm_cron`;
|
|
530
|
+
const { stdout: cronConfig } = await execCommandMayError("crontab -l", {}, context);
|
|
531
|
+
if (cronConfig.includes(constructedLine)) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
let baseCronConfig = cronConfig;
|
|
535
|
+
if (cronConfig.includes(pathToFileNorm)) {
|
|
536
|
+
baseCronConfig = cronConfig.split(`
|
|
537
|
+
`).filter((str) => !str.includes(pathToFileNorm)).join(`
|
|
538
|
+
`);
|
|
539
|
+
}
|
|
540
|
+
const newCronConfig = normalizeFileContent(normalizeFileContent(baseCronConfig) + constructedLine);
|
|
541
|
+
await writeFile(tempFile, newCronConfig, context);
|
|
542
|
+
await execCommandMayError(`cat ${tempFile}`, {}, context);
|
|
543
|
+
await execCommandMayError(`crontab ${tempFile}`, {}, context);
|
|
544
|
+
await execCommandMayError(`rm ${tempFile}`, {}, context);
|
|
545
|
+
};
|
|
546
|
+
// src/build.ts
|
|
547
|
+
var {build, file: file3 } = globalThis.Bun;
|
|
548
|
+
var buildAndRunOverSsh = async ({
|
|
549
|
+
entrypoint,
|
|
550
|
+
context
|
|
551
|
+
}) => {
|
|
552
|
+
const normalizedEntrypoint = normalizePath(entrypoint);
|
|
553
|
+
const distName = `ramm_dist`;
|
|
554
|
+
const distDir = `/tmp/${distName}`;
|
|
555
|
+
const outputs = await build({
|
|
556
|
+
outdir: distDir,
|
|
557
|
+
entrypoints: [normalizedEntrypoint],
|
|
558
|
+
target: "bun"
|
|
559
|
+
});
|
|
560
|
+
const pathToDistFile = outputs.outputs[0]?.path;
|
|
561
|
+
const relativePathToFile = pathToDistFile.slice(distDir.length + 1);
|
|
562
|
+
await copyFilesOverSsh(`${distDir}/`, distDir, context);
|
|
563
|
+
await execCommandOverSsh(`bun run ${distDir}/${relativePathToFile}`, context);
|
|
564
|
+
await execCommand(`rm -rf ${distDir}`, {}, context);
|
|
565
|
+
};
|
|
566
|
+
var pathToJson = "/tmp/ramm_json";
|
|
567
|
+
var passVarsClient = async (data, context) => {
|
|
568
|
+
printFunction("passVarsClient");
|
|
569
|
+
const json = JSON.stringify(data);
|
|
570
|
+
await writeFile(pathToJson, json, context);
|
|
571
|
+
await copyFilesOverSsh(pathToJson, pathToJson, context);
|
|
572
|
+
await execCommand(`rm -rf ${pathToJson}`, {}, context);
|
|
573
|
+
};
|
|
574
|
+
var passVarsServer = async (context = defaultContext) => {
|
|
575
|
+
printFunction("passVarsServer");
|
|
576
|
+
const jsonData = await file3(pathToJson).json();
|
|
577
|
+
await execCommand(`rm -rf ${pathToJson}`, {}, context);
|
|
578
|
+
return jsonData;
|
|
579
|
+
};
|
|
580
|
+
export {
|
|
581
|
+
writeFileStrUniq,
|
|
582
|
+
writeFile,
|
|
583
|
+
startSystemdUnit,
|
|
584
|
+
setupNftable,
|
|
585
|
+
saveSshFingerptint,
|
|
586
|
+
runPodmanContainerService,
|
|
587
|
+
runPodmanContainer,
|
|
588
|
+
restartSystemdUnit,
|
|
589
|
+
reloadSystemd,
|
|
590
|
+
printBlock,
|
|
591
|
+
passVarsServer,
|
|
592
|
+
passVarsClient,
|
|
593
|
+
normalizePath,
|
|
594
|
+
normalizeFileContent,
|
|
595
|
+
loginPodman,
|
|
596
|
+
installSystemPackage,
|
|
597
|
+
installPodman,
|
|
598
|
+
installBunOverSsh,
|
|
599
|
+
getSystemdPathToUnit,
|
|
600
|
+
getServerFingerprint,
|
|
601
|
+
execCommandRaw,
|
|
602
|
+
execCommandOverSsh,
|
|
603
|
+
execCommandMayError,
|
|
604
|
+
execCommand,
|
|
605
|
+
enableSystemdUnit,
|
|
606
|
+
createSystemdUnit,
|
|
607
|
+
createSystemdService,
|
|
608
|
+
createPodmanCommand,
|
|
609
|
+
createCron,
|
|
610
|
+
createAndAddSshKey,
|
|
611
|
+
copyFilesOverSsh,
|
|
612
|
+
buildAndRunOverSsh,
|
|
613
|
+
addSshKeyToUse,
|
|
614
|
+
addNftPodmanRule,
|
|
615
|
+
addKeyToHostConfig,
|
|
616
|
+
Context
|
|
617
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type Subprocess } from "bun";
|
|
2
|
+
import { Context } from "../context.ts";
|
|
3
|
+
export declare const defaultContext: Context;
|
|
4
|
+
export type ExecCommandStore = {
|
|
5
|
+
spawnResult?: Subprocess<"inherit", "pipe", "pipe">;
|
|
6
|
+
};
|
|
7
|
+
type ExecProps = {
|
|
8
|
+
store?: ExecCommandStore;
|
|
9
|
+
env?: Record<string, string>;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
prefix?: string;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
} | void;
|
|
14
|
+
export declare const execCommandRaw: (command: string, { store, signal, env, cwd, prefix }?: ExecProps, ctx?: Context) => Promise<{
|
|
15
|
+
stderr: string;
|
|
16
|
+
stdout: string;
|
|
17
|
+
spawnResult: Subprocess<"inherit", "pipe", "pipe">;
|
|
18
|
+
}>;
|
|
19
|
+
export declare const execCommandMayError: (command: string, props: ExecProps, context?: Context) => Promise<{
|
|
20
|
+
stderr: string;
|
|
21
|
+
stdout: string;
|
|
22
|
+
spawnResult: Subprocess<"inherit", "pipe", "pipe">;
|
|
23
|
+
}>;
|
|
24
|
+
export declare const execCommand: (command: string, props: ExecProps, context?: Context) => Promise<{
|
|
25
|
+
stderr: string;
|
|
26
|
+
stdout: string;
|
|
27
|
+
spawnResult: Subprocess<"inherit", "pipe", "pipe">;
|
|
28
|
+
}>;
|
|
29
|
+
export declare const copyFilesOverSsh: (from: string, to: string, context: Context) => Promise<void>;
|
|
30
|
+
export declare const execCommandOverSsh: (command: string, context: Context) => Promise<{
|
|
31
|
+
stderr: string;
|
|
32
|
+
stdout: string;
|
|
33
|
+
spawnResult: Subprocess<"inherit", "pipe", "pipe">;
|
|
34
|
+
}>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Context } from "./context.ts";
|
|
2
|
+
export declare const buildAndRunOverSsh: ({ entrypoint, context, }: {
|
|
3
|
+
entrypoint: string;
|
|
4
|
+
context: Context;
|
|
5
|
+
}) => Promise<void>;
|
|
6
|
+
export declare const passVarsClient: (data: Record<string, any>, context: Context) => Promise<void>;
|
|
7
|
+
export declare const passVarsServer: (context?: Context) => Promise<any>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class Context {
|
|
2
|
+
user: string;
|
|
3
|
+
domain: string;
|
|
4
|
+
userspace: boolean;
|
|
5
|
+
sudo: boolean;
|
|
6
|
+
sshKey?: string;
|
|
7
|
+
params: Record<string, string>;
|
|
8
|
+
constructor({ user, address, userspace, sudo, sshKey, }: {
|
|
9
|
+
user: string;
|
|
10
|
+
address: string;
|
|
11
|
+
userspace?: boolean;
|
|
12
|
+
sudo?: boolean;
|
|
13
|
+
sshKey?: string;
|
|
14
|
+
});
|
|
15
|
+
getAddress(): string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Context } from "./context.ts";
|
|
2
|
+
export declare const normalizeFileContent: (str: string) => string;
|
|
3
|
+
export declare const writeFileStrUniq: (rawFilePath: string, str: string, context?: Context) => Promise<void>;
|
|
4
|
+
export declare const writeFile: (pathToFile: string, str: string, context?: Context) => Promise<void>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Context } from "./context.ts";
|
|
2
|
+
export declare const localNftChainName = "local_chain";
|
|
3
|
+
export declare const setupNftable: ({ allowedIpV4, allowedPorts, context, }: {
|
|
4
|
+
allowedIpV4: string[];
|
|
5
|
+
allowedPorts: string[];
|
|
6
|
+
context?: Context;
|
|
7
|
+
}) => Promise<void>;
|
|
8
|
+
export declare const safeNftTable: (context?: Context) => Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Context } from "./context.ts";
|
|
2
|
+
type Manager = "dnf" | "apt";
|
|
3
|
+
type PackageConfig = {
|
|
4
|
+
name: string;
|
|
5
|
+
command: string;
|
|
6
|
+
managers?: Record<Manager, ManagerConfig>;
|
|
7
|
+
};
|
|
8
|
+
type ManagerConfig = {
|
|
9
|
+
name: string;
|
|
10
|
+
command: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const installSystemPackage: (packageEnt: string | PackageConfig, context?: Context) => Promise<void>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function normalizePath(rawFilePath: string): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Context } from "./context.ts";
|
|
2
|
+
export declare const installPodman: (context?: Context) => Promise<void>;
|
|
3
|
+
export declare const loginPodman: (address: string, login: string, password: string, context?: Context) => Promise<{
|
|
4
|
+
stderr: string;
|
|
5
|
+
stdout: string;
|
|
6
|
+
spawnResult: import("bun").Subprocess<"inherit", "pipe", "pipe">;
|
|
7
|
+
}>;
|
|
8
|
+
export declare const createPodmanCommand: ({ name, replace, background, networks, envs, volumes, command, }: {
|
|
9
|
+
name?: string;
|
|
10
|
+
replace?: boolean;
|
|
11
|
+
background?: boolean;
|
|
12
|
+
networks?: string[];
|
|
13
|
+
envs?: {
|
|
14
|
+
name: string;
|
|
15
|
+
value: string;
|
|
16
|
+
}[];
|
|
17
|
+
volumes?: {
|
|
18
|
+
from: string;
|
|
19
|
+
to: string;
|
|
20
|
+
}[];
|
|
21
|
+
command: string;
|
|
22
|
+
}) => string;
|
|
23
|
+
export declare const runPodmanContainer: (name: string, command: string, context?: Context) => Promise<void>;
|
|
24
|
+
export declare const runPodmanContainerService: (name: string, command: string, context?: Context) => Promise<void>;
|
|
25
|
+
export declare const addNftPodmanRule: (context?: Context) => Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { execCommandOverSsh, execCommand, execCommandMayError, copyFilesOverSsh, execCommandRaw, } from "./base/base.ts";
|
|
2
|
+
export { Context } from "./context.ts";
|
|
3
|
+
export { installBunOverSsh } from "./init.ts";
|
|
4
|
+
export { installPodman, runPodmanContainer, loginPodman, runPodmanContainerService, addNftPodmanRule, createPodmanCommand, } from "./podman.ts";
|
|
5
|
+
export { startSystemdUnit, enableSystemdUnit, restartSystemdUnit, createSystemdService, getSystemdPathToUnit, createSystemdUnit, reloadSystemd, } from "./systemd.ts";
|
|
6
|
+
export { installSystemPackage } from "./packages.ts";
|
|
7
|
+
export { printBlock } from "./print.ts";
|
|
8
|
+
export { setupNftable } from "./nft.ts";
|
|
9
|
+
export { writeFileStrUniq, writeFile, normalizeFileContent, } from "./files.ts";
|
|
10
|
+
export { createAndAddSshKey, getServerFingerprint, addKeyToHostConfig, addSshKeyToUse, saveSshFingerptint, } from "./ssh.ts";
|
|
11
|
+
export { normalizePath } from "./path.ts";
|
|
12
|
+
export { createCron } from "./cron.ts";
|
|
13
|
+
export { buildAndRunOverSsh, passVarsClient, passVarsServer } from "./build.ts";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Context } from "./context.ts";
|
|
2
|
+
export declare const addKeyToHostConfig: (pathToHost: string, address: string, pathToKey: string, context?: Context) => Promise<void>;
|
|
3
|
+
export declare const getServerFingerprint: (context: Context) => Promise<string>;
|
|
4
|
+
export declare const saveSshFingerptint: (filePath: string, context: Context) => Promise<void>;
|
|
5
|
+
export declare const createAndAddSshKey: (filePath: string, comment: string, context: Context) => Promise<void>;
|
|
6
|
+
export declare const addSshKeyToUse: ({ key, fingerprint, filePath, server, context, }: {
|
|
7
|
+
key: string;
|
|
8
|
+
fingerprint: string;
|
|
9
|
+
filePath: string;
|
|
10
|
+
server: string;
|
|
11
|
+
context?: Context;
|
|
12
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Context } from "./context.ts";
|
|
2
|
+
export declare const reloadSystemd: (context?: Context) => Promise<void>;
|
|
3
|
+
export declare const startSystemdUnit: (unitName: string, context?: Context) => Promise<void>;
|
|
4
|
+
export declare const enableSystemdUnit: (unitName: string, context?: Context) => Promise<void>;
|
|
5
|
+
export declare const restartSystemdUnit: (name: string, context?: Context) => Promise<void>;
|
|
6
|
+
export declare const stopSystemdUnit: (name: string, context?: Context) => Promise<void>;
|
|
7
|
+
export declare const checkSystemdUnit: (serviceName: string, context?: Context) => Promise<boolean>;
|
|
8
|
+
export declare const createSystemdUnit: (unitName: string, content: string, context?: Context) => Promise<void>;
|
|
9
|
+
export declare const getSystemdPathToUnit: (serviceName: string, context?: Context) => string;
|
|
10
|
+
export declare const createSystemdService: (serviceName: string, content: string, context?: Context) => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ramm",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.64",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"build": "bun build
|
|
7
|
-
"
|
|
6
|
+
"build": "bun run build:js && bun run build:types",
|
|
7
|
+
"build:js": "bun build ./src/ramm.ts --target bun --outdir ./dist && cp ./src/bun.sh ./dist/bun.sh",
|
|
8
|
+
"build:types": "tsc --project tsconfig.types.json"
|
|
8
9
|
},
|
|
9
10
|
"main": "dist/ramm.js",
|
|
10
11
|
"types": "dist/types/ramm.d.ts",
|