about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authoryuzu <yuzu@b9215c17-b818-4693-b096-d1e41a411fef>2025-07-12 22:17:26 +0000
committeryuzu <yuzu@b9215c17-b818-4693-b096-d1e41a411fef>2025-07-12 22:17:26 +0000
commite33f9a59f875edf1240ca80c1014235296ff3cbf (patch)
tree5777c00bafad650d6be84f42f51ba4f873d92c53
parent78608add1c69a877b76a05147f6c26b7abe66669 (diff)
downloadsalaryman-e33f9a59f875edf1240ca80c1014235296ff3cbf.tar.gz
salaryman-e33f9a59f875edf1240ca80c1014235296ff3cbf.tar.bz2
salaryman-e33f9a59f875edf1240ca80c1014235296ff3cbf.zip
add additional endpoints; change out mutexes for rwlocks
git-svn-id: svn+ssh://diminuette.aengel.lesbianunix.dev/salaryman/trunk@15 b9215c17-b818-4693-b096-d1e41a411fef
-rw-r--r--Cargo.lock811
-rw-r--r--Cargo.toml51
-rw-r--r--src/cli/main.rs4
-rw-r--r--src/lib.rs3
-rw-r--r--src/model.rs31
-rw-r--r--src/server/context.rs47
-rw-r--r--src/server/endpoints.rs359
-rw-r--r--src/server/main.rs (renamed from src/smd/main.rs)38
-rw-r--r--src/smd/context.rs47
-rw-r--r--src/smd/endpoints.rs205
10 files changed, 1321 insertions, 275 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9752728..2025dbf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -18,6 +18,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
 
 [[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
 name = "android-tzdata"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -83,6 +98,22 @@ dependencies = [
 ]
 
 [[package]]
+name = "async-compression"
+version = "0.4.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4"
+dependencies = [
+ "brotli",
+ "flate2",
+ "futures-core",
+ "memchr",
+ "pin-project-lite",
+ "tokio",
+ "zstd",
+ "zstd-safe",
+]
+
+[[package]]
 name = "async-stream"
 version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -164,6 +195,27 @@ dependencies = [
 ]
 
 [[package]]
+name = "brotli"
+version = "8.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
 name = "bumpalo"
 version = "3.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -196,6 +248,8 @@ version = "1.2.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362"
 dependencies = [
+ "jobserver",
+ "libc",
  "shlex",
 ]
 
@@ -265,6 +319,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
 
 [[package]]
+name = "cookie"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
+dependencies = [
+ "percent-encoding",
+ "time",
+ "version_check",
+]
+
+[[package]]
+name = "cookie_store"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
+dependencies = [
+ "cookie",
+ "document-features",
+ "idna",
+ "log",
+ "publicsuffix",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "time",
+ "url",
+]
+
+[[package]]
 name = "core-foundation"
 version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -290,6 +373,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "crc32fast"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
 name = "crossbeam-channel"
 version = "0.5.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -361,6 +453,26 @@ dependencies = [
 ]
 
 [[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
 name = "dof"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -400,7 +512,7 @@ dependencies = [
  "openapiv3",
  "paste",
  "percent-encoding",
- "rustls",
+ "rustls 0.22.4",
  "rustls-pemfile",
  "schemars",
  "scopeguard",
@@ -417,7 +529,7 @@ dependencies = [
  "slog-term",
  "thiserror 2.0.12",
  "tokio",
- "tokio-rustls",
+ "tokio-rustls 0.25.0",
  "toml",
  "usdt",
  "uuid",
@@ -473,12 +585,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
 
 [[package]]
+name = "errno"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "flate2"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
 name = "fnv"
 version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
 [[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
 name = "form_urlencoded"
 version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -753,6 +906,38 @@ dependencies = [
 ]
 
 [[package]]
+name = "hyper-rustls"
+version = "0.27.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls 0.23.29",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls 0.26.2",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
 name = "hyper-util"
 version = "0.1.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -803,6 +988,113 @@ dependencies = [
 ]
 
 [[package]]
+name = "icu_collections"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
+
+[[package]]
+name = "icu_properties"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "potential_utf",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
+
+[[package]]
+name = "icu_provider"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
 name = "indexmap"
 version = "2.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -820,6 +1112,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
 
 [[package]]
+name = "iri-string"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
 name = "is-terminal"
 version = "0.4.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -843,6 +1145,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
 
 [[package]]
+name = "jobserver"
+version = "0.1.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
+dependencies = [
+ "getrandom 0.3.3",
+ "libc",
+]
+
+[[package]]
 name = "js-sys"
 version = "0.3.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -869,6 +1181,24 @@ dependencies = [
 ]
 
 [[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[package]]
+name = "litemap"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+
+[[package]]
+name = "litrs"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
+
+[[package]]
 name = "lock_api"
 version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -950,6 +1280,23 @@ dependencies = [
 ]
 
 [[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
 name = "num-conv"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1006,6 +1353,50 @@ dependencies = [
 ]
 
 [[package]]
+name = "openssl"
+version = "0.10.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
 name = "parking_lot"
 version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1097,12 +1488,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
 [[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
 name = "plain"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
 
 [[package]]
+name = "potential_utf"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
 name = "powerfmt"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1124,6 +1530,22 @@ dependencies = [
 ]
 
 [[package]]
+name = "psl-types"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
+
+[[package]]
+name = "publicsuffix"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
+dependencies = [
+ "idna",
+ "psl-types",
+]
+
+[[package]]
 name = "quote"
 version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1159,6 +1581,51 @@ dependencies = [
 ]
 
 [[package]]
+name = "reqwest"
+version = "0.12.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
+dependencies = [
+ "async-compression",
+ "base64",
+ "bytes",
+ "cookie",
+ "cookie_store",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-tls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-native-tls",
+ "tokio-util",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
 name = "ring"
 version = "0.17.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1179,6 +1646,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
 
 [[package]]
+name = "rustix"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
 name = "rustls"
 version = "0.22.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1187,7 +1667,20 @@ dependencies = [
  "log",
  "ring",
  "rustls-pki-types",
- "rustls-webpki",
+ "rustls-webpki 0.102.8",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1"
+dependencies = [
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki 0.103.4",
  "subtle",
  "zeroize",
 ]
@@ -1222,6 +1715,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "rustls-webpki"
+version = "0.103.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
 name = "rustversion"
 version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1239,6 +1743,7 @@ version = "0.0.1"
 dependencies = [
  "clap",
  "dropshot",
+ "reqwest",
  "schemars",
  "semver",
  "serde",
@@ -1248,6 +1753,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
 name = "schemars"
 version = "0.8.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1299,6 +1813,29 @@ dependencies = [
 ]
 
 [[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
 name = "semver"
 version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1511,6 +2048,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
 
 [[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
 name = "strsim"
 version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1534,6 +2077,26 @@ dependencies = [
 ]
 
 [[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "system-configuration"
 version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1561,6 +2124,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
 
 [[package]]
+name = "tempfile"
+version = "3.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.3",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
 name = "term"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1664,6 +2240,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "tinystr"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
 name = "tokio"
 version = "1.45.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1693,17 +2279,37 @@ dependencies = [
 ]
 
 [[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
 name = "tokio-rustls"
 version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
 dependencies = [
- "rustls",
+ "rustls 0.22.4",
  "rustls-pki-types",
  "tokio",
 ]
 
 [[package]]
+name = "tokio-rustls"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
+dependencies = [
+ "rustls 0.23.29",
+ "tokio",
+]
+
+[[package]]
 name = "tokio-util"
 version = "0.7.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1758,6 +2364,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
 
 [[package]]
+name = "tower"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
+dependencies = [
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "iri-string",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
 name = "tower-service"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1813,6 +2458,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
 
 [[package]]
+name = "url"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
 name = "usdt"
 version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1877,6 +2533,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
 name = "utf8parse"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1895,6 +2557,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
 name = "version_check"
 version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1960,6 +2628,19 @@ dependencies = [
 ]
 
 [[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
 name = "wasm-bindgen-macro"
 version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1992,6 +2673,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
 name = "winapi"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2184,6 +2875,36 @@ dependencies = [
 ]
 
 [[package]]
+name = "writeable"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+
+[[package]]
+name = "yoke"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
 name = "zerocopy"
 version = "0.7.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2205,7 +2926,89 @@ dependencies = [
 ]
 
 [[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
 name = "zeroize"
 version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zstd"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "7.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
+dependencies = [
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.15+zstd.1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 466871a..8f07281 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,35 +6,72 @@ authors = ["Ren Kararou <[email protected]>"]
 description = "A very simple service management framework"
 
 [features]
-default = ["smd"]
+default = ["models", "smd", "sm-cli"]
+models = []
 smd = [
 	"dep:clap",
 	"dep:dropshot",
-	"dep:schemars",
 	"dep:toml",
 	"dep:semver",
+	"models",
+]
+sm-cli = [
+	"dep:clap",
+	"dep:toml",
+	"dep:reqwest",
+	"models",
 ]
 
 [dependencies]
-clap = { version = "4.5.39", features = ["derive"], optional = true }
-dropshot = { version = "0.16.2", features = ["usdt","usdt-probes"], optional = true }
-schemars = { version = "0.8.22", features = ["uuid1"], optional = true }
+schemars = { version = "0.8.22", features = ["uuid1"] }
 semver = { version = "1.0.26", optional = true }
 serde = { version = "1.0.219", features = ["derive"] }
 tokio = { version = "1.45.1", features = ["full"] }
 toml = { version = "0.8.22", optional = true }
 uuid = { version = "1.17.0", features = ["v4", "serde"] }
 
+[dependencies.clap]
+version = "4.5.39"
+features = [
+	"derive",
+]
+optional = true
+
+[dependencies.dropshot]
+version = "0.16.2"
+features = [
+	"usdt",
+	"usdt-probes",
+]
+optional = true
+
+[dependencies.reqwest]
+version = "0.12.22"
+features = [
+	"gzip",
+	"cookies",
+	"brotli",
+	"zstd"
+]
+optional = true
+
 [[bin]]
 name = "smd"
-path = "src/smd/main.rs"
+path = "src/server/main.rs"
 test = false
 bench = false
 required-features = ["smd"]
 
+[[bin]]
+name = "sm"
+path = "src/cli/main.rs"
+test = false
+bench = false
+required-features = ["sm-cli"]
+
 [profile.release]
 strip = true
-lto = "thin"
+lto = true
 panic = "abort"
 codegen-units = 1
 
diff --git a/src/cli/main.rs b/src/cli/main.rs
new file mode 100644
index 0000000..1b85472
--- /dev/null
+++ b/src/cli/main.rs
@@ -0,0 +1,4 @@
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    Ok(())
+}
diff --git a/src/lib.rs b/src/lib.rs
index 1f278a4..95486cd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1 +1,4 @@
 pub mod service;
+
+#[cfg(feature = "models")]
+pub mod model;
diff --git a/src/model.rs b/src/model.rs
new file mode 100644
index 0000000..578d9b4
--- /dev/null
+++ b/src/model.rs
@@ -0,0 +1,31 @@
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use std::net::IpAddr;
+use std::path::PathBuf;
+use uuid::Uuid;
+
+#[derive(Serialize, Deserialize, JsonSchema, Debug)]
+pub struct UpdateConf {
+    pub address: Option<IpAddr>,
+    pub port: Option<u16>,
+}
+
+#[derive(Serialize, Deserialize, JsonSchema, Debug)]
+pub struct StdinBuffer {
+    pub stdin: String,
+    pub endl: Option<bool>,
+}
+
+#[derive(Serialize, Deserialize, JsonSchema, Debug)]
+pub struct ServicePath {
+    pub service_uuid: Uuid,
+}
+
+#[derive(Serialize, Deserialize, JsonSchema, Debug)]
+pub struct NewService {
+    pub name: Option<String>,
+    pub command: Option<String>,
+    pub args: Option<Option<String>>,
+    pub directory: Option<Option<PathBuf>>,
+    pub autostart: Option<bool>,
+}
diff --git a/src/server/context.rs b/src/server/context.rs
new file mode 100644
index 0000000..132e2dc
--- /dev/null
+++ b/src/server/context.rs
@@ -0,0 +1,47 @@
+use super::Config;
+use salaryman::service::{Service, ServiceConf};
+use std::path::PathBuf;
+use std::sync::Arc;
+use tokio::sync::RwLock;
+
+pub struct SalarymanService {
+    pub config: ServiceConf,
+    pub service: Arc<RwLock<Service>>,
+}
+impl SalarymanService {
+    pub fn new() -> Self {
+        Self {
+            config: ServiceConf::new(),
+            service: Arc::new(RwLock::new(Service::new())),
+        }
+    }
+    pub fn from_parts(config: ServiceConf, service: Arc<RwLock<Service>>) -> Self {
+        Self { config, service }
+    }
+}
+
+pub struct SalarymanDContext {
+    pub services: RwLock<Vec<Arc<SalarymanService>>>,
+    pub save_file: PathBuf,
+    pub config: Arc<RwLock<Config>>,
+}
+impl SalarymanDContext {
+    pub fn new() -> Self {
+        Self {
+            services: RwLock::new(Vec::new()),
+            save_file: PathBuf::from(""),
+            config: Arc::new(RwLock::new(Config::new())),
+        }
+    }
+    pub fn from_parts(
+        services: RwLock<Vec<Arc<SalarymanService>>>,
+        save_file: PathBuf,
+        config: Arc<RwLock<Config>>,
+    ) -> Self {
+        Self {
+            services,
+            save_file,
+            config,
+        }
+    }
+}
diff --git a/src/server/endpoints.rs b/src/server/endpoints.rs
new file mode 100644
index 0000000..8ebc42e
--- /dev/null
+++ b/src/server/endpoints.rs
@@ -0,0 +1,359 @@
+use super::Config;
+use super::context::{SalarymanDContext, SalarymanService};
+use dropshot::{HttpError, HttpResponseOk, Path, RequestContext, TypedBody, endpoint};
+use salaryman::model::{NewService, ServicePath, StdinBuffer, UpdateConf};
+use salaryman::service::{Service, ServiceConf};
+use std::sync::Arc;
+use tokio::fs::File;
+use tokio::io::AsyncWriteExt;
+use tokio::sync::RwLock;
+
+#[endpoint {
+    method = GET,
+    path = "/config",
+}]
+pub async fn endpoint_get_config(
+    rqctx: RequestContext<Arc<SalarymanDContext>>,
+) -> Result<HttpResponseOk<Config>, HttpError> {
+    let ctx = rqctx.context();
+    let lock = ctx.config.read().await;
+    let conf = lock.clone();
+    drop(lock);
+    Ok(HttpResponseOk(conf))
+}
+#[endpoint {
+    method = PUT,
+    path = "/config",
+}]
+pub async fn endpoint_put_config(
+    rqctx: RequestContext<Arc<SalarymanDContext>>,
+    body: TypedBody<UpdateConf>,
+) -> Result<HttpResponseOk<()>, HttpError> {
+    let ctx = rqctx.context();
+    let update = body.into_inner();
+    if let Some(addr) = update.address {
+        let mut lock = ctx.config.write().await;
+        lock.address = Some(addr);
+        drop(lock);
+    }
+    if let Some(port) = update.port {
+        let mut lock = ctx.config.write().await;
+        lock.port = Some(port);
+        drop(lock);
+    }
+    Ok(HttpResponseOk(()))
+}
+#[endpoint {
+    method = GET,
+    path = "/config/save"
+}]
+pub async fn endpoint_get_config_save(
+    rqctx: RequestContext<Arc<SalarymanDContext>>,
+) -> Result<HttpResponseOk<()>, HttpError> {
+    let ctx = rqctx.context();
+    let mut v: Vec<ServiceConf> = Vec::new();
+    let rlock = ctx.services.read().await;
+    for i in 0..rlock.len() {
+        v.push(rlock[i].config.clone());
+    }
+    drop(rlock);
+    let rlock = ctx.config.read().await;
+    let save = Config {
+        address: rlock.address,
+        port: rlock.port,
+        user: rlock.user.clone(),
+        service: v,
+    };
+    drop(rlock);
+    let mut f = match File::open(&ctx.save_file).await {
+        Ok(f) => f,
+        Err(_) => {
+            return Err(HttpError::for_internal_error(String::from(
+                "cannot open desired file",
+            )));
+        }
+    };
+    let save = match toml::to_string(&save) {
+        Ok(s) => s,
+        Err(_) => {
+            return Err(HttpError::for_internal_error(String::from(
+                "cannot serialize config!",
+            )));
+        }
+    };
+    match f.write_all(save.as_str().as_bytes()).await {
+        Ok(_) => (),
+        Err(_) => {
+            return Err(HttpError::for_internal_error(String::from(
+                "could not write to file!",
+            )));
+        }
+    }
+    Ok(HttpResponseOk(()))
+}
+#[endpoint {
+    method = GET,
+    path = "/service",
+}]
+pub async fn endpoint_get_services(
+    rqctx: RequestContext<Arc<SalarymanDContext>>,
+) -> Result<HttpResponseOk<Vec<ServiceConf>>, HttpError> {
+    let ret = {
+        let ctx = rqctx.context();
+        let mut v: Vec<ServiceConf> = Vec::new();
+        let lock = ctx.services.read().await;
+        for i in 0..lock.len() {
+            v.push(lock[i].config.clone());
+        }
+        v
+    };
+    Ok(HttpResponseOk(ret))
+}
+#[endpoint {
+    method = POST,
+    path = "/service",
+}]
+pub async fn endpoint_post_service(
+    rqctx: RequestContext<Arc<SalarymanDContext>>,
+    body: TypedBody<NewService>,
+) -> Result<HttpResponseOk<ServiceConf>, HttpError> {
+    let ctx = rqctx.context();
+    let body = body.into_inner();
+    let mut s: ServiceConf = ServiceConf::new();
+    if let Some(name) = &body.name {
+        s.name = name.clone().to_owned();
+    } else {
+        return Err(HttpError::for_bad_request(
+            None,
+            String::from("name field is required!"),
+        ));
+    }
+    if let Some(command) = &body.command {
+        s.command = command.clone().to_owned();
+    } else {
+        return Err(HttpError::for_bad_request(
+            None,
+            String::from("command field is required!"),
+        ));
+    }
+    if let Some(args) = &body.args {
+        if let Some(args) = args {
+            s.args = Some(args.clone().to_owned());
+        }
+    }
+    if let Some(dir) = &body.directory {
+        if let Some(dir) = dir {
+            s.directory = Some(dir.clone().to_owned());
+        }
+    }
+    if let Some(auto) = &body.autostart {
+        s.autostart = auto.clone().to_owned();
+    } else {
+        s.autostart = false;
+    }
+    let service: SalarymanService =
+        SalarymanService::from_parts(s.clone(), Arc::new(RwLock::new(Service::from_conf(&s))));
+    if service.config.autostart {
+        let mut lock = service.service.write().await;
+        match lock.start_with_output().await {
+            Ok(_) => (),
+            Err(_) => (),
+        }
+        drop(lock);
+    }
+    let mut lock = ctx.config.write().await;
+    lock.service.push(s.clone());
+    drop(lock);
+    let mut lock = ctx.services.write().await;
+    lock.push(Arc::new(service));
+    drop(lock);
+    Ok(HttpResponseOk(ServiceConf::new()))
+}
+#[endpoint {
+    method = GET,
+    path = "/service/{service_uuid}",
+}]
+pub async fn endpoint_get_service(
+    rqctx: RequestContext<Arc<SalarymanDContext>>,
+    path_params: Path<ServicePath>,
+) -> Result<HttpResponseOk<ServiceConf>, HttpError> {
+    let u = path_params.into_inner().service_uuid;
+    let ctx = rqctx.context();
+    let mut service: Option<Arc<SalarymanService>> = None;
+    let lock = ctx.services.read().await;
+    for i in 0..lock.len() {
+        if lock[i].config.uuid == u {
+            service = Some(lock[i].clone());
+        } else {
+            continue;
+        }
+    }
+    let s = match service {
+        Some(s) => s.config.clone(),
+        None => {
+            return Err(HttpError::for_unavail(
+                None,
+                String::from("Service Not Found"),
+            ));
+        }
+    };
+    Ok(HttpResponseOk(s))
+}
+#[endpoint {
+    method = GET,
+    path = "/service/{service_uuid}/start",
+}]
+pub async fn endpoint_start_service(
+    rqctx: RequestContext<Arc<SalarymanDContext>>,
+    path_params: Path<ServicePath>,
+) -> Result<HttpResponseOk<()>, HttpError> {
+    let u = path_params.into_inner().service_uuid;
+    let ctx = rqctx.context();
+    let mut service: Option<Arc<SalarymanService>> = None;
+    let lock = ctx.services.read().await;
+    for i in 0..lock.len() {
+        if lock[i].config.uuid == u {
+            service = Some(lock[i].clone());
+        } else {
+            continue;
+        }
+    }
+    match service {
+        Some(s) => {
+            let mut lock = s.service.write().await;
+            match lock.start_with_output().await {
+                Ok(_) => (),
+                Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
+            }
+        }
+        None => {
+            return Err(HttpError::for_unavail(
+                None,
+                String::from("Service Not Found"),
+            ));
+        }
+    };
+    Ok(HttpResponseOk(()))
+}
+#[endpoint {
+    method = GET,
+    path = "/service/{service_uuid}/stop",
+}]
+pub async fn endpoint_stop_service(
+    rqctx: RequestContext<Arc<SalarymanDContext>>,
+    path_params: Path<ServicePath>,
+) -> Result<HttpResponseOk<()>, HttpError> {
+    let u = path_params.into_inner().service_uuid;
+    let ctx = rqctx.context();
+    let mut service: Option<Arc<SalarymanService>> = None;
+    let lock = ctx.services.read().await;
+    for i in 0..lock.len() {
+        if lock[i].config.uuid == u {
+            service = Some(lock[i].clone());
+        } else {
+            continue;
+        }
+    }
+    match service {
+        Some(s) => {
+            let mut lock = s.service.write().await;
+            match lock.stop().await {
+                Ok(_) => (),
+                Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
+            }
+        }
+        None => {
+            return Err(HttpError::for_unavail(
+                None,
+                String::from("Service Not Found"),
+            ));
+        }
+    };
+    Ok(HttpResponseOk(()))
+}
+#[endpoint {
+    method = GET,
+    path = "/service/{service_uuid}/restart",
+}]
+pub async fn endpoint_restart_service(
+    rqctx: RequestContext<Arc<SalarymanDContext>>,
+    path_params: Path<ServicePath>,
+) -> Result<HttpResponseOk<()>, HttpError> {
+    let u = path_params.into_inner().service_uuid;
+    let ctx = rqctx.context();
+    let mut service: Option<Arc<SalarymanService>> = None;
+    let lock = ctx.services.read().await;
+    for i in 0..lock.len() {
+        if lock[i].config.uuid == u {
+            service = Some(lock[i].clone());
+        } else {
+            continue;
+        }
+    }
+    match service {
+        Some(s) => {
+            let mut lock = s.service.write().await;
+            match lock.restart_with_output().await {
+                Ok(_) => (),
+                Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
+            }
+        }
+        None => {
+            return Err(HttpError::for_unavail(
+                None,
+                String::from("Service Not Found"),
+            ));
+        }
+    };
+    Ok(HttpResponseOk(()))
+}
+#[endpoint {
+    method = PUT,
+    path = "/service/{service_uuid}/write"
+}]
+pub async fn endpoint_post_stdin(
+    rqctx: RequestContext<Arc<SalarymanDContext>>,
+    path_params: Path<ServicePath>,
+    update: TypedBody<StdinBuffer>,
+) -> Result<HttpResponseOk<()>, HttpError> {
+    let ctx = rqctx.context();
+    let stdin_str = update.into_inner();
+    let u = path_params.into_inner().service_uuid;
+    let mut service: Option<Arc<SalarymanService>> = None;
+    let lock = ctx.services.read().await;
+    for i in 0..lock.len() {
+        if lock[i].config.uuid == u {
+            service = Some(lock[i].clone());
+        } else {
+            continue;
+        }
+    }
+    match service {
+        Some(s) => {
+            let mut lock = s.service.write().await;
+            if lock.started().await {
+                let b = if let Some(endl) = stdin_str.endl {
+                    if endl {
+                        lock.writeln_stdin(stdin_str.stdin.clone()).await //TODO: PROPERLY HANDLE ERROR!
+                    } else {
+                        lock.write_stdin(stdin_str.stdin.clone()).await //TODO: PROPERLY HANDLE ERROR!
+                    }
+                } else {
+                    lock.writeln_stdin(stdin_str.stdin.clone()).await //TODO: PROPERLY HANDLE ERROR!
+                };
+                match b {
+                    Ok(_) => (),
+                    Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
+                }
+            }
+            drop(lock);
+        }
+        None => {
+            return Err(HttpError::for_unavail(
+                None,
+                String::from("Service Not Found"),
+            ));
+        }
+    }
+    Ok(HttpResponseOk(()))
+}
diff --git a/src/smd/main.rs b/src/server/main.rs
index b81df2f..7557145 100644
--- a/src/smd/main.rs
+++ b/src/server/main.rs
@@ -6,7 +6,7 @@ use dropshot::{ApiDescription, ConfigDropshot, ConfigLogging, ConfigLoggingLevel
 use salaryman::service::{Service, ServiceConf};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use tokio::{fs::read_to_string, sync::Mutex};
+use tokio::{fs::read_to_string, sync::RwLock};
 
 use std::{
     net::{IpAddr, SocketAddr},
@@ -16,7 +16,8 @@ use std::{
 
 use crate::context::{SalarymanDContext, SalarymanService};
 use crate::endpoints::{
-    endpoint_get_service, endpoint_get_services, endpoint_post_stdin, endpoint_restart_service,
+    endpoint_get_config, endpoint_get_config_save, endpoint_get_service, endpoint_get_services,
+    endpoint_post_service, endpoint_post_stdin, endpoint_put_config, endpoint_restart_service,
     endpoint_start_service, endpoint_stop_service,
 };
 
@@ -107,26 +108,35 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
         args.port
     };
     let bind = SocketAddr::new(addr, port);
-    let mut services: Vec<Arc<SalarymanService>> = Vec::new();
+    let services: RwLock<Vec<Arc<SalarymanService>>> = RwLock::new(Vec::new());
     for i in 0..conf.service.len() {
-        services.push(Arc::new(SalarymanService::from_parts(
+        let mut lock = services.write().await;
+        lock.push(Arc::new(SalarymanService::from_parts(
             conf.service[i].clone(),
-            Arc::new(Mutex::new(Service::from_conf(&conf.service[i]))),
+            Arc::new(RwLock::new(Service::from_conf(&conf.service[i]))),
         )));
+        drop(lock);
     }
-    for i in 0..services.len() {
-        if services[i].config.autostart {
-            let mut lock = services[i].service.lock().await;
-            lock.start().await?;
-            lock.scan_stdout().await?;
-            lock.scan_stderr().await?;
+    let lock = services.write().await;
+    for i in 0..lock.len() {
+        if lock[i].config.autostart {
+            let mut l = lock[i].service.write().await;
+            l.start().await?;
+            l.scan_stdout().await?;
+            l.scan_stderr().await?;
+            drop(l);
         }
     }
+    drop(lock);
     let log_conf = ConfigLogging::StderrTerminal {
         level: ConfigLoggingLevel::Info,
     };
     let log = log_conf.to_logger("smd")?;
-    let ctx = Arc::new(SalarymanDContext::from_vec(services));
+    let ctx = Arc::new(SalarymanDContext::from_parts(
+        services,
+        args.config,
+        Arc::new(RwLock::new(conf)),
+    ));
     let config = ConfigDropshot {
         bind_address: bind,
         ..Default::default()
@@ -138,6 +148,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
     api.register(endpoint_stop_service)?;
     api.register(endpoint_restart_service)?;
     api.register(endpoint_post_stdin)?;
+    api.register(endpoint_post_service)?;
+    api.register(endpoint_get_config)?;
+    api.register(endpoint_put_config)?;
+    api.register(endpoint_get_config_save)?;
     api.openapi("Salaryman", semver::Version::new(1, 0, 0))
         .write(&mut std::io::stdout())?;
     let server = ServerBuilder::new(api, ctx.clone(), log)
diff --git a/src/smd/context.rs b/src/smd/context.rs
deleted file mode 100644
index 5d8038c..0000000
--- a/src/smd/context.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use salaryman::service::{Service, ServiceConf};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use std::sync::Arc;
-use tokio::sync::Mutex;
-use uuid::Uuid;
-
-pub struct SalarymanService {
-    pub config: ServiceConf,
-    pub service: Arc<Mutex<Service>>,
-}
-impl SalarymanService {
-    pub fn new() -> Self {
-        Self {
-            config: ServiceConf::new(),
-            service: Arc::new(Mutex::new(Service::new())),
-        }
-    }
-    pub fn from_parts(config: ServiceConf, service: Arc<Mutex<Service>>) -> Self {
-        Self { config, service }
-    }
-}
-
-pub struct SalarymanDContext {
-    pub services: Vec<Arc<SalarymanService>>,
-}
-impl SalarymanDContext {
-    pub fn new() -> Self {
-        Self {
-            services: Vec::new(),
-        }
-    }
-    pub fn from_vec(services: Vec<Arc<SalarymanService>>) -> Self {
-        Self { services }
-    }
-}
-
-#[derive(Serialize, Deserialize, JsonSchema, Debug)]
-pub struct StdinBuffer {
-    pub stdin: String,
-    pub endl: Option<bool>,
-}
-
-#[derive(Serialize, Deserialize, JsonSchema, Debug)]
-pub struct ServicePath {
-    pub service_uuid: Uuid,
-}
diff --git a/src/smd/endpoints.rs b/src/smd/endpoints.rs
deleted file mode 100644
index f0ba0ea..0000000
--- a/src/smd/endpoints.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-use super::context::{SalarymanDContext, SalarymanService, ServicePath, StdinBuffer};
-use dropshot::{HttpError, HttpResponseOk, Path, RequestContext, TypedBody, endpoint};
-use salaryman::service::ServiceConf;
-use std::sync::Arc;
-
-#[endpoint {
-    method = GET,
-    path = "/service",
-}]
-pub async fn endpoint_get_services(
-    rqctx: RequestContext<Arc<SalarymanDContext>>,
-) -> Result<HttpResponseOk<Vec<ServiceConf>>, HttpError> {
-    let ret = {
-        let ctx = rqctx.context();
-        let mut v: Vec<ServiceConf> = Vec::new();
-        for i in 0..ctx.services.len() {
-            v.push(ctx.services[i].config.clone());
-        }
-        v
-    };
-    Ok(HttpResponseOk(ret))
-}
-#[endpoint {
-    method = GET,
-    path = "/service/{service_uuid}",
-}]
-pub async fn endpoint_get_service(
-    rqctx: RequestContext<Arc<SalarymanDContext>>,
-    path_params: Path<ServicePath>,
-) -> Result<HttpResponseOk<ServiceConf>, HttpError> {
-    let u = path_params.into_inner().service_uuid;
-    let ctx = rqctx.context();
-    let mut service: Option<Arc<SalarymanService>> = None;
-    for i in 0..ctx.services.len() {
-        if ctx.services[i].config.uuid == u {
-            service = Some(ctx.services[i].clone());
-        } else {
-            continue;
-        }
-    }
-    let s = match service {
-        Some(s) => s.config.clone(),
-        None => {
-            return Err(HttpError::for_unavail(
-                None,
-                String::from("Service Not Found"),
-            ));
-        }
-    };
-    Ok(HttpResponseOk(s))
-}
-#[endpoint {
-    method = GET,
-    path = "/service/{service_uuid}/start",
-}]
-pub async fn endpoint_start_service(
-    rqctx: RequestContext<Arc<SalarymanDContext>>,
-    path_params: Path<ServicePath>,
-) -> Result<HttpResponseOk<()>, HttpError> {
-    let u = path_params.into_inner().service_uuid;
-    let ctx = rqctx.context();
-    let mut service: Option<Arc<SalarymanService>> = None;
-    for i in 0..ctx.services.len() {
-        if ctx.services[i].config.uuid == u {
-            service = Some(ctx.services[i].clone());
-        } else {
-            continue;
-        }
-    }
-    match service {
-        Some(s) => {
-            let mut lock = s.service.lock().await;
-            match lock.start_with_output().await {
-                Ok(_) => (),
-                Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
-            }
-        }
-        None => {
-            return Err(HttpError::for_unavail(
-                None,
-                String::from("Service Not Found"),
-            ));
-        }
-    };
-    Ok(HttpResponseOk(()))
-}
-#[endpoint {
-    method = GET,
-    path = "/service/{service_uuid}/stop",
-}]
-pub async fn endpoint_stop_service(
-    rqctx: RequestContext<Arc<SalarymanDContext>>,
-    path_params: Path<ServicePath>,
-) -> Result<HttpResponseOk<()>, HttpError> {
-    let u = path_params.into_inner().service_uuid;
-    let ctx = rqctx.context();
-    let mut service: Option<Arc<SalarymanService>> = None;
-    for i in 0..ctx.services.len() {
-        if ctx.services[i].config.uuid == u {
-            service = Some(ctx.services[i].clone());
-        } else {
-            continue;
-        }
-    }
-    match service {
-        Some(s) => {
-            let mut lock = s.service.lock().await;
-            match lock.stop().await {
-                Ok(_) => (),
-                Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
-            }
-        }
-        None => {
-            return Err(HttpError::for_unavail(
-                None,
-                String::from("Service Not Found"),
-            ));
-        }
-    };
-    Ok(HttpResponseOk(()))
-}
-#[endpoint {
-    method = GET,
-    path = "/service/{service_uuid}/restart",
-}]
-pub async fn endpoint_restart_service(
-    rqctx: RequestContext<Arc<SalarymanDContext>>,
-    path_params: Path<ServicePath>,
-) -> Result<HttpResponseOk<()>, HttpError> {
-    let u = path_params.into_inner().service_uuid;
-    let ctx = rqctx.context();
-    let mut service: Option<Arc<SalarymanService>> = None;
-    for i in 0..ctx.services.len() {
-        if ctx.services[i].config.uuid == u {
-            service = Some(ctx.services[i].clone());
-        } else {
-            continue;
-        }
-    }
-    match service {
-        Some(s) => {
-            let mut lock = s.service.lock().await;
-            match lock.restart_with_output().await {
-                Ok(_) => (),
-                Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
-            }
-        }
-        None => {
-            return Err(HttpError::for_unavail(
-                None,
-                String::from("Service Not Found"),
-            ));
-        }
-    };
-    Ok(HttpResponseOk(()))
-}
-#[endpoint {
-    method = PUT,
-    path = "/service/{service_uuid}/write"
-}]
-pub async fn endpoint_post_stdin(
-    rqctx: RequestContext<Arc<SalarymanDContext>>,
-    path_params: Path<ServicePath>,
-    update: TypedBody<StdinBuffer>,
-) -> Result<HttpResponseOk<()>, HttpError> {
-    let ctx = rqctx.context();
-    let stdin_str = update.into_inner();
-    let u = path_params.into_inner().service_uuid;
-    let mut service: Option<Arc<SalarymanService>> = None;
-    for i in 0..ctx.services.len() {
-        if ctx.services[i].config.uuid == u {
-            service = Some(ctx.services[i].clone());
-        } else {
-            continue;
-        }
-    }
-    match service {
-        Some(s) => {
-            let mut lock = s.service.lock().await;
-            if lock.started().await {
-                let b = if let Some(endl) = stdin_str.endl {
-                    if endl {
-                        lock.writeln_stdin(stdin_str.stdin.clone()).await //TODO: PROPERLY HANDLE ERROR!
-                    } else {
-                        lock.write_stdin(stdin_str.stdin.clone()).await //TODO: PROPERLY HANDLE ERROR!
-                    }
-                } else {
-                    lock.writeln_stdin(stdin_str.stdin.clone()).await //TODO: PROPERLY HANDLE ERROR!
-                };
-                match b {
-                    Ok(_) => (),
-                    Err(e) => return Err(HttpError::for_internal_error(e.to_string())),
-                }
-            }
-            drop(lock);
-        }
-        None => {
-            return Err(HttpError::for_unavail(
-                None,
-                String::from("Service Not Found"),
-            ));
-        }
-    }
-    Ok(HttpResponseOk(()))
-}