From 50cdd3115f37a0170f2ff1c26b7b3f2468b62a7c Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 6 Jun 2023 20:49:00 +0200 Subject: [PATCH] feat: solely handle single terminating signal for now --- .dockerignore | 5 ++++ CHANGELOG.md | 8 +++++++ Dockerfile | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 14 +++++------ src/signals.rs | 33 +++++++++++++++++--------- src/stdin.rs | 2 +- 6 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1917669 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +* + +!Cargo.toml +!Cargo.lock +!src/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b969cb..4339669 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://git.rustybever.be/Chewing_Bever/alex/src/branch/dev) +### Added + +* Rudimentary signal handling for gently stopping server + * A single stop signal will trigger the Java process to shut down, but Alex + still expects to be run from a utility such as dumb-init +* Properly back up entire config directory +* Inject Java optimisation flags + ## [0.1.1](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.1.1) ### Changed diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cb3b9e0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,64 @@ +FROM rust:1.70-alpine3.18 AS builder + +ARG DI_VER=1.2.5 + +WORKDIR /app + +COPY . ./ + +RUN apk add --no-cache build-base unzip curl && \ + curl -Lo - "https://github.com/Yelp/dumb-init/archive/refs/tags/v${DI_VER}.tar.gz" | tar -xzf - && \ + cd "dumb-init-${DI_VER}" && \ + make SHELL=/bin/sh && \ + mv dumb-init .. + +RUN cargo build && \ + [ "$(readelf -d target/debug/alex | grep NEEDED | wc -l)" = 0 ] + + +# We use ${:-} instead of a default value because the argument is always passed +# to the build, it'll just be blank most likely +FROM eclipse-temurin:17-jre-alpine + +# Build arguments +ARG MC_VERSION=1.19.4 +ARG PAPERMC_VERSION=545 + +RUN addgroup -Sg 1000 paper && \ + adduser -SHG paper -u 1000 paper + +# Create worlds and config directory +WORKDIR /app +RUN mkdir -p worlds config/cache backups + +# Download server file +ADD "https://papermc.io/api/v2/projects/paper/versions/$MC_VERSION/builds/$PAPERMC_VERSION/downloads/paper-$MC_VERSION-$PAPERMC_VERSION.jar" server.jar + +# Make sure the server user can access all necessary folders +RUN chown -R paper:paper /app + +# Store the cache in an anonymous volume, which means it won't get stored in the other volumes +VOLUME /app/config/cache +VOLUME /app/backups + +COPY --from=builder /app/dumb-init /bin/dumb-init +COPY --from=builder /app/target/debug/alex /bin/alex + +RUN chmod +x /bin/alex + +# Default value to keep users from eating up all ram accidentally +ENV ALEX_XMS=1024 \ + ALEX_XMX=2048 \ + ALEX_JAR=/app/server.jar \ + ALEX_CONFIG_DIR=/app/config \ + ALEX_WORLD_DIR=/app/worlds \ + ALEX_BACKUPS_DIR=/app/backups \ + ALEX_SERVER_VERSION="${MC_VERSION}-${PAPERMC_VERSION}" + +# Document exposed ports +EXPOSE 25565 + +# Switch to non-root user +USER paper:paper + +ENTRYPOINT ["/bin/alex", "paper"] diff --git a/src/main.rs b/src/main.rs index 1c083fa..9f993a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,7 +82,7 @@ fn backups_thread(counter: Arc>, frequency: u64) { } fn main() -> io::Result<()> { - let (term, mut signals) = signals::install_signal_handlers()?; + let (_, mut signals) = signals::install_signal_handlers()?; let cli = Cli::parse(); let cmd = server::ServerCommand::new(cli.type_, &cli.server_version) @@ -94,17 +94,17 @@ fn main() -> io::Result<()> { .xms(cli.xms) .xmx(cli.xmx) .max_backups(cli.max_backups); - let server = Arc::new(cmd.spawn()?); + let counter = Arc::new(Mutex::new(cmd.spawn()?)); if cli.frequency > 0 { - let clone = Arc::clone(&server); - std::thread::spawn(move || backups_thread(server, cli.frequency)); + let clone = Arc::clone(&counter); + std::thread::spawn(move || backups_thread(clone, cli.frequency)); } // Spawn thread that handles the main stdin loop - let clone = Arc::clone(&server); - std::thread::spawn(move || stdin::handle_stdin(server)); + let clone = Arc::clone(&counter); + std::thread::spawn(move || stdin::handle_stdin(clone)); // Signal handler loop exits the process when necessary - signals::handle_signals(&mut signals, term, server) + signals::handle_signals(&mut signals, counter) } diff --git a/src/signals.rs b/src/signals.rs index a173047..61c38f9 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -1,6 +1,6 @@ use std::io; +use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; -use std::sync::atomic::{AtomicBool, Ordering}; use signal_hook::consts::TERM_SIGNALS; use signal_hook::flag; @@ -16,12 +16,15 @@ pub fn install_signal_handlers() -> io::Result<(Arc, SignalsInfo)> { // atomic bool. With this, the process will get killed immediately once it receives a second // termination signal (e.g. a double ctrl-c). // https://docs.rs/signal-hook/0.3.15/signal_hook/#a-complex-signal-handling-with-a-background-thread - // for sig in TERM_SIGNALS { - // // But this will "arm" the above for the second time, by setting it to true. - // // The order of registering these is important, if you put this one first, it will - // // first arm and then terminate ‒ all in the first round. - // flag::register(*sig, Arc::clone(&term))?; - // } + for sig in TERM_SIGNALS { + // When terminated by a second term signal, exit with exit code 1. + // This will do nothing the first time (because term_now is false). + flag::register_conditional_shutdown(*sig, 1, Arc::clone(&term))?; + // But this will "arm" the above for the second time, by setting it to true. + // The order of registering these is important, if you put this one first, it will + // first arm and then terminate ‒ all in the first round. + flag::register(*sig, Arc::clone(&term))?; + } let signals = TERM_SIGNALS; @@ -29,7 +32,10 @@ pub fn install_signal_handlers() -> io::Result<(Arc, SignalsInfo)> { } /// Loop that handles terminating signals as they come in. -pub fn handle_signals(signals: &mut SignalsInfo, term: Arc, server: Arc) -> io::Result<()> { +pub fn handle_signals( + signals: &mut SignalsInfo, + counter: Arc>, +) -> io::Result<()> { let mut force = false; // We only register terminating signals, so we don't need to differentiate between what kind of @@ -37,15 +43,20 @@ pub fn handle_signals(signals: &mut SignalsInfo, term: Arc, server: for _ in signals { // If term is already true, this is the second signal, meaning we kill the process // immediately. + // This will currently not work, as the initial stop command will block the kill from + // happening. if force { - return server.kill() + let mut server = counter.lock().unwrap(); + return server.kill(); } // The stop command runs in a separate thread to avoid blocking the signal handling loop. // After stopping the server, the thread terminates the process. else { - let clone = Arc::clone(&server); + let clone = Arc::clone(&counter); + std::thread::spawn(move || { - let _ = clone.stop(); + let mut server = clone.lock().unwrap(); + let _ = server.stop(); std::process::exit(0); }); } diff --git a/src/stdin.rs b/src/stdin.rs index 98d8463..f9a22e8 100644 --- a/src/stdin.rs +++ b/src/stdin.rs @@ -1,5 +1,5 @@ -use std::sync::{Arc, Mutex}; use std::io; +use std::sync::{Arc, Mutex}; use crate::server;