Skip to content

Commit 4ca2625

Browse files
committed
feat(reboot-tracker): add reboot-tracker.mjs (TS-stripped Deno script)
1 parent 80cbb5b commit 4ca2625

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

reboot-tracker.mjs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// scripts/reboot-tracker.mjs
2+
import { join } from "https://deno.land/std@0.224.0/path/mod.ts";
3+
import { parseArgs } from "https://deno.land/std@0.224.0/cli/parse_args.ts";
4+
5+
const REASONS = [
6+
"Planned Maintenance",
7+
"Security Update",
8+
"Hardware Issue",
9+
"OS Update",
10+
"Unexpected Crash",
11+
"Software Bug",
12+
"Migration",
13+
"Other"
14+
];
15+
16+
const BASE_DIR = "monitoring/reboot-tracker/logs";
17+
const LOG_FILE = join(BASE_DIR, "reboot-reasons.json");
18+
const SNAPSHOT_DIR = join(BASE_DIR, "snapshots");
19+
20+
async function captureLogs(timestamp) {
21+
const filename = `snapshot-${timestamp.replace(/[:.]/g, "-")}.log`;
22+
const filepath = join(SNAPSHOT_DIR, filename);
23+
24+
console.log(`\nCapturing system logs to ${filename}...`);
25+
26+
try {
27+
await Deno.mkdir(SNAPSHOT_DIR, { recursive: true });
28+
29+
// Capture journalctl and dmesg
30+
const journalCmd = new Deno.Command("sudo", {
31+
args: ["journalctl", "-n", "200", "--no-pager"],
32+
});
33+
const dmesgCmd = new Deno.Command("sudo", {
34+
args: ["dmesg", "-T"], // -T for human readable timestamps
35+
});
36+
37+
const journalResult = await journalCmd.output();
38+
const dmesgResult = await dmesgCmd.output();
39+
40+
const decoder = new TextDecoder();
41+
const logContent = [
42+
"=== SYSTEM LOG SNAPSHOT ===",
43+
`Captured at: ${timestamp}`,
44+
"",
45+
"--- journalctl (last 200 lines) ---",
46+
decoder.decode(journalResult.stdout),
47+
"",
48+
"--- dmesg (tail) ---",
49+
decoder.decode(dmesgResult.stdout).split("\n").slice(-100).join("\n"),
50+
].join("\n");
51+
52+
await Deno.writeTextFile(filepath, logContent);
53+
return filename;
54+
} catch (err) {
55+
console.error(`Warning: Failed to capture system logs: ${err.message}`);
56+
return null;
57+
}
58+
}
59+
60+
async function main() {
61+
const args = parseArgs(Deno.args);
62+
const isShutdown = args.shutdown === true;
63+
const actionName = isShutdown ? "SHUTDOWN" : "REBOOT";
64+
65+
console.log("--------------------------------------------------");
66+
console.log(` SYSTEM ${actionName} TRACKER (Server Reason Prompt) `);
67+
console.log("--------------------------------------------------");
68+
console.log(`\nPlease select a reason for the ${actionName}:`);
69+
REASONS.forEach((reason, i) => {
70+
console.log(` [${i + 1}] ${reason}`);
71+
});
72+
73+
let selection = "";
74+
while (true) {
75+
const input = prompt("\nEnter your choice [1-8]:");
76+
if (input && Number(input) >= 1 && Number(input) <= REASONS.length) {
77+
selection = REASONS[Number(input) - 1];
78+
break;
79+
}
80+
console.log("Invalid selection. Please try again.");
81+
}
82+
83+
const details = prompt("\nProvide details/comments (optional):") || "No details provided.";
84+
85+
const timestamp = new Date().toISOString();
86+
const snapshotFile = await captureLogs(timestamp);
87+
88+
const logEntry = {
89+
timestamp: timestamp,
90+
user: Deno.env.get("USER") || "unknown",
91+
action: actionName,
92+
reason: selection,
93+
details: details,
94+
hostname: Deno.hostname(),
95+
snapshot: snapshotFile,
96+
};
97+
98+
try {
99+
let logs = [];
100+
try {
101+
const content = await Deno.readTextFile(LOG_FILE);
102+
logs = JSON.parse(content);
103+
} catch {
104+
// File doesn't exist or is empty
105+
}
106+
107+
logs.push(logEntry);
108+
await Deno.writeTextFile(LOG_FILE, JSON.stringify(logs, null, 2));
109+
console.log(`\nReason and log snapshot recorded in ${BASE_DIR}`);
110+
} catch (err) {
111+
console.error(`Error logging reason: ${err.message}`);
112+
const retry = prompt(`\nContinue with ${actionName} anyway? (y/N):`);
113+
if (retry?.toLowerCase() !== 'y') {
114+
console.log("Aborted.");
115+
Deno.exit(1);
116+
}
117+
}
118+
119+
const confirm = prompt(`\nAre you sure you want to ${actionName} now? (y/N):`);
120+
if (confirm?.toLowerCase() === 'y') {
121+
console.log(`Initiating ${actionName}...`);
122+
const cmd = new Deno.Command("sudo", {
123+
args: [isShutdown ? "shutdown" : "reboot"],
124+
});
125+
await cmd.spawn();
126+
} else {
127+
console.log(`${actionName} cancelled. Reason and logs have been recorded.`);
128+
}
129+
}
130+
131+
main();

0 commit comments

Comments
 (0)