using Dates using Printf # Try to load HTTP; if not installed, give instruction and exit try @eval using HTTP catch println("HTTP.jl not found. Install it with: julia -e 'using Pkg; Pkg.add(\"HTTP\")'") exit(1) end # Configuration const URL = "https://www.xxx.cc" const TIMEOUT_SECS = 6 # request timeout const ATTEMPTS_PER_CHECK = 3 # number of HTTP attempts per check const BACKOFF_BETWEEN_ATTEMPTS = 2 # seconds between attempts const FAILS_TO_REBOOT = 3 # consecutive failed checks required to trigger reboot const COOLDOWN_AFTER_REBOOT_SECS = 600 # do not reboot again within this many seconds const DRY_RUN = true # set false to actually reboot (DRY RUN true for testing) const LOGFILE = "./check_website_reboot_log.txt" # write logs here and also broadcast const CHECK_INTERVAL_SECS = 60 # run a check every CHECK_INTERVAL_SECS seconds # Persist state in current directory as requested const STATE_FILE = "./check_and_reboot_state.json" # Simple broadcast helper (safe Cmd construction) function broadcast_msg(msg::AbstractString) try if Sys.islinux() # Try wall if available by writing to its stdin wall_paths = ("/usr/bin/wall", "/bin/wall") for p in wall_paths if isfile(p) try proc = open(`$p`, "w") try write(proc, msg * "\n") finally close(proc) end return true catch # ignore and try next end end end # Fallback to logger (safe arg passing) try run(Cmd(["logger", msg])) return true catch end elseif Sys.isapple() # Use AppleScript notification as a fallback (escape double quotes) try escaped = replace(msg, "\"" => "\\\"") applescript = "display notification \"" * escaped * "\" with title \"check_and_reboot\"" run(Cmd(["osascript", "-e", applescript])) return true catch end elseif Sys.iswindows() # Try msg to all sessions (may require privileges); best-effort try run(Cmd(["msg", "*", msg])) return true catch end end catch # swallow any unexpected errors end return false end # Simple logging (prints, appends to LOGFILE, and broadcasts) function logmsg(s::AbstractString) t = Dates.format(now(), "yyyy-mm-dd HH:MM:SS") out = "[$t] $s" # write to logfile (append) try open(LOGFILE, "a") do io println(io, out) end catch e # If logfile write fails, fallback to stdout println("[$t] (log write failed: $e) $s") end # Also print to stdout for immediate console visibility println(out) # Best-effort system broadcast so operators on console see it try broadcast_msg(out) catch # ignore broadcast failures end end # Minimal JSON helper (uses JSON.jl if available, otherwise a tiny fallback) module JSONHelper export parse_obj, write_obj function parse_obj(s::String) try @eval using JSON return JSON.parse(s) catch try return eval(Meta.parse(replace(s, "null"=>"nothing"))) catch return Dict{String,Any}() end end end function write_obj(io, obj::Dict) try @eval using JSON JSON.print(io, obj) catch # naive serializer for simple dict of numbers/strings print(io, "{") first = true for (k,v) in obj if !first; print(io, ","); end first = false if isa(v, String) # escape quotes and backslashes minimally esc = replace(replace(v, "\\"=>"\\\\") , "\"" => "\\\"") print(io, "\"$k\":\"$esc\"") else print(io, "\"$k\":$v") end end print(io, "}") end end end # State handling: store last_reboot as a DateTime mutable struct State consecutive_fails::Int last_reboot::DateTime end # Default epoch for "never rebooted" state const NEVER_REBOOTED = DateTime(1970,1,1) function load_state() try if isfile(STATE_FILE) s = read(STATE_FILE, String) obj = JSONHelper.parse_obj(s) cf = haskey(obj, "consecutive_fails") ? Int(obj["consecutive_fails"]) : 0 lr = NEVER_REBOOTED if haskey(obj, "last_reboot") && isa(obj["last_reboot"], String) try lr = DateTime(obj["last_reboot"]) # expects ISO-like string catch # ignore parse error, keep NEVER_REBOOTED end end return State(cf, lr) end catch e logmsg("Warning loading state: $e") end return State(0, NEVER_REBOOTED) end # Atomic save_state to avoid partial/corrupted state across reboots function save_state(st::State) # write ISO-8601 string for DateTime lr_str = Dates.format(st.last_reboot, Dates.ISODateTime) obj = Dict("consecutive_fails" => st.consecutive_fails, "last_reboot" => lr_str) tmp = STATE_FILE * ".tmp" try open(tmp, "w") do io JSONHelper.write_obj(io, obj) end mv(tmp, STATE_FILE; force=true) catch e logmsg("Warning: failed to write/replace state file: $e") # attempt best-effort cleanup try isfile(tmp) && rm(tmp) catch end end end # Helper: system uptime on Linux (seconds), Inf on other OS or error function system_uptime_seconds() try if Sys.islinux() s = read("/proc/uptime", String) return parse(Float64, split(s)[1]) end catch end return Inf end # HTTP check function check_url_once(url::AbstractString; timeout=TIMEOUT_SECS) try resp = HTTP.request("GET", url; connect_timeout=timeout, read_timeout=timeout) return 200 <= resp.status < 400, resp.status catch e return false, nothing end end # Reboot command selection # Return a tuple of strings (program and args) suitable for constructing Cmd function reboot_command() if Sys.iswindows() return ("/usr/bin/cmd", "/C", "shutdown /r /t 0") elseif Sys.isapple() return ("/usr/bin/sudo", "shutdown", "-r", "now") elseif Sys.islinux() if isfile("/bin/systemctl") || isfile("/usr/bin/systemctl") return ("/usr/bin/sudo", "systemctl", "reboot") else return ("/usr/bin/sudo", "reboot") end else return nothing end end function do_reboot() cmd = reboot_command() if cmd === nothing logmsg("Reboot not supported on this OS") return false end # Build a readable command string for logs (escape each arg safely) cmd_str = join(map(x -> replace(x, '"' => "\\\""), cmd), " ") if DRY_RUN logmsg("DRY RUN: would run reboot command: $cmd_str") return true end logmsg("Executing reboot command: $cmd_str") try # Construct a Cmd from an array so arguments are passed directly (no shell) cmd_array = collect(cmd) # Tuple{String,...} -> Vector{String} run(Cmd(cmd_array)) return true catch e logmsg("Failed to execute reboot command: $e") return false end end # Single check iteration function perform_check!(st::State) # If we're still within cooldown after a reboot, skip checks if st.last_reboot != NEVER_REBOOTED elapsed = now() - st.last_reboot if elapsed < Second(COOLDOWN_AFTER_REBOOT_SECS) remaining = Int(clamp(round((Second(COOLDOWN_AFTER_REBOOT_SECS) - elapsed).value), 0, typemax(Int))) logmsg("In cooldown after recent reboot; skipping check for $remaining more seconds.") return end end # Boot grace: skip checks if system just booted (helps prevent immediate reboots while services settle) upt = system_uptime_seconds() if Sys.islinux() && upt < 120 logmsg("System boot grace active (uptime=$(round(upt))s); skipping check until uptime >= 120s.") return end success = false last_code = nothing for i in 1:ATTEMPTS_PER_CHECK ok, code = check_url_once(URL) last_code = code if ok success = true break end sleep(BACKOFF_BETWEEN_ATTEMPTS) end if success if st.consecutive_fails > 0 logmsg("Website reachable; resetting consecutive failure counter.") else logmsg("Website reachable.") end st.consecutive_fails = 0 save_state(st) return else st.consecutive_fails += 1 logmsg(@sprintf("Website unreachable (last HTTP status: %s). Consecutive fails: %d/%d.", isnothing(last_code) ? "no response" : string(last_code), st.consecutive_fails, FAILS_TO_REBOOT)) save_state(st) end if st.consecutive_fails >= FAILS_TO_REBOOT st.last_reboot = now() save_state(st) ok = do_reboot() if ok logmsg("Reboot executed (or simulated). Resetting failure counter.") st.consecutive_fails = 0 save_state(st) else logmsg("Reboot attempt failed; will retry after next interval.") end end end # Main loop: runs indefinitely every CHECK_INTERVAL_SECS function main_loop() logmsg("Starting check loop. Checking every $(CHECK_INTERVAL_SECS) seconds.") logmsg("STATE_FILE path: $(abspath(STATE_FILE))") st = load_state() # Log loaded state for visibility age = st.last_reboot == NEVER_REBOOTED ? "never" : string(Int(round((now() - st.last_reboot).value))) lr_str = st.last_reboot == NEVER_REBOOTED ? "never" : Dates.format(st.last_reboot, Dates.ISODateTime) logmsg("Loaded state: consecutive_fails=$(st.consecutive_fails) last_reboot=$(lr_str) (age=${age}s)") while true try perform_check!(st) catch e logmsg("Error during check: $e") end sleep(CHECK_INTERVAL_SECS) end end # Run main_loop()