diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe0445b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +target +.env +tmp diff --git a/.gitignore b/.gitignore index 81c8511..94c0456 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target autotasker.db .env +tmp/ diff --git a/Cargo.lock b/Cargo.lock index 6c6600b..b737816 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ dependencies = [ "getrandom", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -62,9 +62,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -77,33 +77,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -180,7 +180,6 @@ dependencies = [ "argh", "axum", "chrono", - "clap", "env_logger", "fully_pub", "log", @@ -317,15 +316,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" [[package]] name = "cfg-if" @@ -348,38 +347,11 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "clap" -version = "4.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_lex" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" - [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "const-oid" @@ -493,9 +465,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", @@ -503,9 +475,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", @@ -888,9 +860,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown", @@ -898,9 +870,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" @@ -1011,9 +983,9 @@ dependencies = [ [[package]] name = "minijinja" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f7e8e35b6c7b169bf40b0176d2c79291ab8ee53290b84e0668ab21d841aa9d" +checksum = "f4bf71af278c578cbcc91d0b1ff092910208bc86f7b3750364642bd424e3dcd3" dependencies = [ "serde", ] @@ -1035,13 +1007,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1112,21 +1085,11 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" -version = "0.36.1" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] @@ -1242,9 +1205,12 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f" +dependencies = [ + "zerocopy 0.6.6", +] [[package]] name = "proc-macro2" @@ -1420,11 +1386,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1772,12 +1739,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "strum_macros" version = "0.26.4" @@ -1880,21 +1841,20 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.1" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1914,9 +1874,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", @@ -2133,9 +2093,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -2361,13 +2321,34 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "zerocopy" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6" +dependencies = [ + "byteorder", + "zerocopy-derive 0.6.6", +] + [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy-derive" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7cb4a8d..03352f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,13 @@ serde_yaml = "0.9" chrono = { version = "0.4.26", features = ["serde"] } sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "chrono", "uuid"] } anyhow = "1.0.75" -clap = "4.5.4" tokio = { version = "1.37.0", features = ["full"] } axum = { version = "0.7.5", features = ["json"] } minijinja = { version = "2.1", features = ["builtins"] } uuid = { version = "1.8.0", features = ["serde", "v4"] } fully_pub = "0.1.4" log = "0.4.22" -env_logger = "0.11.3" +env_logger = "0.11.5" tower-http = { version = "0.5.2", features = ["fs"] } tokio-cron-scheduler = "0.10.2" argh = "0.1.12" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e3bd639 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM rust:1.80-alpine3.20 as builder +WORKDIR /usr/src/autotasker +COPY . . +RUN apk add musl-dev +RUN cargo install --locked --path . + +FROM alpine:3.20 + +RUN apk add sqlite +COPY --from=builder /usr/local/cargo/bin/autotasker /usr/local/bin/autotasker +RUN mkdir -p /usr/local/src/autotasker/migrations +RUN mkdir -p /usr/local/lib/autotasker/assets +RUN mkdir -p /var/lib/autotasker +RUN mkdir -p /etc/autotasker +COPY --from=builder /usr/src/autotasker/migrations/all.sql /usr/local/src/autotasker/migrations +COPY --from=builder /usr/src/autotasker/init_db.sh /usr/local/bin/autotasker_init_db.sh +COPY --from=builder /usr/src/autotasker/assets /usr/local/lib/autotasker/assets + +USER 1000 +ENV RUST_LOG=info +ENV RUST_BACKTRACE=1 + +CMD ["autotasker", "--listen-host", "127.0.0.1", "--listen-port", "8080"] + diff --git a/README.md b/README.md index 15f77bf..44d899c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Autotasker free software licenced under [GPLv3](https://www.gnu.org/licenses/gpl ## Getting started - Create a config file to define your tasks. -- Deploy the server (using git clone + cargo or docker or binary + systemd service). +- Deploy the server with docker or using git clone + cargo install binary + systemd service. - Enjoy! ## Philosophy @@ -35,9 +35,35 @@ Unix-like - `Task` is a configured command to be run. - `TaskRun` refer to a singular task run. +## Deploy & Usage + +### Standard + +You can use standard rust and cargo command to run the server. +You can start the daemon with `autotasker --config /path/to/config --database /path/to/database.db`. + +### Docker + +Build the alpine image: + + just docker-build + +Initialize the db: + + just docker-init-db + +Run with config and db volume: + + just docker-run + +## Getting started on development + +This project use a `justfile`. [just](https://just.systems/man/en/) is a handy way to save and run project-specific commands. + ## Built with - [Axum](https://lib.rs/crates/axum) for web server. - [Tera](https://lib.rs/crates/tera) for templating. - [Sqlx](https://lib.rs/crates/sqlx) to interact with Sqlite. - [Sqlite](https://sqlite.org/) for storing state and logs. + diff --git a/TODO.md b/TODO.md index bb39c65..8ddce0a 100644 --- a/TODO.md +++ b/TODO.md @@ -3,6 +3,8 @@ ## TODO - [x] Add CSS badge and color code on job status +- [x] Add `Dockerfile` +- [ ] Add cli arg to change listen address and port - [ ] Add tasks timeout - [ ] Add details on runtime - [ ] Implement basic auth with OAuth2, find a minimal oauth2 @@ -11,7 +13,6 @@ - [ ] Don't use long UUID, but only ids - [ ] Validating config file - validate schedule CRON syntax -- [ ] Add `Dockerfile` and docker-compose example - [ ] Add CI/CD to build docker image - [ ] Add configuration options to limit the logs capture head and tail - In a limited mode, we would filter out logs and only keep the head and tail AND Errors or stacktrace diff --git a/init_db.sh b/init_db.sh new file mode 100755 index 0000000..b88016a --- /dev/null +++ b/init_db.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +DEFAULT_DB_PATH="/var/lib/autotasker/autotasker.db" +DEFAULT_MIGRATION_PATH="/usr/local/src/autotasker/migrations/all.sql" + +DB_PATH="${DB_PATH:-$DEFAULT_DB_PATH}" +MIGRATION_PATH="${MIGRATION_PATH:-$DEFAULT_MIGRATION_PATH}" + +sqlite3 $DB_PATH < $MIGRATION_PATH diff --git a/justfile b/justfile index da97f56..0d8d32e 100644 --- a/justfile +++ b/justfile @@ -4,3 +4,13 @@ export RUST_LOG := "trace" watch-run: cargo-watch -x 'run -- --config config.yaml' +docker-run: + docker run -p 3085:8080 -v ./config:/etc/autotasker -v ./db:/var/lib/autotasker autotasker + +docker-init-db: + docker run -v ./config:/etc/autotasker -v ./db:/var/lib/autotasker autotasker /usr/local/bin/autotasker_init_db.sh + +docker-build: + docker build -t autotasker . + + diff --git a/src/main.rs b/src/main.rs index 268f55c..749af09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,21 @@ struct CliFlags { /// path to YAML config file to use to configure autotasker #[argh(option)] config: Option, + + /// path to the Sqlite3 DB file to use + #[argh(option)] + database: Option, + + /// path to the static assets dir + #[argh(option)] + static_assets: Option, + + /// HTTP listen host + #[argh(option, default="String::from(\"localhost\")")] + listen_host: String, + /// HTTP listen port + #[argh(option, default="8085")] + listen_port: u32 } fn get_config(path: String) -> Result { @@ -50,18 +65,20 @@ fn get_config(path: String) -> Result { fn build_templating_env() -> Environment<'static> { let mut templating_env = Environment::new(); - templating_env - .add_template("layouts/base.html", include_str!("./templates/layouts/base.html")) - .unwrap(); - for path in fs::read_dir("./src/templates/pages").unwrap() { - let file_name = path.unwrap().file_name().into_string().unwrap(); - let path = format!("./src/templates/pages/{}", file_name); - let content: &'static str = Box::leak(fs::read_to_string(&path).unwrap().into_boxed_str()); - let path: &'static str = Box::leak(format!("pages/{}", file_name).into_boxed_str()); - templating_env - .add_template(path, content) - .unwrap(); - } + let _ = templating_env + .add_template("layouts/base.html", include_str!("./templates/layouts/base.html")); + let _ = templating_env + .add_template("pages/home.html", include_str!("./templates/pages/home.html")); + let _ = templating_env + .add_template("pages/list_tasks.html", include_str!("./templates/pages/list_tasks.html")); + let _ = templating_env + .add_template("pages/list_task_runs.html", include_str!("./templates/pages/list_task_runs.html")); + let _ = templating_env + .add_template("pages/task_run_details.html", include_str!("./templates/pages/task_run_details.html")); + let _ = templating_env + .add_template("pages/run_task.html", include_str!("./templates/pages/run_task.html")); + + // TODO: better loading with embed https://docs.rs/minijinja-embed/latest/minijinja_embed/ templating_env.add_global("gl", context! { instance => context! { version => "1.243".to_string() @@ -70,13 +87,19 @@ fn build_templating_env() -> Environment<'static> { templating_env } +const DEFAULT_DB_PATH: &'static str = &"/var/lib/autotasker/autotasker.db"; +const DEFAULT_ASSETS_PATH: &'static str = &"/usr/local/lib/autotasker/assets"; +const DEFAULT_CONFIG_PATH: &'static str = &"/etc/autotasker/config.yaml"; + #[tokio::main] async fn main() -> Result<()> { let flags: CliFlags = argh::from_env(); env_logger::init(); info!("Starting autotasker"); - let pool = prepare_database().await.context("Prepare db")?; + let pool = prepare_database( + &flags.database.unwrap_or(DEFAULT_DB_PATH.to_string()) + ).await.context("Could not prepare db")?; // start channel to talk to executor daemon let (executor_tx, executor_rx) = mpsc::channel::(32); @@ -85,7 +108,7 @@ async fn main() -> Result<()> { let config_path = match flags.config { Some(v) => v, - None => "/etc/autotasker/config.yaml".to_string() + None => DEFAULT_CONFIG_PATH.to_string() }; info!("Using config file at {}", &config_path); let config: Config = get_config(config_path).expect("Cannot get config"); @@ -125,10 +148,10 @@ async fn main() -> Result<()> { get(controllers::get_task_run), ) .route("/webhooks/:id/:token", get(controllers::handle_webhook)) - .nest_service("/assets", ServeDir::new("./assets")) + .nest_service("/assets", ServeDir::new(flags.static_assets.unwrap_or(DEFAULT_ASSETS_PATH.to_string()))) .with_state(state); - let listen_addr = "0.0.0.0:8085"; + let listen_addr = format!("{}:{}", flags.listen_host, flags.listen_port); info!("Starting web server on http://{}", &listen_addr); let listener = tokio::net::TcpListener::bind(listen_addr).await.unwrap(); axum::serve(listener, app).await?; @@ -138,13 +161,13 @@ async fn main() -> Result<()> { Ok(()) } -async fn prepare_database() -> Result> { - let conn_str = "sqlite:./tmp/dbs/autotasker.db"; +async fn prepare_database(sqlite_db_path: &str) -> Result> { + let conn_str = format!("sqlite:{}", sqlite_db_path); let pool = SqlitePoolOptions::new() .max_connections(50) .connect_with( - SqliteConnectOptions::from_str(conn_str)? + SqliteConnectOptions::from_str(&conn_str)? .log_statements(log::LevelFilter::Trace) ) .await