@tyevco/homelab-lxc-agent 1.9.8 → 1.9.10

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/lxc.js CHANGED
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.joinExecTerminal = exports.getDistributions = exports.cloneContainer = exports.createContainer = exports.saveConfig = exports.deleteContainer = exports.unfreezeContainer = exports.freezeContainer = exports.restartContainer = exports.stopContainer = exports.startContainer = exports.getContainer = exports.getContainerList = exports.STACK_TYPE_LXC = exports.FROZEN = exports.EXITED = exports.RUNNING = exports.UNKNOWN = void 0;
26
+ exports.joinExecTerminal = exports.getDistributions = exports.listSnapshots = exports.cloneContainer = exports.createContainer = exports.saveConfig = exports.deleteContainer = exports.unfreezeContainer = exports.freezeContainer = exports.restartContainer = exports.stopContainer = exports.startContainer = exports.getContainer = exports.getContainerList = exports.STACK_TYPE_LXC = exports.FROZEN = exports.EXITED = exports.RUNNING = exports.UNKNOWN = void 0;
27
27
  const promisify_child_process_1 = require("promisify-child-process");
28
28
  const fs = __importStar(require("fs"));
29
29
  const path = __importStar(require("path"));
@@ -265,24 +265,50 @@ async function createContainer(socket, endpoint, name, dist, release, arch, init
265
265
  console.log(`[lxc] Created: ${name}`);
266
266
  }
267
267
  exports.createContainer = createContainer;
268
- async function cloneContainer(socket, endpoint, sourceName, destName, initialConfig) {
268
+ async function cloneContainer(socket, endpoint, sourceName, destName, snapshotName, initialConfig) {
269
269
  if (!/^[a-z0-9_.-]+$/.test(sourceName)) {
270
270
  throw new Error("Invalid source container name");
271
271
  }
272
272
  if (!/^[a-z0-9_.-]+$/.test(destName)) {
273
273
  throw new Error("Invalid destination container name");
274
274
  }
275
- console.log(`[lxc] Cloning container: ${sourceName} → ${destName}`);
276
- const code = await terminal_1.AgentTerminal.exec(socket, getLxcTerminalName(endpoint, destName), "lxc-copy", ["-n", sourceName, "-N", destName], LXC_PATH);
275
+ if (snapshotName && !/^[a-z0-9_.-]+$/.test(snapshotName)) {
276
+ throw new Error("Invalid snapshot name");
277
+ }
278
+ const args = snapshotName
279
+ ? ["-n", sourceName, "-s", snapshotName, "-N", destName]
280
+ : ["-n", sourceName, "-N", destName];
281
+ console.log(`[lxc] Cloning container: ${sourceName}${snapshotName ? `@${snapshotName}` : ""} → ${destName}`);
282
+ const code = await terminal_1.AgentTerminal.exec(socket, getLxcTerminalName(endpoint, destName), "lxc-copy", args, LXC_PATH);
277
283
  if (code !== 0) {
278
284
  throw new Error("Failed to clone LXC container");
279
285
  }
280
286
  if (initialConfig) {
281
287
  await appendConfig(destName, initialConfig);
282
288
  }
283
- console.log(`[lxc] Cloned: ${sourceName} → ${destName}`);
289
+ console.log(`[lxc] Cloned: ${sourceName}${snapshotName ? `@${snapshotName}` : ""} → ${destName}`);
284
290
  }
285
291
  exports.cloneContainer = cloneContainer;
292
+ async function listSnapshots(containerName) {
293
+ if (!/^[a-z0-9_.-]+$/.test(containerName)) {
294
+ throw new Error("Invalid container name");
295
+ }
296
+ try {
297
+ const res = await (0, promisify_child_process_1.spawn)("lxc-snapshot", ["-n", containerName, "-L"], { encoding: "utf-8" });
298
+ const output = (res.stdout || "").trim();
299
+ if (!output) {
300
+ return [];
301
+ }
302
+ // Each line: "snap0 (/var/lib/lxc/.../snap0) 2024-01-01 ..."
303
+ return output.split("\n")
304
+ .map(line => line.trim().split(" ")[0])
305
+ .filter(name => name.length > 0);
306
+ }
307
+ catch {
308
+ return [];
309
+ }
310
+ }
311
+ exports.listSnapshots = listSnapshots;
286
312
  async function appendConfig(name, extra) {
287
313
  const configPath = path.join(LXC_PATH, name, "config");
288
314
  const existing = await fs.promises.readFile(configPath, "utf-8").catch(() => "");
package/dist/server.js CHANGED
@@ -263,18 +263,28 @@ async function dispatch(socket, endpoint, eventName, args) {
263
263
  break;
264
264
  }
265
265
  case "cloneLxcContainer": {
266
- const [sourceName, destName, initialConfig] = args;
266
+ const [sourceName, destName, snapshotName, initialConfig] = args;
267
267
  if (typeof sourceName !== "string") {
268
268
  throw new Error("Source name must be a string");
269
269
  }
270
270
  if (typeof destName !== "string") {
271
271
  throw new Error("Destination name must be a string");
272
272
  }
273
- await lxc.cloneContainer(socket, endpoint, sourceName, destName, initialConfig || undefined);
273
+ await lxc.cloneContainer(socket, endpoint, sourceName, destName, snapshotName || undefined, initialConfig || undefined);
274
274
  await pushList();
275
275
  ok("Cloned");
276
276
  break;
277
277
  }
278
+ case "getLxcSnapshots": {
279
+ const [containerName] = args;
280
+ if (typeof containerName !== "string") {
281
+ throw new Error("Container name must be a string");
282
+ }
283
+ const snapshots = await lxc.listSnapshots(containerName);
284
+ callback?.({ ok: true,
285
+ snapshots });
286
+ break;
287
+ }
278
288
  case "getLxcDistributions": {
279
289
  const distributions = await lxc.getDistributions();
280
290
  callback?.({ ok: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyevco/homelab-lxc-agent",
3
- "version": "1.9.8",
3
+ "version": "1.9.10",
4
4
  "description": "Lightweight LXC agent for Homelab",
5
5
  "bin": {
6
6
  "homelab-lxc-agent": "bin/homelab-lxc-agent.js"