Initial commit
This commit is contained in:
commit
ad731f1793
93
.gitignore
vendored
Normal file
93
.gitignore
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
|
||||
### CLion+iml ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### CLion+iml Patch ###
|
||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||
|
||||
*.iml
|
||||
modules.xml
|
||||
.idea/misc.xml
|
||||
*.ipr
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/clion+iml
|
||||
|
||||
/target
|
||||
/minitd.toml
|
||||
/*.log
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
771
Cargo.lock
generated
Normal file
771
Cargo.lock
generated
Normal file
@ -0,0 +1,771 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base-x"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_fn"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "daemonize"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "discard"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graph-cycles"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6ad932c6dd3cfaf16b66754a42f87bbeefd591530c4b6a8334270a7df3e853"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"petgraph",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "hhmmss"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11a3a7d0916cb01ef108a66108640419767991ea31d11a1c851bed37686a6062"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minitd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"daemonize",
|
||||
"graph-cycles",
|
||||
"hhmmss",
|
||||
"libc",
|
||||
"nix",
|
||||
"petgraph",
|
||||
"serde",
|
||||
"shlex",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.20+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.195"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.195"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
|
||||
dependencies = [
|
||||
"sha1_smol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1_smol"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "standback"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stdweb"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
|
||||
dependencies = [
|
||||
"discard",
|
||||
"rustc_version",
|
||||
"stdweb-derive",
|
||||
"stdweb-internal-macros",
|
||||
"stdweb-internal-runtime",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stdweb-derive"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stdweb-internal-macros"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
|
||||
dependencies = [
|
||||
"base-x",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stdweb-internal-runtime"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
|
||||
dependencies = [
|
||||
"const_fn",
|
||||
"libc",
|
||||
"standback",
|
||||
"stdweb",
|
||||
"time-macros",
|
||||
"version_check",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"time-macros-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros-impl"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"standback",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "minitd"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nix = { version = "0.27.1", features = ["fs", "process", "event", "signal", "poll", "net"] }
|
||||
libc = "0.2.152"
|
||||
daemonize = "0.5.0"
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.8.8"
|
||||
shlex = "1.3.0"
|
||||
|
||||
petgraph = "0.6.4"
|
||||
graph-cycles = "0.1.0"
|
||||
|
||||
base64 = "0.21.7"
|
||||
hhmmss = "0.1.0"
|
68
src/config.rs
Normal file
68
src/config.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use graph_cycles::Cycles;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub service: Vec<CService>,
|
||||
|
||||
#[serde(default)]
|
||||
pub autostart: Vec<String>
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CService {
|
||||
pub name: String,
|
||||
pub command: String,
|
||||
pub directory: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub depends_on: Vec<String>
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Config, String> {
|
||||
let data = std::fs::read_to_string("minitd.toml")
|
||||
.map_err(|err| format!("Failed to read config file: {err}"))?;
|
||||
let config = toml::from_str::<Config>(&data)
|
||||
.map_err(|err| format!("Failed to parse config: {err}"))?;
|
||||
|
||||
let mut graph = petgraph::Graph::new();
|
||||
let mut node_map = std::collections::HashMap::new();
|
||||
for service in &config.service {
|
||||
let node = graph.add_node(&service.name);
|
||||
node_map.insert(service.name.clone(), node);
|
||||
}
|
||||
for service in &config.service {
|
||||
let my_id = node_map.get(&service.name).unwrap();
|
||||
for dep in &service.depends_on {
|
||||
let other_id = match node_map.get(dep) {
|
||||
Some(v) => v,
|
||||
None => return Err(format!("Service '{}' depends on unknown service '{}'", service.name, dep))
|
||||
};
|
||||
graph.add_edge(*my_id, *other_id, ());
|
||||
}
|
||||
}
|
||||
let cycles = graph.cycles();
|
||||
if !cycles.is_empty() {
|
||||
let mut msg = String::new();
|
||||
for cycle in cycles {
|
||||
msg += "Dependency cycle: ";
|
||||
for node in &cycle {
|
||||
msg += graph.node_weight(*node).unwrap();
|
||||
msg += " -> ";
|
||||
}
|
||||
msg += graph.node_weight(cycle[0]).unwrap();
|
||||
msg += "\n";
|
||||
}
|
||||
return Err(msg);
|
||||
}
|
||||
|
||||
for auto in &config.autostart {
|
||||
if config.service.iter().find(|item| item.name == *auto).is_none() {
|
||||
return Err(format!("autostart: unkown service '{auto}'"));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
}
|
130
src/html.rs
Normal file
130
src/html.rs
Normal file
@ -0,0 +1,130 @@
|
||||
use std::io::Write;
|
||||
use std::net::TcpStream;
|
||||
use std::os::fd::AsRawFd;
|
||||
use base64::Engine;
|
||||
use hhmmss::Hhmmss;
|
||||
use crate::{Data, service};
|
||||
|
||||
fn index_write_service(stream: &mut impl Write, service: &crate::service::Service) -> Option<()> {
|
||||
let (state, desc, can_start, can_stop) = match &service.status {
|
||||
crate::service::ServiceStatus::STOPPED => ("stopped", "".to_string(), true, false),
|
||||
crate::service::ServiceStatus::STARTED(started_at) =>
|
||||
("started", format!("Pid: {}, uptime: {}", service.pid.as_ref().map_or(-1, |v| v.0.as_raw()), started_at.elapsed().hhmmss()), false, true),
|
||||
crate::service::ServiceStatus::STOPPING => ("stopping", "".to_string(), false, false),
|
||||
crate::service::ServiceStatus::FAILED(err) => ("failed", err.clone(), true, false)
|
||||
};
|
||||
write!(stream, "<tr><td data-status=\"{}\"></td><td>{}</td><td>{}</td><td>", state, &service.config.name, desc).ok()?;
|
||||
if can_start { write!(stream, "<a href=\"/start/{}\">Start</a>", &service.config.name).ok()?; }
|
||||
stream.write_all(b"</td><td>").ok()?;
|
||||
if can_stop { write!(stream, "<a href=\"/stop/{}\">Stop</a>", &service.config.name).ok()?; }
|
||||
write!(stream, "</td><td><a href=\"/tail#{}\">Tail log</a></td></tr>", &service.config.name).ok()
|
||||
}
|
||||
|
||||
pub fn send_404(mut stream: TcpStream) -> Option<()> {
|
||||
stream.write_all(b"HTTP/1.0 404 Not found\r\nContent-Type: text/plain;charset=utf-8\r\n\r\n404 Not found").ok()
|
||||
}
|
||||
|
||||
pub fn send_301(mut stream: TcpStream) -> Option<()> {
|
||||
stream.write_all(b"HTTP/1.0 302 Found\r\nLocation: /\r\n\r\n").ok()
|
||||
}
|
||||
|
||||
pub fn send_index(stream: TcpStream) -> Option<()> {
|
||||
let mut stream = std::io::BufWriter::new(stream);
|
||||
stream.write_all(b"HTTP/1.0 200 OK\r\nContent-Type: text/html;charset=utf-8\r\n\r\n").ok()?;
|
||||
stream.write_all(b"<!DOCTYPE html><html lang=\"en\"><head><title>Minitd</title><style>
|
||||
.center-child { display: flex; flex-direction: column; align-items: center; row-gap: 2em; background-color: #aaffaa44; }
|
||||
.space-child > * { margin: 0 0.75em; }
|
||||
a, a:link, a:visited { color: #444; }
|
||||
a:hover { color: #000; }
|
||||
table { border: 1px solid #999; }
|
||||
thead { color: #fff; background-color: #999; }
|
||||
td, th { padding: 0.25em 0.5em; border-bottom: 1px solid #fff; }
|
||||
tbody > tr:nth-child(even) { background-color: #eee; }
|
||||
tbody > tr:nth-child(odd) { background-color: #ddd; }
|
||||
[data-status] { padding: 0.125em 0.25em; }
|
||||
[data-status]::after { padding: 0.125em 0.25em; }
|
||||
[data-status='stopped']::after { content: 'Stopped'; background-color: #ef8; }
|
||||
[data-status='started']::after { content: 'Started'; background-color: #8f8; }
|
||||
[data-status='stopping']::after { content: 'Stopping'; background-color: #fc8; }
|
||||
[data-status='failed']::after { content: 'Failed'; background-color: #faa; }
|
||||
</style></head><body class=\"center-child\">").ok()?;
|
||||
stream.write_all(b"<div class=\"space-child\"><a href=\".\">Refresh</a><a href=\"/tail#minitd\">Tail log</a><a href=\"/reload\">Reload</a><a href=\"/stop_all\">Stop all</a><a href=\"/shutdown\">Shutdown</a></div>").ok()?;
|
||||
stream.write_all(b"<table cellspacing=\"0\"><thead><th>State</th><th>Name</th><th>Description</th><th colspan=\"3\">Action</th></thead><tbody>").ok()?;
|
||||
for (_, service) in &crate::Data::get().services {
|
||||
index_write_service(&mut stream, service)?;
|
||||
}
|
||||
stream.write_all(b"</tbody></table></body></html>").ok()
|
||||
}
|
||||
|
||||
pub fn send_tail_html(stream: TcpStream) -> Option<()> {
|
||||
let mut stream = std::io::BufWriter::new(stream);
|
||||
stream.write_all(b"HTTP/1.0 200 OK\r\nContent-Type: text/html;charset=utf-8\r\n\r\n").ok()?;
|
||||
stream.write_all(b"<!DOCTYPE html><html lang=\"en\"><head><title>Minitd</title></head><body><pre id=\"c\"></pre><script>").ok()?;
|
||||
stream.write_all(b"const c = document.getElementById('c'); const e = new EventSource('/_tail/' + location.hash.slice(1)); e.onerror = _ => e.close(); e.addEventListener('line', msg => c.innerText += atob(msg.data));").ok()?;
|
||||
stream.write_all(b"</script></body></html>").ok()
|
||||
}
|
||||
|
||||
pub fn send_tail(mut stream: TcpStream, file: String) -> Option<()> {
|
||||
let mut b64_buf = [0_u8; 2048];
|
||||
stream.write_all(b"HTTP/1.0 200 OK\r\ncontent-type: text/event-stream\r\n\r\n").ok()?;
|
||||
|
||||
let file = match std::fs::File::open(file) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
let msg = format!("Failed to open file: {err}");
|
||||
let size = base64::prelude::BASE64_STANDARD.encode_slice(&msg, &mut b64_buf).ok()?;
|
||||
stream.write_all(b"event:line\ndata:").ok()?;
|
||||
stream.write_all(&b64_buf[..size]).ok()?;
|
||||
return stream.write_all(b"\n\n").ok();
|
||||
}
|
||||
};
|
||||
let mut buf = [0_u8; 1024];
|
||||
let mut fds = [nix::poll::PollFd::new(&file, nix::poll::PollFlags::POLLIN | nix::poll::PollFlags::POLLHUP | nix::poll::PollFlags::POLLERR)];
|
||||
|
||||
loop {
|
||||
let ready = nix::poll::poll(&mut fds, 15000).ok()?;
|
||||
if ready == 0 { // timeout
|
||||
stream.write_all(b"event:ka\ndata:\n\n").ok()?;
|
||||
} else if fds[0].revents().unwrap().contains(nix::poll::PollFlags::POLLIN) {
|
||||
let size = nix::unistd::read(file.as_raw_fd(), &mut buf).ok()?;
|
||||
if size > 0 {
|
||||
let size = base64::prelude::BASE64_STANDARD.encode_slice(&buf[..size], &mut b64_buf).ok()?;
|
||||
stream.write_all(b"event:line\ndata:").ok()?;
|
||||
stream.write_all(&b64_buf[..size]).ok()?;
|
||||
stream.write_all(b"\n\n").ok()?;
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn reload_config() -> Result<(), String> {
|
||||
let data = Data::get();
|
||||
let new_config = crate::config::Config::load()?;
|
||||
let mut removed_services = data.services.keys().cloned().collect::<std::collections::HashSet<_>>();
|
||||
for service in new_config.service {
|
||||
match data.services.iter_mut().find(|s| s.1.config.name == service.name) {
|
||||
Some((id, s)) => { // Replace old service
|
||||
s.config = service;
|
||||
removed_services.remove(id);
|
||||
}
|
||||
None => { // New service
|
||||
let s = service::Service::new(service);
|
||||
data.services.insert(s.id, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let queue = data.work_queue.as_ref().unwrap();
|
||||
for work in removed_services.iter().map(|id| crate::WorkItem::StopService(*id, false)) { queue.send(work).unwrap(); }
|
||||
for id in removed_services {
|
||||
let s = data.services.get(&id).unwrap();
|
||||
while !s.stopped() { std::thread::sleep(std::time::Duration::from_millis(250)); }
|
||||
data.services.remove(&id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
132
src/main.rs
Normal file
132
src/main.rs
Normal file
@ -0,0 +1,132 @@
|
||||
use std::os::unix::prelude::CommandExt;
|
||||
use std::process::Command;
|
||||
use crate::web::setup_webserver;
|
||||
|
||||
mod config;
|
||||
mod service;
|
||||
mod watch;
|
||||
mod web;
|
||||
mod html;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum WorkItem {
|
||||
StartService(u64),
|
||||
StopService(u64, bool)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Data {
|
||||
pub epoll: nix::sys::epoll::Epoll,
|
||||
pub sigfd: nix::sys::signalfd::SignalFd,
|
||||
pub work_queue: Option<std::sync::mpsc::Sender<WorkItem>>,
|
||||
pub services: std::collections::HashMap<u64, service::Service>
|
||||
}
|
||||
impl Data {
|
||||
fn new() -> Self {
|
||||
let epoll = nix::sys::epoll::Epoll::new(nix::sys::epoll::EpollCreateFlags::EPOLL_CLOEXEC).expect("Failed to create epoll");
|
||||
let mut sigset = nix::sys::signal::SigSet::empty();
|
||||
sigset.add(nix::sys::signal::SIGTERM);
|
||||
sigset.add(nix::sys::signal::SIGINT);
|
||||
sigset.add(nix::sys::signal::SIGCHLD);
|
||||
nix::sys::signal::sigprocmask(nix::sys::signal::SigmaskHow::SIG_BLOCK, Some(&sigset), None).expect("Failed to set signal mask");
|
||||
let sigfd = nix::sys::signalfd::SignalFd::with_flags(&sigset, nix::sys::signalfd::SfdFlags::SFD_CLOEXEC).expect("Failed to create signalfd");
|
||||
epoll.add(&sigfd, nix::sys::epoll::EpollEvent::new(nix::sys::epoll::EpollFlags::EPOLLIN, watch::EPOLL_SIGFD_ID)).expect("Failed to add signalfd to epoll");
|
||||
Self {
|
||||
epoll,
|
||||
sigfd,
|
||||
work_queue: None,
|
||||
services: std::collections::HashMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get() -> &'static mut Self {
|
||||
static mut INSTANCE: Option<Data> = None;
|
||||
unsafe {
|
||||
if INSTANCE.is_none() { INSTANCE.replace(Data::new()); }
|
||||
INSTANCE.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn any_running(&self) -> bool {
|
||||
return self.services.iter().any(|s| !s.1.stopped());
|
||||
}
|
||||
|
||||
pub fn service_update_dep(&mut self) {
|
||||
let mut id_map = std::collections::HashMap::new();
|
||||
for (_, service) in &self.services {
|
||||
id_map.insert(service.config.name.clone(), service.id);
|
||||
}
|
||||
|
||||
let id_map = id_map;
|
||||
let mut dep_map: std::collections::HashMap<u64, Vec<u64>> = std::collections::HashMap::new();
|
||||
for (_, service) in &mut self.services {
|
||||
service.dependencies.clear();
|
||||
for dep in &service.config.depends_on {
|
||||
let did = *id_map.get(dep).unwrap();
|
||||
service.dependencies.push(did);
|
||||
dep_map.entry(did).or_default().push(service.id);
|
||||
}
|
||||
}
|
||||
for (_, service) in &mut self.services {
|
||||
service.dependents = dep_map.entry(service.id).or_default().clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (start_as_daemon, config) = {
|
||||
let mut start_as_daemon = true;
|
||||
|
||||
let mut arg_iter = std::env::args().skip(1);
|
||||
while let Some(arg) = arg_iter.next() {
|
||||
if arg == "-n" {
|
||||
start_as_daemon = false;
|
||||
} else if arg == "-s" {
|
||||
let new_args = std::env::args().filter(|s| s != "-s");
|
||||
Command::new("strace").arg("-f").arg("-e").arg("trace=%process,%ipc,/epoll.*").args(new_args).exec();
|
||||
} else if arg.starts_with('-') {
|
||||
eprintln!("Invalid switch '{arg}'");
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
eprintln!("Invalid arg '{arg}'");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let config = match config::Config::load() {
|
||||
Ok(v) => v,
|
||||
Err(e) => { eprintln!("{e}"); std::process::exit(1); }
|
||||
};
|
||||
|
||||
(start_as_daemon, config)
|
||||
};
|
||||
|
||||
if start_as_daemon {
|
||||
println!("Launching as daemon!");
|
||||
daemonize::Daemonize::new()
|
||||
.working_directory(std::env::current_dir().unwrap())
|
||||
.start()
|
||||
.expect("Failed to daemonize");
|
||||
let log_file = nix::fcntl::open(
|
||||
"minitd.log",
|
||||
nix::fcntl::OFlag::O_WRONLY | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_CREAT | nix::fcntl::OFlag::O_APPEND,
|
||||
nix::sys::stat::Mode::S_IWUSR | nix::sys::stat::Mode::S_IRUSR
|
||||
).expect("Failed to open log file");
|
||||
nix::unistd::dup2(log_file, 1).expect("Failed to redirect stdout");
|
||||
nix::unistd::dup2(log_file, 2).expect("Failed to redirect stderr");
|
||||
nix::unistd::close(log_file).expect("Failed to close log file");
|
||||
}
|
||||
|
||||
{
|
||||
let data = Data::get();
|
||||
for service in config.service {
|
||||
let service = service::Service::new(service);
|
||||
data.services.insert(service.id, service);
|
||||
}
|
||||
data.service_update_dep();
|
||||
}
|
||||
|
||||
setup_webserver();
|
||||
|
||||
watch::watch_services(config.autostart);
|
||||
}
|
177
src/service.rs
Normal file
177
src/service.rs
Normal file
@ -0,0 +1,177 @@
|
||||
use std::ffi::CString;
|
||||
use std::os::fd::{AsRawFd, FromRawFd};
|
||||
use crate::Data;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ServiceStatus {
|
||||
STOPPED,
|
||||
STARTED(std::time::Instant), // Started at
|
||||
STOPPING,
|
||||
FAILED(String)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Service {
|
||||
pub id: u64, // > 255
|
||||
pub pid: Option<(nix::unistd::Pid, std::os::fd::OwnedFd)>,
|
||||
pub status: ServiceStatus,
|
||||
pub reaped: std::sync::atomic::AtomicBool,
|
||||
pub dependencies: Vec<u64>,
|
||||
pub dependents: Vec<u64>,
|
||||
pub restart_count: u8,
|
||||
pub config: crate::config::CService
|
||||
}
|
||||
|
||||
impl Service {
|
||||
fn get_args(&self) -> Result<Vec<CString>, String> {
|
||||
let args = match shlex::split(&self.config.command) {
|
||||
Some(v) => v,
|
||||
None => return Err("Invalid command".into())
|
||||
};
|
||||
if args.is_empty() {
|
||||
return Err("Command is empty".into());
|
||||
}
|
||||
Ok(args.into_iter().map(|s| CString::new(s).unwrap()).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
pub fn started(&self) -> bool {
|
||||
match &self.status {
|
||||
ServiceStatus::STARTED(_) => !self.reaped.load(std::sync::atomic::Ordering::Relaxed),
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stopped(&self) -> bool {
|
||||
match &self.status {
|
||||
ServiceStatus::STOPPED | ServiceStatus::FAILED(_) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_autostart(&self) -> bool {
|
||||
return self.restart_count < 3;
|
||||
}
|
||||
|
||||
pub fn new(config: crate::config::CService) -> Self {
|
||||
static NEXT_SERVICE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(256);
|
||||
|
||||
let id = NEXT_SERVICE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
Self {
|
||||
id,
|
||||
pid: None,
|
||||
status: ServiceStatus::STOPPED,
|
||||
reaped: std::sync::atomic::AtomicBool::new(false),
|
||||
dependents: Vec::new(),
|
||||
dependencies: Vec::new(),
|
||||
restart_count: 0,
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
if self.started() { return; }
|
||||
println!("[U...] {}", &self.config.name);
|
||||
self.restart_count += 1;
|
||||
self.reaped.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
let args = match self.get_args() {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
self.status = ServiceStatus::FAILED(err);
|
||||
println!("[FAIL] {}", &self.config.name);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let (r, s) = nix::unistd::pipe().unwrap();
|
||||
let (r, s) = unsafe { (std::os::fd::OwnedFd::from_raw_fd(r), std::os::fd::OwnedFd::from_raw_fd(s)) };
|
||||
let mut pipe_buf = [0_u8];
|
||||
|
||||
match unsafe {nix::unistd::fork()} {
|
||||
Ok(nix::unistd::ForkResult::Child) => {
|
||||
drop(r);
|
||||
let mut sigset = nix::sys::signal::SigSet::empty();
|
||||
sigset.add(nix::sys::signal::SIGTERM);
|
||||
sigset.add(nix::sys::signal::SIGINT);
|
||||
sigset.add(nix::sys::signal::SIGCHLD);
|
||||
nix::sys::signal::sigprocmask(nix::sys::signal::SigmaskHow::SIG_UNBLOCK, Some(&sigset), None).expect("Failed to set signal mask");
|
||||
|
||||
let log_name = self.config.name.clone() + ".log";
|
||||
let log_file = nix::fcntl::open(
|
||||
log_name.as_str(),
|
||||
nix::fcntl::OFlag::O_WRONLY | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_CREAT | nix::fcntl::OFlag::O_APPEND,
|
||||
nix::sys::stat::Mode::S_IWUSR | nix::sys::stat::Mode::S_IRUSR
|
||||
).expect("Failed to open log file");
|
||||
nix::unistd::dup2(log_file, 1).expect("Failed to redirect stdout");
|
||||
nix::unistd::dup2(log_file, 2).expect("Failed to redirect stderr");
|
||||
nix::unistd::close(log_file).expect("Failed to close log file");
|
||||
nix::unistd::close(0).expect("Failed to close stdin");
|
||||
|
||||
nix::unistd::setpgid(nix::unistd::Pid::from_raw(0), nix::unistd::Pid::from_raw(0)).expect("Failed to set pgid");
|
||||
|
||||
if let Some(dir) = &self.config.directory { nix::unistd::chdir(dir.as_str()).expect(&format!("Failed to change dir to {dir}")); }
|
||||
|
||||
nix::unistd::write(s.as_raw_fd(), &pipe_buf).unwrap();
|
||||
drop(s);
|
||||
|
||||
nix::unistd::execvp(&args[0], args.as_slice()).unwrap();
|
||||
std::process::exit(0);
|
||||
}
|
||||
Ok(nix::unistd::ForkResult::Parent { child }) => {
|
||||
drop(s);
|
||||
|
||||
let pidfd = nix::errno::Errno::result(unsafe { libc::syscall(libc::SYS_pidfd_open, child.as_raw(), 0 as libc::c_int) as libc::c_int }).expect("Failed to open pidfd");
|
||||
let pidfd = unsafe { std::os::fd::OwnedFd::from_raw_fd(pidfd) };
|
||||
self.pid.replace((child, pidfd));
|
||||
|
||||
let mut fds = [
|
||||
nix::poll::PollFd::new(&r, nix::poll::PollFlags::POLLIN | nix::poll::PollFlags::POLLHUP | nix::poll::PollFlags::POLLPRI),
|
||||
nix::poll::PollFd::new(&r, nix::poll::PollFlags::POLLIN | nix::poll::PollFlags::POLLPRI)
|
||||
];
|
||||
nix::poll::poll(&mut fds, -1).expect("Failed to poll fds");
|
||||
if fds[0].any().unwrap_or(false) {
|
||||
let read = nix::unistd::read(r.as_raw_fd(), &mut pipe_buf).unwrap();
|
||||
if read == 0 {
|
||||
self.status = ServiceStatus::FAILED("Check log".into());
|
||||
println!("[FAIL] {}", &self.config.name);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
self.status = ServiceStatus::FAILED("Check log".into());
|
||||
println!("[FAIL] {}", &self.config.name);
|
||||
return;
|
||||
}
|
||||
|
||||
self.status = ServiceStatus::STARTED(std::time::Instant::now());
|
||||
Data::get().epoll.add(&self.pid.as_ref().unwrap().1, nix::sys::epoll::EpollEvent::new(nix::sys::epoll::EpollFlags::EPOLLIN, self.id)).expect("Failed to add epoll");
|
||||
println!("[ OK ] {}", &self.config.name);
|
||||
}
|
||||
Err(err) => panic!("Failed to fork for service: {}", err)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, manual: bool) {
|
||||
const TERM_WAIT_MS: u128 = 10000;
|
||||
|
||||
if self.stopped() { return; }
|
||||
|
||||
if let Some((pid, _)) = self.pid.as_ref() {
|
||||
let pid = *pid;
|
||||
println!("[D...] {}", &self.config.name);
|
||||
self.status = ServiceStatus::STOPPING;
|
||||
let stop_started = std::time::Instant::now();
|
||||
nix::sys::signal::kill(pid, nix::sys::signal::SIGTERM).expect("Failed to send SIGTERM");
|
||||
while !self.reaped.load(std::sync::atomic::Ordering::Relaxed) && stop_started.elapsed().as_millis() < TERM_WAIT_MS {
|
||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||||
}
|
||||
if !self.reaped.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
nix::sys::signal::kill(pid, nix::sys::signal::SIGKILL).expect("Failed to send SIGKILL");
|
||||
}
|
||||
}
|
||||
|
||||
println!("[STOP] {}", &self.config.name);
|
||||
self.status = ServiceStatus::STOPPED;
|
||||
|
||||
if manual { self.restart_count = 0; }
|
||||
}
|
||||
}
|
152
src/watch.rs
Normal file
152
src/watch.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use std::collections::VecDeque;
|
||||
use crate::{Data, WorkItem};
|
||||
use crate::service::ServiceStatus;
|
||||
|
||||
pub const EPOLL_SIGFD_ID: u64 = 0;
|
||||
pub const EPOLL_WEBSERVER_ID: u64 = 1;
|
||||
|
||||
const EPOLL_MAX_EVENTS: usize = 16;
|
||||
static SHUTDOWN_STARTED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
pub fn watch_services(autostart: Vec<String>) {
|
||||
let starter = std::thread::spawn(move || starter_thread(autostart));
|
||||
|
||||
let data = Data::get();
|
||||
let mut events = [nix::sys::epoll::EpollEvent::empty(); EPOLL_MAX_EVENTS];
|
||||
loop {
|
||||
let ready = match data.epoll.wait(&mut events, 15000) {
|
||||
Ok(v) => v,
|
||||
Err(nix::errno::Errno::EINTR) => 0,
|
||||
Err(e) => panic!("Epoll wait failed: {:?}", e)
|
||||
};
|
||||
for i in 0..ready {
|
||||
let event = &events[i];
|
||||
match event.data() {
|
||||
EPOLL_SIGFD_ID => {
|
||||
let sig = data.sigfd.read_signal().unwrap().unwrap();
|
||||
if sig.ssi_signo == libc::SIGCHLD as u32 {
|
||||
//println!("Got SIGCHLD, ignoring");
|
||||
} else {
|
||||
let s = nix::sys::signal::Signal::try_from(sig.ssi_signo as i32).unwrap();
|
||||
println!("sigfd event, treated as exit command. Signal: {s:#?}");
|
||||
SHUTDOWN_STARTED.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
for (id, _) in &data.services {
|
||||
data.work_queue.as_ref().unwrap().send(WorkItem::StopService(*id, false)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
EPOLL_WEBSERVER_ID => {
|
||||
crate::web::webserver_accept();
|
||||
}
|
||||
id => {
|
||||
let service = match data.services.get_mut(&id) {
|
||||
None => { panic!("Got epoll event for id '{}' but no service found\n{:#?}", id, data.services); }
|
||||
Some(v) => v,
|
||||
};
|
||||
let name = &service.config.name;
|
||||
let pid = service.pid.take().unwrap();
|
||||
data.epoll.delete(pid.1).expect("Failed to delete from epoll");
|
||||
let status = nix::sys::wait::waitpid(pid.0, None).expect("waitpid failed");
|
||||
match status {
|
||||
nix::sys::wait::WaitStatus::Exited(_, code) => println!("{name} exited with status {code}"),
|
||||
nix::sys::wait::WaitStatus::Signaled(_, sig, _) => println!("{name} exited with signal {sig}"),
|
||||
_ => eprintln!("Unknown status {status:#?}")
|
||||
}
|
||||
service.reaped.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
data.work_queue.as_ref().unwrap().send(WorkItem::StopService(id, false)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
if SHUTDOWN_STARTED.load(std::sync::atomic::Ordering::Relaxed) && !data.any_running() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Data::get().work_queue.as_ref().unwrap().send(WorkItem::StopService(0, true)).unwrap();
|
||||
|
||||
starter.join().unwrap();
|
||||
}
|
||||
|
||||
fn starter_thread(autostart: Vec<String>) {
|
||||
let data = Data::get();
|
||||
let (recv, mut todo) = {
|
||||
let (s,r) = std::sync::mpsc::channel();
|
||||
data.work_queue.replace(s);
|
||||
|
||||
let mut id_map = std::collections::HashMap::new();
|
||||
for (_, service) in &data.services {
|
||||
id_map.insert(service.config.name.clone(), service.id);
|
||||
}
|
||||
|
||||
let todo = autostart.into_iter()
|
||||
.map(|s| WorkItem::StartService(*id_map.get(&s).unwrap()))
|
||||
.collect::<VecDeque<_>>();
|
||||
|
||||
(r, todo)
|
||||
};
|
||||
|
||||
'main_loop: loop {
|
||||
while let Some(work) = todo.pop_front() {
|
||||
match work {
|
||||
WorkItem::StartService(id) => {
|
||||
if SHUTDOWN_STARTED.load(std::sync::atomic::Ordering::Relaxed) { continue; }
|
||||
let (missing_deps, dep_fail) = {
|
||||
let mut missing_deps = Vec::new();
|
||||
let mut dep_fail = false;
|
||||
let service = data.services.get(&id).unwrap();
|
||||
for dep in &service.dependencies {
|
||||
let dep = data.services.get(dep).unwrap();
|
||||
if !dep.started() {
|
||||
if dep.can_autostart() {
|
||||
missing_deps.push(WorkItem::StartService(dep.id));
|
||||
} else {
|
||||
dep_fail = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
(missing_deps, dep_fail)
|
||||
};
|
||||
let service = data.services.get_mut(&id).unwrap();
|
||||
if dep_fail {
|
||||
service.status = ServiceStatus::FAILED("Failed to start dependency".into());
|
||||
service.restart_count = 3;
|
||||
continue;
|
||||
} else if !missing_deps.is_empty() {
|
||||
for dep in missing_deps { todo.push_front(dep); }
|
||||
todo.push_back(WorkItem::StartService(id));
|
||||
continue;
|
||||
}
|
||||
service.start();
|
||||
}
|
||||
WorkItem::StopService(id, manual) => {
|
||||
let can_stop = {
|
||||
let mut can_stop = true;
|
||||
let service = match data.services.get(&id) {
|
||||
Some(v) => v,
|
||||
None => continue
|
||||
};
|
||||
for dep in &service.dependents {
|
||||
let dep = data.services.get(dep).unwrap();
|
||||
if !dep.stopped() {
|
||||
todo.push_front(WorkItem::StopService(dep.id, manual));
|
||||
can_stop = false;
|
||||
}
|
||||
}
|
||||
can_stop
|
||||
};
|
||||
let service = data.services.get_mut(&id).unwrap();
|
||||
if !can_stop {
|
||||
todo.push_back(WorkItem::StopService(id, manual));
|
||||
continue;
|
||||
}
|
||||
service.stop(manual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let work = recv.recv().unwrap();
|
||||
match work { WorkItem::StopService(0, _) => break 'main_loop, _ => {} }
|
||||
todo.push_back(work);
|
||||
}
|
||||
}
|
89
src/web.rs
Normal file
89
src/web.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use std::io::{Write, BufRead};
|
||||
use std::net::TcpStream;
|
||||
use crate::{Data, html, watch, WorkItem};
|
||||
|
||||
static mut WEBSERVER_FD: Option<std::net::TcpListener> = None;
|
||||
|
||||
pub fn setup_webserver() {
|
||||
let data = Data::get();
|
||||
let listener = std::net::TcpListener::bind("0.0.0.0:9001").expect("Failed to bind listener");
|
||||
listener.set_nonblocking(true).expect("Failed to set non-blocking");
|
||||
data.epoll.add(&listener, nix::sys::epoll::EpollEvent::new(nix::sys::epoll::EpollFlags::EPOLLIN, watch::EPOLL_WEBSERVER_ID)).expect("Failed to add socket to epoll");
|
||||
unsafe { WEBSERVER_FD = Some(listener) };
|
||||
}
|
||||
|
||||
pub fn webserver_accept() {
|
||||
let listener = unsafe { WEBSERVER_FD.as_ref().unwrap() };
|
||||
if let Ok((stream, _)) = listener.accept() {
|
||||
std::thread::spawn(move || handle_stream(stream));
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_stream(mut stream: TcpStream) -> Option<()> {
|
||||
stream.set_read_timeout(Some(std::time::Duration::new(5, 0))).ok()?;
|
||||
stream.set_write_timeout(Some(std::time::Duration::new(5, 0))).ok()?;
|
||||
|
||||
let reader = std::io::BufReader::new(&mut stream);
|
||||
let mut lines = reader.lines();
|
||||
let (method, path) = {
|
||||
let first = lines.next()?.ok()?;
|
||||
let (method, first) = first.split_once(' ')?;
|
||||
let path = first.split_once(' ')?.0;
|
||||
(method.to_string(), path.to_string())
|
||||
};
|
||||
for line in lines {
|
||||
if line.ok()?.is_empty() { break; }
|
||||
}
|
||||
if method != "GET" { return html::send_404(stream); }
|
||||
|
||||
if path == "/" {
|
||||
html::send_index(stream)
|
||||
} else if path == "/tail" {
|
||||
html::send_tail_html(stream)
|
||||
} else if path == "/reload" {
|
||||
match html::reload_config() {
|
||||
Ok(()) => html::send_301(stream),
|
||||
Err(e) => write!(stream, "HTTP/1.0 400 Bad request\r\nContent-Type: text/plain;charset=utf-8\r\n\r\nFailed to reload config:\n{e}").ok()
|
||||
}
|
||||
} else if path == "/stop_all" {
|
||||
let data = Data::get();
|
||||
for (id, _) in &data.services {
|
||||
data.work_queue.as_ref().unwrap().send(WorkItem::StopService(*id, false)).unwrap();
|
||||
}
|
||||
html::send_301(stream)
|
||||
} else if path == "/shutdown" {
|
||||
nix::sys::signal::kill(nix::unistd::getpid(), nix::sys::signal::SIGTERM).ok()?;
|
||||
html::send_301(stream)
|
||||
} else if path.starts_with("/_tail/") {
|
||||
let mut path = path;
|
||||
drop(path.drain(..7));
|
||||
if path.contains('.') || path.contains('/') {
|
||||
html::send_404(stream)
|
||||
} else {
|
||||
let path = path + ".log";
|
||||
html::send_tail(stream, path)
|
||||
}
|
||||
} else if path.starts_with("/start/") {
|
||||
let service = &path[7..];
|
||||
let data = Data::get();
|
||||
match data.services.iter().find(|(_, s)| s.config.name == service) {
|
||||
Some((id, _)) => {
|
||||
data.work_queue.as_ref().unwrap().send(WorkItem::StartService(*id)).unwrap();
|
||||
html::send_301(stream)
|
||||
},
|
||||
None => html::send_404(stream)
|
||||
}
|
||||
} else if path.starts_with("/stop/") {
|
||||
let service = &path[6..];
|
||||
let data = Data::get();
|
||||
match data.services.iter().find(|(_, s)| s.config.name == service) {
|
||||
Some((id, _)) => {
|
||||
data.work_queue.as_ref().unwrap().send(WorkItem::StopService(*id, true)).unwrap();
|
||||
html::send_301(stream)
|
||||
},
|
||||
None => html::send_404(stream)
|
||||
}
|
||||
} else {
|
||||
html::send_404(stream)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user