mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 19:35:13 +08:00
✨ Sync 2026-09-09 01:26:15
This commit is contained in:
1 parent
2b23b9049b
commit
7e90b2cbf7
27 files changed
+1113
No files matched your search
@@ -0,0 +1,47 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=smart-reboot
|
||||
PKG_RELEASE:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/smart-reboot
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=Smart reboot based on network idle state
|
||||
DEPENDS:=+busybox +uci +ucode +ucode-mod-uci +ucode-mod-log
|
||||
endef
|
||||
|
||||
define Package/smart-reboot/description
|
||||
Reboot router at configured dawn time only when monitored interfaces are idle.
|
||||
endef
|
||||
|
||||
define Package/smart-reboot/conffiles
|
||||
/etc/config/smart-reboot
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
true
|
||||
endef
|
||||
|
||||
define Package/smart-reboot/install
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/etc/config/smart-reboot $(1)/etc/config/smart-reboot
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/etc/init.d/smart-reboot $(1)/etc/init.d/smart-reboot
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) ./files/usr/sbin/smart-reboot-check $(1)/usr/sbin/smart-reboot-check
|
||||
endef
|
||||
|
||||
define Package/smart-reboot/postinst
|
||||
#!/bin/sh
|
||||
[ -n "$$IPKG_INSTROOT" ] || {
|
||||
/etc/init.d/smart-reboot enable >/dev/null 2>&1
|
||||
/etc/init.d/smart-reboot restart >/dev/null 2>&1
|
||||
}
|
||||
exit 0
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,smart-reboot))
|
||||
@@ -0,0 +1,8 @@
|
||||
config settings 'settings'
|
||||
option enabled '1'
|
||||
option time '04:00'
|
||||
option all_ifaces '0'
|
||||
option last_auto_reboot ''
|
||||
list ifaces 'wan'
|
||||
option sample_seconds '120'
|
||||
option byte_threshold '262144'
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=95
|
||||
STOP=10
|
||||
|
||||
CRON_FILE="/etc/crontabs/root"
|
||||
MARKER="# smart-reboot"
|
||||
|
||||
. /lib/functions.sh
|
||||
|
||||
cron_cleanup() {
|
||||
[ -f "$CRON_FILE" ] || return 0
|
||||
sed -i "\\|$MARKER|d" "$CRON_FILE"
|
||||
}
|
||||
|
||||
cron_append() {
|
||||
local minute="$1"
|
||||
local hour="$2"
|
||||
echo "$minute $hour * * * /usr/sbin/smart-reboot-check $MARKER" >> "$CRON_FILE"
|
||||
}
|
||||
|
||||
gen_schedule() {
|
||||
local enabled hour minute time
|
||||
|
||||
config_load smart-reboot
|
||||
config_get enabled settings enabled '0'
|
||||
config_get time settings time ''
|
||||
|
||||
case "$time" in
|
||||
[0-1][0-9]:[0-5][0-9]|2[0-3]:[0-5][0-9])
|
||||
hour="${time%:*}"
|
||||
minute="${time#*:}"
|
||||
hour=$((10#$hour))
|
||||
minute=$((10#$minute))
|
||||
;;
|
||||
*)
|
||||
config_get hour settings hour '4'
|
||||
config_get minute settings minute '0'
|
||||
;;
|
||||
esac
|
||||
|
||||
cron_cleanup
|
||||
|
||||
[ "$enabled" = "1" ] || return 0
|
||||
[ -n "$hour" ] || return 0
|
||||
[ -n "$minute" ] || return 0
|
||||
|
||||
cron_append "$minute" "$hour"
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
start_service
|
||||
}
|
||||
|
||||
start_service() {
|
||||
[ -f "$CRON_FILE" ] || touch "$CRON_FILE"
|
||||
gen_schedule
|
||||
/etc/init.d/cron restart >/dev/null 2>&1
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
cron_cleanup
|
||||
/etc/init.d/cron restart >/dev/null 2>&1
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "smart-reboot"
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/ucode
|
||||
'use strict';
|
||||
|
||||
import * as uci from "uci";
|
||||
import * as log from "log";
|
||||
import * as fs from "fs";
|
||||
|
||||
const CFG = "smart-reboot";
|
||||
const SECTION = "settings";
|
||||
const LOCK_FILE = "/var/lock/smart-reboot.lock";
|
||||
|
||||
function shquote(s) {
|
||||
return "'" + replace(s, /'/g, "'\\''") + "'";
|
||||
}
|
||||
|
||||
function cmd_out(cmd) {
|
||||
let f = fs.popen(cmd, "r");
|
||||
if (!f)
|
||||
return "";
|
||||
|
||||
let out = f.read("all") ?? "";
|
||||
f.close();
|
||||
return trim(out);
|
||||
}
|
||||
|
||||
let cursor = uci.cursor();
|
||||
|
||||
function cfg_get(key, def) {
|
||||
let val = cursor.get(CFG, SECTION, key);
|
||||
|
||||
if (val == null)
|
||||
return def;
|
||||
|
||||
if (type(val) == "string" && !length(val))
|
||||
return def;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
function logger(msg) {
|
||||
log.syslog(log.LOG_INFO, msg);
|
||||
}
|
||||
|
||||
function set_last_auto_reboot() {
|
||||
let ts = cmd_out("date '+%Y-%m-%d %H:%M:%S'");
|
||||
cursor.set(CFG, SECTION, "last_auto_reboot", ts);
|
||||
cursor.commit(CFG);
|
||||
}
|
||||
|
||||
function list_all_ifaces() {
|
||||
let out = [];
|
||||
for (let path in fs.glob("/sys/class/net/*")) {
|
||||
let name = fs.basename(path);
|
||||
if (name && name != "lo")
|
||||
push(out, name);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function parse_ifaces(raw) {
|
||||
if (!raw)
|
||||
return [];
|
||||
|
||||
if (type(raw) == "array")
|
||||
return raw;
|
||||
|
||||
return filter(split(raw, /[ \t\r\n]+/), v => length(v));
|
||||
}
|
||||
|
||||
function sum_iface_bytes(ifaces) {
|
||||
let total = 0;
|
||||
|
||||
for (let iface in ifaces) {
|
||||
if (!iface)
|
||||
continue;
|
||||
|
||||
let rx = trim(fs.readfile(`/sys/class/net/${iface}/statistics/rx_bytes`) ?? "0");
|
||||
let tx = trim(fs.readfile(`/sys/class/net/${iface}/statistics/tx_bytes`) ?? "0");
|
||||
total += int(rx || "0") + int(tx || "0");
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
function parse_time(time_str) {
|
||||
if (!time_str || index(time_str, ":") < 0)
|
||||
return null;
|
||||
|
||||
let parts = split(time_str, ":");
|
||||
if (length(parts) != 2)
|
||||
return null;
|
||||
|
||||
let hour = int(parts[0]);
|
||||
let minute = int(parts[1]);
|
||||
|
||||
if (hour < 0 || hour > 23 || minute < 0 || minute > 59)
|
||||
return null;
|
||||
|
||||
return { hour, minute };
|
||||
}
|
||||
|
||||
function main() {
|
||||
log.openlog("smart-reboot", log.LOG_PID);
|
||||
|
||||
/*
|
||||
* Acquire an exclusive non-blocking lock on the lock file using the
|
||||
* native ucode fs.file.lock() API instead of shelling out to `lock`. This
|
||||
* avoids spawning a new process on every invocation and is what the
|
||||
* reviewer suggested.
|
||||
*/
|
||||
let lockfd = fs.open(LOCK_FILE, "w+");
|
||||
if (!lockfd || !lockfd.lock("xn"))
|
||||
return 0; /* cannot obtain lock */
|
||||
|
||||
/* helper to release and close the handle */
|
||||
function cleanup() {
|
||||
if (lockfd) {
|
||||
lockfd.lock("u");
|
||||
lockfd.close();
|
||||
lockfd = null;
|
||||
}
|
||||
}
|
||||
|
||||
let enabled = cfg_get("enabled", "0");
|
||||
if (enabled != "1") {
|
||||
cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
let parsed = parse_time(cfg_get("time", ""));
|
||||
let hour = parsed ? parsed.hour : int(cfg_get("hour", "4"));
|
||||
let minute = parsed ? parsed.minute : int(cfg_get("minute", "0"));
|
||||
|
||||
let sample_seconds = int(cfg_get("sample_seconds", "120"));
|
||||
let byte_threshold = int(cfg_get("byte_threshold", "262144"));
|
||||
let all_ifaces = cfg_get("all_ifaces", "0");
|
||||
let ifaces = parse_ifaces(cfg_get("ifaces", []));
|
||||
|
||||
if (all_ifaces == "1") {
|
||||
ifaces = list_all_ifaces();
|
||||
}
|
||||
else if (!length(ifaces)) {
|
||||
logger("No monitored interfaces configured. Set smart-reboot.settings.ifaces or enable all_ifaces.");
|
||||
cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
let now = localtime(time());
|
||||
if (now.hour != hour || now.min != minute) {
|
||||
cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
let start_bytes = sum_iface_bytes(ifaces);
|
||||
/* use built-in sleep (milliseconds) instead of shelling out */
|
||||
sleep(sample_seconds * 1000);
|
||||
let end_bytes = sum_iface_bytes(ifaces);
|
||||
let delta = end_bytes - start_bytes;
|
||||
|
||||
if (delta <= byte_threshold) {
|
||||
logger(`Network idle detected (delta=${delta} bytes, threshold=${byte_threshold}), rebooting now`);
|
||||
set_last_auto_reboot();
|
||||
system("/sbin/reboot");
|
||||
}
|
||||
else {
|
||||
logger(`Skip reboot, network is active (delta=${delta} bytes, threshold=${byte_threshold})`);
|
||||
}
|
||||
|
||||
/* release lock before exiting */
|
||||
cleanup();
|
||||
exit(main());
|
||||
Reference in new issue
Block a user