1#![allow(rustdoc::private_intra_doc_links)]
8
9use anyhow::Result;
10use customlog::{ConsoleLogger, FileLogger, FlushGuard, RotateOptions, RotateSize};
11use daemonize::Daemonize;
12use getopts::Options;
13use log::{LevelFilter, Log, error, info};
14use std::env;
15use std::fs::File;
16use std::io::{BufWriter, Write};
17use std::os::unix::prelude::*;
18use sys::sysmod::SystemModules;
19use sys::taskserver::{Control, RunResult, TaskServer};
20
21const FILE_STDOUT: &str = "stdout.txt";
23const FILE_STDERR: &str = "stderr.txt";
25const FILE_EXEC_SH: &str = "exec.sh";
27const FILE_KILL_SH: &str = "kill.sh";
29const FILE_FLUSH_SH: &str = "flushlog.sh";
31const FILE_CRON: &str = "cron.txt";
33const FILE_PID: &str = "shanghai.pid";
35const FILE_LOG: &str = "shanghai.log";
37
38const LOG_FILTER: &[&str] = &[module_path!(), "sys"];
39const LOG_ROTATE_SIZE: usize = 1024 * 1024;
40const LOG_ROTATE_COUNT: u16 = 10;
41const LOG_BUF_SIZE: usize = 64 * 1024;
42
43fn daemon() {
49 let stdout = match File::create(FILE_STDOUT) {
50 Ok(f) => f,
51 Err(e) => {
52 eprintln!("Open {FILE_STDOUT} error: {e}");
53 std::process::exit(1);
54 }
55 };
56 let stderr = match File::create(FILE_STDERR) {
57 Ok(f) => f,
58 Err(e) => {
59 eprintln!("Open {FILE_STDERR} error: {e}");
60 std::process::exit(1);
61 }
62 };
63
64 let daemonize = Daemonize::new()
65 .pid_file(FILE_PID)
66 .working_directory(".")
68 .stdout(stdout)
71 .stderr(stderr);
72
73 if let Err(e) = daemonize.start() {
74 eprintln!("Daemonize error: {e}");
75 std::process::exit(1);
76 }
77}
78
79fn log_target_filter(target: &str) -> bool {
80 LOG_FILTER.iter().any(|filter| target.starts_with(filter))
81}
82
83fn init_log(is_daemon: bool) -> FlushGuard {
93 let rotate_opts = RotateOptions {
95 size: RotateSize::Enabled(LOG_ROTATE_SIZE),
96 file_count: LOG_ROTATE_COUNT,
97 ..Default::default()
98 };
99 let file_log = FileLogger::new_boxed(
100 LevelFilter::Info,
101 log_target_filter,
102 customlog::default_formatter,
103 FILE_LOG,
104 LOG_BUF_SIZE,
105 rotate_opts,
106 )
107 .expect("Log file open failed");
108
109 let loggers: Vec<Box<dyn Log>> = if is_daemon {
110 vec![file_log]
111 } else {
112 let console_log = ConsoleLogger::new_boxed(
113 customlog::Console::Stdout,
114 LevelFilter::Trace,
115 log_target_filter,
116 customlog::default_formatter,
117 );
118 vec![console_log, file_log]
119 };
120 customlog::init(loggers, LevelFilter::Trace)
121}
122
123async fn boot_msg_task(ctrl: Control) -> Result<()> {
125 let build_info = verinfo::version_info();
126 let now = chrono::Local::now();
128 let now = now.format("%F %T %:z");
129 let msg = format!("[{now}] Boot...\n{build_info}");
130
131 {
132 let mut twitter = ctrl.sysmods().twitter.lock().await;
133 if let Err(why) = twitter.tweet(&msg).await {
134 error!("error on tweet");
135 error!("{why:#?}");
136 }
137 }
138 {
139 let mut discord = ctrl.sysmods().discord.lock().await;
140 if let Err(why) = discord.say(&msg).await {
141 error!("error on discord notification");
142 error!("{why:#?}");
143 }
144 }
145
146 Ok(())
147}
148
149fn system_main() -> Result<()> {
158 let sigusr1 = || {
159 info!("Flush log");
160 log::logger().flush();
161 None
162 };
163 let sigusr2 = || None;
164
165 loop {
166 info!("system main");
167 info!("{}", verinfo::version_info());
168 log::logger().flush();
169
170 sys::config::load()?;
171
172 let sysmods = SystemModules::new()?;
173 let ts = TaskServer::new(sysmods);
174
175 ts.spawn_oneshot_task("boot_msg", boot_msg_task);
176 let run_result = ts.run(sigusr1, sigusr2);
177
178 info!("task server dropped");
179
180 match run_result {
181 RunResult::Shutdown => {
182 info!("result: shutdown");
183 break;
184 }
185 RunResult::Reboot => {
186 info!("result: reboot");
187 }
188 }
189 }
190
191 Ok(())
192}
193
194fn create_sh(path: &str) -> Result<File> {
196 let f = File::create(path)?;
197
198 let mut perm = f.metadata()?.permissions();
199 perm.set_mode(0o755);
200 f.set_permissions(perm)?;
201
202 Ok(f)
203}
204
205fn create_run_script() -> Result<()> {
209 let exe = env::current_exe()?.to_string_lossy().to_string();
210 let cd = env::current_dir()?.to_string_lossy().to_string();
211
212 {
213 let f = create_sh(FILE_EXEC_SH)?;
214 let mut w = BufWriter::new(f);
215
216 writeln!(&mut w, "#!/bin/bash")?;
217 writeln!(&mut w, "set -euxo pipefail")?;
218 writeln!(&mut w)?;
219 writeln!(&mut w, "cd '{cd}'")?;
220 writeln!(&mut w, "'{exe}' --daemon")?;
221 }
222 {
223 let f = create_sh(FILE_KILL_SH)?;
224 let mut w = BufWriter::new(f);
225
226 writeln!(&mut w, "#!/bin/bash")?;
227 writeln!(&mut w, "set -euxo pipefail")?;
228 writeln!(&mut w)?;
229 writeln!(&mut w, "cd '{cd}'")?;
230 writeln!(&mut w, "kill `cat {FILE_PID}`")?;
231 }
232 {
233 let f = create_sh(FILE_FLUSH_SH)?;
234 let mut w = BufWriter::new(f);
235
236 writeln!(&mut w, "#!/bin/bash")?;
237 writeln!(&mut w, "set -euxo pipefail")?;
238 writeln!(&mut w)?;
239 writeln!(&mut w, "cd '{cd}'")?;
240 writeln!(&mut w, "kill -SIGUSR1 `cat {FILE_PID}`")?;
241 }
242 {
243 let f = File::create(FILE_CRON)?;
244 let mut w = BufWriter::new(f);
245
246 write!(
247 &mut w,
248 "# How to use
249# $ crontab < {FILE_CRON}
250# How to verify
251# $ crontab -l
252
253# workaround: wait for 30 sec to wait for network
254# It seems that DNS fails just after reboot
255
256@reboot sleep 30; cd {cd}; ./{FILE_EXEC_SH}
257"
258 )?;
259 }
260
261 Ok(())
262}
263
264fn print_help(program: &str, opts: Options) {
269 let brief = format!("Usage: {program} [options]");
270 print!("{}", opts.usage(&brief));
271}
272
273pub fn main() -> Result<()> {
277 create_run_script()?;
278
279 let args: Vec<String> = env::args().collect();
281 let program = &args[0];
282
283 let mut opts = Options::new();
284 opts.optflag("h", "help", "Print this help");
285 opts.optflag("d", "daemon", "Run as daemon");
286 let matches = match opts.parse(&args[1..]) {
287 Ok(m) => m,
288 Err(fail) => {
289 eprintln!("{fail}");
290 std::process::exit(1);
291 }
292 };
293
294 if matches.opt_present("h") {
296 print_help(program, opts);
297 std::process::exit(0);
298 }
299
300 let _flush = if matches.opt_present("d") {
301 daemon();
302 init_log(true)
303 } else {
304 init_log(false)
305 };
306
307 system_main().map_err(|e| {
308 error!("Error in system_main");
309 error!("{e:#}");
310 e
311 })
312
313 }