Compare commits
25 Commits
main
...
asyncversi
| Author | SHA1 | Date |
|---|---|---|
|
|
8d32426962 | |
|
|
5f52900ded | |
|
|
5200ba7723 | |
|
|
7a615c4489 | |
|
|
4c44c3531b | |
|
|
04cb9e29d9 | |
|
|
95dece7fed | |
|
|
ec86134356 | |
|
|
8c334ec751 | |
|
|
2f894d7729 | |
|
|
e64ddafeb6 | |
|
|
3caa73304f | |
|
|
85cc1d5a39 | |
|
|
792054749f | |
|
|
aa792c736c | |
|
|
5fa0855b9b | |
|
|
62b5ab9303 | |
|
|
983308aabe | |
|
|
49d7628ad5 | |
|
|
3a03ceb8ce | |
|
|
b8f65ecf9d | |
|
|
5f42d33040 | |
|
|
84096c2d98 | |
|
|
317d2a84c9 | |
|
|
8336412fb3 |
|
|
@ -1,8 +1,11 @@
|
||||||
/target
|
target/
|
||||||
.vscode
|
.vscode
|
||||||
main
|
main
|
||||||
main.exe
|
main.exe
|
||||||
main.pdb
|
main.pdb
|
||||||
.idea/
|
.idea/
|
||||||
restarts.log
|
restarts.log
|
||||||
nohup.out
|
nohup.out
|
||||||
|
server.log
|
||||||
|
control_panel.log
|
||||||
|
onefetch.out
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "VelNetServerRust"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono"
|
|
||||||
version = "0.4.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
"time",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.117"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-integer"
|
|
||||||
version = "0.1.44"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-traits"
|
|
||||||
version = "0.2.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.36"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-xid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.136"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.136"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.79"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.86"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-xid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "time"
|
|
||||||
version = "0.1.44"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"wasi",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-xid"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasi"
|
|
||||||
version = "0.10.0+wasi-snapshot-preview1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
|
||||||
|
|
||||||
[[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"
|
|
||||||
71
README.md
|
|
@ -1,23 +1,56 @@
|
||||||
# VelNetServerRust
|
# VelNet Server
|
||||||
|
|
||||||
|
This basic, single-file relay server is designed to be used for network games, and is similar to Photon Realtime in design. It is written in Rust, with a single-threaded, non-blocking design and does not rely on any network frameworks (pure TCP/UDP). A Unity/C# client implementation can be found in our [VelNetUnity](https://github.com/velaboratory/VelNetUnity) repository.
|
||||||
|
|
||||||
|
Like Photon, there is no built-in persistence of rooms or data. Rooms are created when the first client joins and destroyed when the last client leaves.
|
||||||
|
|
||||||
|
The only game logic implemented by the server is that of a "master client", which is an easier way to negotiate a leader in a room that can perform room level operations.
|
||||||
|
|
||||||
|
The "group" functionality is used to specify specific clients to communicate with. Note, these client ids can bridge across rooms.
|
||||||
|
|
||||||
|
The server supports both TCP and UDP transports.
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
1. Get a linoox server
|
### Option 1: Pull from Docker Hub
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run -p 5000:5000 -p 5000:5000/udp velaboratory/velnet
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run -p 5050:5000 -p 5050:5000/udp --name velnet velaboratory/velnet
|
||||||
|
```
|
||||||
|
To run on a different port and change the name of the container.
|
||||||
|
|
||||||
|
### Option 2: Use docker-compose
|
||||||
|
|
||||||
|
Runs both the control panel and the server.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
to run, and
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose stop
|
||||||
|
```
|
||||||
|
to stop.
|
||||||
|
|
||||||
|
This builds the images from the local data in the folder, and doesn't pull anything from Docker Hub.
|
||||||
|
|
||||||
|
### Option 3: Run Rust natively
|
||||||
|
|
||||||
|
1. Get a linoox server (also runs fine on Windows & OSX, but the instructions below are for Linux)
|
||||||
2. Clone this repo
|
2. Clone this repo
|
||||||
3. Install rust: `sudo apt install cargo`
|
3. Edit `config.json` to an open port on your firewall
|
||||||
5. Build: `cargo build --release`
|
4. Modify the `user` field in `control-panel/config.json` to be your username.
|
||||||
6. Run: `sudo ./target/release/VelNetServerRust`
|
5. Install rust through using rustup: https://rustup.rs/
|
||||||
7. Or run in the background so that it doesn't quit when you leave ssh: `nohup sudo ./target/release/VelNetServerRust`. You'll have to install `nohup` with apt.
|
6. Install: `sudo ./install.sh`
|
||||||
|
7. Run server: `sudo systemctl start velnet`
|
||||||
|
8. Run control panel: `sudo systemctl start velnet-control-panel`
|
||||||
## Running with control panel server
|
9. Install tuptime: `cargo install tuptime`
|
||||||
|
9. Install onefetch: `cargo install onefetch`
|
||||||
You don't need to do both of these steps. The control panel runs the other server.
|
|
||||||
|
|
||||||
1. Get a linoox server
|
|
||||||
2. Clone this repo
|
|
||||||
3. Install rust: `sudo apt install cargo`
|
|
||||||
3. Switch to control panel: `cd control-panel`
|
|
||||||
5. Build: `cargo build --release`
|
|
||||||
6. `touch ../nohup.out ../restarts.log`
|
|
||||||
6. Run the web server in the background so that it doesn't quit when you leave ssh: `nohup sudo ./target/release/control-panel`. You'll have to install `nohup` with apt.
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"port":5000,
|
|
||||||
"tcp_timeout":30,
|
|
||||||
"tcp_send_buffer":100000
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
/target
|
|
||||||
.vscode
|
|
||||||
main
|
|
||||||
main.exe
|
|
||||||
main.pdb
|
|
||||||
.idea/
|
|
||||||
onefetch.out
|
|
||||||
|
|
@ -19,6 +19,29 @@ dependencies = [
|
||||||
"tokio-util 0.7.0",
|
"tokio-util 0.7.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-files"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e04dcf7654254676d434b0285e2298d577ed4826f67f536e7a39bb0f64721164"
|
||||||
|
dependencies = [
|
||||||
|
"actix-http",
|
||||||
|
"actix-service",
|
||||||
|
"actix-utils",
|
||||||
|
"actix-web",
|
||||||
|
"askama_escape",
|
||||||
|
"bitflags",
|
||||||
|
"bytes",
|
||||||
|
"derive_more",
|
||||||
|
"futures-core",
|
||||||
|
"http-range",
|
||||||
|
"log",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
|
|
@ -62,8 +85,8 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6"
|
checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote 1.0.15",
|
||||||
"syn",
|
"syn 1.0.86",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -176,9 +199,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc"
|
checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-router",
|
"actix-router",
|
||||||
"proc-macro2",
|
"proc-macro2 1.0.36",
|
||||||
"quote",
|
"quote 1.0.15",
|
||||||
"syn",
|
"syn 1.0.86",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -222,6 +245,12 @@ dependencies = [
|
||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_escape"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
@ -333,16 +362,6 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "control-panel"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"actix-web",
|
|
||||||
"handlebars",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
@ -388,6 +407,17 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "default-env"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f753eb82d29277e79efc625e84aecacfd4851ee50e05a8573a4740239a77bfd3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 0.4.30",
|
||||||
|
"quote 0.6.13",
|
||||||
|
"syn 0.15.44",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.17"
|
version = "0.99.17"
|
||||||
|
|
@ -395,10 +425,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"proc-macro2",
|
"proc-macro2 1.0.36",
|
||||||
"quote",
|
"quote 1.0.15",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"syn",
|
"syn 1.0.86",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -420,6 +450,12 @@ dependencies = [
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.30"
|
version = "0.8.30"
|
||||||
|
|
@ -569,6 +605,12 @@ version = "0.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
|
|
@ -589,6 +631,12 @@ dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
|
@ -622,6 +670,15 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
@ -715,6 +772,16 @@ version = "0.3.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
|
@ -849,9 +916,9 @@ checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_meta",
|
"pest_meta",
|
||||||
"proc-macro2",
|
"proc-macro2 1.0.36",
|
||||||
"quote",
|
"quote 1.0.15",
|
||||||
"syn",
|
"syn 1.0.86",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -883,13 +950,22 @@ version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "0.4.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.36"
|
version = "1.0.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -898,13 +974,22 @@ version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "0.6.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 0.4.30",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2 1.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -972,6 +1057,12 @@ dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
|
|
@ -1014,9 +1105,9 @@ version = "1.0.136"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2 1.0.36",
|
||||||
"quote",
|
"quote 1.0.15",
|
||||||
"syn",
|
"syn 1.0.86",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1074,6 +1165,17 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simplelog"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"termcolor",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
|
@ -1096,15 +1198,66 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2 1.0.36",
|
||||||
|
"quote 1.0.15",
|
||||||
|
"rustversion",
|
||||||
|
"syn 1.0.86",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "0.15.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 0.4.30",
|
||||||
|
"quote 0.6.13",
|
||||||
|
"unicode-xid 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.86"
|
version = "1.0.86"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2 1.0.36",
|
||||||
"quote",
|
"quote 1.0.15",
|
||||||
"unicode-xid",
|
"unicode-xid 0.2.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "systemctl"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3134cbab79000bee5ed4d71331d2c9071db897f8369e3e3089b6be85cc781d1"
|
||||||
|
dependencies = [
|
||||||
|
"default-env",
|
||||||
|
"itertools",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1218,6 +1371,15 @@ version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
|
|
@ -1233,6 +1395,12 @@ dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
@ -1251,6 +1419,20 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "velnet_control_panel"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"actix-files",
|
||||||
|
"actix-web",
|
||||||
|
"handlebars",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simplelog",
|
||||||
|
"systemctl",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
[package]
|
[package]
|
||||||
name = "control-panel"
|
name = "velnet_control_panel"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4"
|
actix-web = "4.1.0"
|
||||||
handlebars = { version = "4.2.1", features = ["dir_source"] }
|
handlebars = { version = "4.2.1", features = ["dir_source"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
simplelog = "^0.12.0"
|
||||||
|
log = "*"
|
||||||
|
systemctl = "0.1.7"
|
||||||
|
actix-files = "0.6.1"
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
FROM rust:1.64 as build
|
||||||
|
|
||||||
|
# 1. Create a new empty shell project
|
||||||
|
RUN USER=root cargo new --bin velnet_control_panel
|
||||||
|
WORKDIR /velnet_control_panel
|
||||||
|
|
||||||
|
# 2. Copy our manifests
|
||||||
|
COPY ./Cargo.lock ./Cargo.lock
|
||||||
|
COPY ./Cargo.toml ./Cargo.toml
|
||||||
|
|
||||||
|
# 3. Build only the dependencies to cache them
|
||||||
|
RUN cargo build --release && rm src/*.rs
|
||||||
|
|
||||||
|
# 4. Now that the dependency is built, copy your source code
|
||||||
|
COPY ./src ./src
|
||||||
|
|
||||||
|
# 5. Build for release.
|
||||||
|
RUN rm ./target/release/deps/velnet_control_panel*
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
# our final base
|
||||||
|
FROM rust:1.64-slim
|
||||||
|
WORKDIR /velnet_control_panel
|
||||||
|
|
||||||
|
# RUN apt-get update && apt-get install -y extra-runtime-dependencies && rm -rf /var/lib/apt/lists/*
|
||||||
|
RUN apt update && apt install -y tuptime
|
||||||
|
|
||||||
|
# copy the build artifact from the build stage
|
||||||
|
COPY --from=build /velnet_control_panel/target/release/velnet_control_panel .
|
||||||
|
|
||||||
|
# Copy the config files and helper scripts
|
||||||
|
COPY static static
|
||||||
|
COPY config.json .
|
||||||
|
COPY onefetch_file.sh .
|
||||||
|
COPY git_pull.sh .
|
||||||
|
COPY compile_server.sh .
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# run
|
||||||
|
CMD ["./velnet_control_panel"]
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
sudo -u "$1" bash -c "~/.cargo/bin/cargo build --release"
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
"port": 8080,
|
"port": 8080,
|
||||||
"log_file": "/home/ntsfranz/Documents/VelNetServerRust/nohup.out",
|
"user": "ntsfranz",
|
||||||
"server_dir": "/home/ntsfranz/Documents/VelNetServerRust/"
|
"server_log_file": "../logs/server.log",
|
||||||
}
|
"control_panel_log_file": "control_panel.log",
|
||||||
|
"server_dir": "../",
|
||||||
|
"handlebars_dev_mode": true
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
sudo -u "$1" bash -c "git pull"
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
sudo -u ubuntu bash -c "onefetch > onefetch.out"
|
#!/bin/bash
|
||||||
|
|
||||||
|
sudo -u "$1" bash -c "~/.cargo/bin/onefetch > onefetch.out"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
docker stop velnet_control_panel
|
||||||
|
docker build -t velaboratory/velnet_control_panel .
|
||||||
|
docker rm velnet_control_panel
|
||||||
|
docker run -d -p 8080:8080 --name velnet_control_panel velaboratory/velnet_control_panel
|
||||||
|
|
@ -9,57 +9,64 @@ use serde_json::json;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{prelude::*, BufReader};
|
use std::io::{prelude::*, BufReader};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::{File, OpenOptions};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command};
|
||||||
use std::env;
|
use std::cmp;
|
||||||
|
use std::error::Error;
|
||||||
|
use simplelog;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct Config {
|
struct ControlPanelConfig {
|
||||||
port: u16,
|
port: u16,
|
||||||
log_file: String,
|
user: String,
|
||||||
server_dir: String
|
server_log_file: String,
|
||||||
|
control_panel_log_file: String,
|
||||||
|
server_dir: String,
|
||||||
|
handlebars_dev_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
|
fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
|
||||||
let file = File::open(filename).expect("no such file");
|
if filename.as_ref().is_file() {
|
||||||
let buf = BufReader::new(file);
|
let file = File::open(filename).expect("no such file");
|
||||||
buf.lines()
|
let buf = BufReader::new(file);
|
||||||
.map(|l| l.expect("Could not parse line"))
|
buf.lines()
|
||||||
.collect()
|
.map(|l| l.expect("Could not parse line"))
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
|
async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
|
||||||
|
let config = read_config_file().unwrap();
|
||||||
//read the config file
|
|
||||||
let file = fs::read_to_string("config.json").unwrap();
|
|
||||||
let config: Config = serde_json::from_str(&file).unwrap();
|
|
||||||
|
|
||||||
//read the log file
|
//read the log file
|
||||||
let log_file = lines_from_file(config.log_file);
|
let log_file = lines_from_file(config.server_log_file);
|
||||||
let restarts_log = lines_from_file("../restarts.log");
|
|
||||||
|
|
||||||
let uptime = Command::new("uptime")
|
// let restarts_log = lines_from_file(config.restarts_log_file);
|
||||||
|
|
||||||
|
let uptime = Command::new("tuptime")
|
||||||
.output()
|
.output()
|
||||||
.expect("failed to execute process");
|
.expect("failed to execute process");
|
||||||
|
|
||||||
let _onefetch = Command::new("sh")
|
// let _onefetch = Command::new("sh")
|
||||||
.arg("onefetch_file.sh")
|
// .arg("onefetch_file.sh")
|
||||||
.output()
|
// .arg(config.user)
|
||||||
.expect("failed");
|
// .output()
|
||||||
|
// .expect("failed");
|
||||||
let onefetch = fs::read_to_string("onefetch.out").unwrap();
|
|
||||||
|
|
||||||
|
// let onefetch = fs::read_to_string("onefetch.out").unwrap();
|
||||||
|
|
||||||
let data = json!({
|
let data = json!({
|
||||||
"log_output": log_file,
|
"log_output": &log_file[(cmp::max((log_file.len() as i64) - 1000, 0) as usize)..],
|
||||||
"restarts_output": restarts_log,
|
// "restarts_output": &restarts_log[(cmp::max((restarts_log.len() as i64) - 1000, 0) as usize)..],
|
||||||
"uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)),
|
"uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)),
|
||||||
//"onefetch": format!("{}", String::from_utf8_lossy(&onefetch.stdout))
|
//"onefetch": format!("{}", String::from_utf8_lossy(&onefetch.stdout))
|
||||||
"onefetch": onefetch
|
"onefetch": ""
|
||||||
});
|
});
|
||||||
let body = hb.render("index", &data).unwrap();
|
let body = hb.render("index", &data).unwrap();
|
||||||
|
|
||||||
|
|
@ -70,23 +77,33 @@ async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
|
||||||
async fn restart_server() -> HttpResponse {
|
async fn restart_server() -> HttpResponse {
|
||||||
|
|
||||||
// Restart the process
|
// Restart the process
|
||||||
let _output = Command::new("sh")
|
// let _output = Command::new("sh")
|
||||||
.arg("../run.sh")
|
// .arg("../restart_server.sh")
|
||||||
.stdin(Stdio::null())
|
// .stdin(Stdio::null())
|
||||||
//.stdout(Stdio::null())
|
// .spawn();
|
||||||
//.stderr(Stdio::null())
|
// let _output = Command::new("systemd")
|
||||||
.spawn();
|
// .arg("restart")
|
||||||
|
// .arg("velnet")
|
||||||
|
// .stdin(Stdio::null())
|
||||||
|
// .spawn();
|
||||||
|
|
||||||
HttpResponse::Ok().body("DONE")
|
systemctl::restart("velnet.service").unwrap();
|
||||||
|
|
||||||
|
let ret = systemctl::status("velnet.service").unwrap();
|
||||||
|
|
||||||
|
HttpResponse::Ok().body(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[get("/git_pull")]
|
#[get("/git_pull")]
|
||||||
async fn git_pull() -> HttpResponse {
|
async fn git_pull() -> HttpResponse {
|
||||||
|
|
||||||
let output = Command::new("git")
|
//read the config file
|
||||||
.arg("pull")
|
let config = read_config_file().unwrap();
|
||||||
|
|
||||||
|
let output = Command::new("sh")
|
||||||
|
.arg("git_pull.sh")
|
||||||
|
.arg(config.user)
|
||||||
.output()
|
.output()
|
||||||
.expect("failed to execute process");
|
.expect("failed to execute process");
|
||||||
|
|
||||||
|
|
@ -98,39 +115,63 @@ async fn git_pull() -> HttpResponse {
|
||||||
async fn compile() -> HttpResponse {
|
async fn compile() -> HttpResponse {
|
||||||
|
|
||||||
//read the config file
|
//read the config file
|
||||||
let file = fs::read_to_string("config.json").unwrap();
|
let config = read_config_file().unwrap();
|
||||||
let config: Config = serde_json::from_str(&file).unwrap();
|
|
||||||
|
|
||||||
let orig_dir = std::env::current_dir().unwrap();
|
log::debug!("before");
|
||||||
|
|
||||||
let root = Path::new(&config.server_dir);
|
let output = Command::new("sh")
|
||||||
let _new_dir = env::set_current_dir(&root);
|
.arg("compile_server.sh")
|
||||||
|
.arg(config.user)
|
||||||
print!("before");
|
|
||||||
|
|
||||||
let output = Command::new("cargo")
|
|
||||||
.arg("build")
|
|
||||||
.arg("--release")
|
|
||||||
.output()
|
.output()
|
||||||
.expect("failed to execute process");
|
.expect("failed to execute process");
|
||||||
|
|
||||||
print!("after");
|
log::debug!("after");
|
||||||
|
|
||||||
let _new_dir = env::set_current_dir(orig_dir);
|
|
||||||
|
|
||||||
HttpResponse::Ok().body(output.stdout)
|
HttpResponse::Ok().body(output.stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_config_file() -> Result<ControlPanelConfig, Box<dyn Error>> {
|
||||||
|
// Open the file in read-only mode with buffer.
|
||||||
|
let file = File::open("config.json")?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
let config = serde_json::from_reader(reader)?;
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
|
|
||||||
|
//read the config file
|
||||||
|
let config: ControlPanelConfig = read_config_file().unwrap();
|
||||||
|
|
||||||
|
let f = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(config.control_panel_log_file)
|
||||||
|
.unwrap();
|
||||||
|
simplelog::CombinedLogger::init(
|
||||||
|
vec![
|
||||||
|
simplelog::TermLogger::new(simplelog::LevelFilter::Info, simplelog::Config::default(), simplelog::TerminalMode::Mixed, simplelog::ColorChoice::Auto),
|
||||||
|
simplelog::WriteLogger::new(simplelog::LevelFilter::Debug, simplelog::Config::default(), f),
|
||||||
|
]
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
log::info!("Starting control panel server.");
|
||||||
|
|
||||||
|
|
||||||
let mut handlebars = Handlebars::new();
|
let mut handlebars = Handlebars::new();
|
||||||
handlebars.set_dev_mode(true);
|
handlebars.set_dev_mode(config.handlebars_dev_mode);
|
||||||
handlebars.register_templates_directory(".hbs", "./static/templates").unwrap();
|
handlebars.register_templates_directory(".hbs", "./static/templates").unwrap();
|
||||||
let handlebars_ref = web::Data::new(handlebars);
|
let handlebars_ref = web::Data::new(handlebars);
|
||||||
|
|
||||||
|
|
||||||
|
log::info!("http://127.0.0.1:{}", config.port);
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(error_handlers())
|
.wrap(error_handlers())
|
||||||
|
|
@ -139,10 +180,11 @@ async fn main() -> io::Result<()> {
|
||||||
.service(restart_server)
|
.service(restart_server)
|
||||||
.service(git_pull)
|
.service(git_pull)
|
||||||
.service(compile)
|
.service(compile)
|
||||||
|
.service(actix_files::Files::new("/static", "./static"))
|
||||||
})
|
})
|
||||||
.bind(("127.0.0.1", 8080))?
|
.bind(("0.0.0.0", config.port))?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom error handlers, to return HTML responses when an error occurs.
|
// Custom error handlers, to return HTML responses when an error occurs.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
use actix_web::body::BoxBody;
|
||||||
|
use actix_web::dev::ServiceResponse;
|
||||||
|
use actix_web::http::header::ContentType;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers};
|
||||||
|
use actix_web::{get, web, App, HttpResponse, HttpServer, Result};
|
||||||
|
use handlebars::Handlebars;
|
||||||
|
use serde_json::json;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{prelude::*, BufReader};
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::Path;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use std::env;
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Config {
|
||||||
|
port: u16,
|
||||||
|
log_file: String,
|
||||||
|
server_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
|
||||||
|
let file = File::open(filename).expect("no such file");
|
||||||
|
let buf = BufReader::new(file);
|
||||||
|
buf.lines()
|
||||||
|
.map(|l| l.expect("Could not parse line"))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
|
||||||
|
|
||||||
|
//read the config file
|
||||||
|
let file = fs::read_to_string("config.json").unwrap();
|
||||||
|
let config: Config = serde_json::from_str(&file).unwrap();
|
||||||
|
|
||||||
|
//read the log file
|
||||||
|
let log_file = lines_from_file(config.log_file);
|
||||||
|
|
||||||
|
let restarts_log = lines_from_file("../restarts.log");
|
||||||
|
|
||||||
|
let uptime = Command::new("uptime")
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute process");
|
||||||
|
|
||||||
|
let _onefetch = Command::new("sh")
|
||||||
|
.arg("onefetch_file.sh")
|
||||||
|
.output()
|
||||||
|
.expect("failed");
|
||||||
|
|
||||||
|
let onefetch = fs::read_to_string("onefetch.out").unwrap();
|
||||||
|
|
||||||
|
let data = json!({
|
||||||
|
"log_output": log_file,
|
||||||
|
"restarts_output": restarts_log,
|
||||||
|
"uptime": format!("{}", String::from_utf8_lossy(&uptime.stdout)),
|
||||||
|
//"onefetch": format!("{}", String::from_utf8_lossy(&onefetch.stdout))
|
||||||
|
"onefetch": onefetch
|
||||||
|
});
|
||||||
|
let body = hb.render("index", &data).unwrap();
|
||||||
|
|
||||||
|
HttpResponse::Ok().body(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/nohup.out")]
|
||||||
|
async fn nohup() -> HttpResponse {
|
||||||
|
|
||||||
|
//read the config file
|
||||||
|
let file = fs::read_to_string("config.json").unwrap();
|
||||||
|
let config: Config = serde_json::from_str(&file).unwrap();
|
||||||
|
|
||||||
|
//read the log file
|
||||||
|
let log_file = lines_from_file(config.log_file);
|
||||||
|
|
||||||
|
// get the last 100 lines
|
||||||
|
let all_lines = log_file.as_slice()[cmp::max(log_file.len()-100, 0)..].to_vec().join("\n");
|
||||||
|
|
||||||
|
HttpResponse::Ok().body(all_lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/restarts.log2")]
|
||||||
|
async fn restarts() -> HttpResponse {
|
||||||
|
|
||||||
|
//read the log file
|
||||||
|
let log_file = lines_from_file("../restarts.log");
|
||||||
|
|
||||||
|
// get the last 100 lines
|
||||||
|
let all_lines = log_file.as_slice()[cmp::max(log_file.len()-100, 0)..].to_vec().join("\n");
|
||||||
|
|
||||||
|
HttpResponse::Ok().body(all_lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/restart_server")]
|
||||||
|
async fn restart_server() -> HttpResponse {
|
||||||
|
|
||||||
|
// Restart the process
|
||||||
|
let _output = Command::new("sh")
|
||||||
|
.arg("../run.sh")
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
//.stdout(Stdio::null())
|
||||||
|
//.stderr(Stdio::null())
|
||||||
|
.spawn();
|
||||||
|
|
||||||
|
HttpResponse::Ok().body("DONE")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[get("/git_pull")]
|
||||||
|
async fn git_pull() -> HttpResponse {
|
||||||
|
|
||||||
|
let output = Command::new("git")
|
||||||
|
.arg("pull")
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute process");
|
||||||
|
|
||||||
|
HttpResponse::Ok().body(output.stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[get("/compile")]
|
||||||
|
async fn compile() -> HttpResponse {
|
||||||
|
|
||||||
|
//read the config file
|
||||||
|
let file = fs::read_to_string("config.json").unwrap();
|
||||||
|
let config: Config = serde_json::from_str(&file).unwrap();
|
||||||
|
|
||||||
|
let orig_dir = std::env::current_dir().unwrap();
|
||||||
|
|
||||||
|
let root = Path::new(&config.server_dir);
|
||||||
|
let _new_dir = env::set_current_dir(&root);
|
||||||
|
|
||||||
|
print!("before");
|
||||||
|
|
||||||
|
let output = Command::new("cargo")
|
||||||
|
.arg("build")
|
||||||
|
.arg("--release")
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute process");
|
||||||
|
|
||||||
|
print!("after");
|
||||||
|
|
||||||
|
let _new_dir = env::set_current_dir(orig_dir);
|
||||||
|
|
||||||
|
HttpResponse::Ok().body(output.stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> io::Result<()> {
|
||||||
|
let mut handlebars = Handlebars::new();
|
||||||
|
handlebars.set_dev_mode(true);
|
||||||
|
handlebars.register_templates_directory(".hbs", "./static/templates").unwrap();
|
||||||
|
let handlebars_ref = web::Data::new(handlebars);
|
||||||
|
|
||||||
|
HttpServer::new(move || {
|
||||||
|
App::new()
|
||||||
|
.wrap(error_handlers())
|
||||||
|
.app_data(handlebars_ref.clone())
|
||||||
|
.service(index)
|
||||||
|
.service(restart_server)
|
||||||
|
.service(git_pull)
|
||||||
|
.service(compile)
|
||||||
|
.service(nohup)
|
||||||
|
.service(restarts)
|
||||||
|
})
|
||||||
|
.bind(("127.0.0.1", 8080))?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom error handlers, to return HTML responses when an error occurs.
|
||||||
|
fn error_handlers() -> ErrorHandlers<BoxBody> {
|
||||||
|
ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error handler for a 404 Page not found error.
|
||||||
|
fn not_found<B>(res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<BoxBody>> {
|
||||||
|
let response = get_error_response(&res, "Page not found");
|
||||||
|
Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
|
||||||
|
res.into_parts().0,
|
||||||
|
response.map_into_left_body(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic error handler.
|
||||||
|
fn get_error_response<B>(res: &ServiceResponse<B>, error: &str) -> HttpResponse<BoxBody> {
|
||||||
|
let request = res.request();
|
||||||
|
|
||||||
|
// Provide a fallback to a simple plain text response in case an error occurs during the
|
||||||
|
// rendering of the error page.
|
||||||
|
let fallback = |e: &str| {
|
||||||
|
HttpResponse::build(res.status())
|
||||||
|
.content_type(ContentType::plaintext())
|
||||||
|
.body(e.to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
let hb = request
|
||||||
|
.app_data::<web::Data<Handlebars>>()
|
||||||
|
.map(|t| t.get_ref());
|
||||||
|
match hb {
|
||||||
|
Some(hb) => {
|
||||||
|
let data = json!({
|
||||||
|
"error": error,
|
||||||
|
"status_code": res.status().as_str()
|
||||||
|
});
|
||||||
|
let body = hb.render("error", &data);
|
||||||
|
|
||||||
|
match body {
|
||||||
|
Ok(body) => HttpResponse::build(res.status())
|
||||||
|
.content_type(ContentType::html())
|
||||||
|
.body(body),
|
||||||
|
Err(_) => fallback(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => fallback(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#b91d47</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
||||||
|
After Width: | Height: | Size: 815 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="300.000000pt" height="300.000000pt" viewBox="0 0 300.000000 300.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,300.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M605 1500 l600 -600 97 0 98 0 0 600 0 600 -100 0 -100 0 0 -452 0
|
||||||
|
-453 -453 453 -452 452 -145 0 -145 0 600 -600z"/>
|
||||||
|
<path d="M1500 2000 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
|
||||||
|
<path d="M2200 1500 l0 -600 398 2 397 3 3 98 3 97 -301 0 -300 0 -2 498 -3
|
||||||
|
497 -97 3 -98 3 0 -601z"/>
|
||||||
|
<path d="M1500 1500 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
|
||||||
|
<path d="M1500 1000 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 977 B |
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"short_name": "",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-256x256.png",
|
||||||
|
"sizes": "256x256",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,24 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicons/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="/static/favicons/site.webmanifest">
|
||||||
|
<link rel="mask-icon" href="/static/favicons/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
|
<meta name="msapplication-TileColor" content="#b91d47">
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
<title>VelNet Control Panel</title>
|
<title>VelNet Control Panel</title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.bottom-scroller {
|
.bottom-scroller {
|
||||||
height: 30em;
|
max-height: 40em;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
|
|
@ -22,97 +31,80 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<img src="https://vel.engr.uga.edu/wp-content/uploads/elementor/thumbs/4dLogo-1-oaxk7d2mcrutzo0dhqah6qlgpp2c1tvihad1dchrc0.png" style="float: right;">
|
<img src="https://vel.engr.uga.edu/wp-content/uploads/elementor/thumbs/4dLogo-1-oaxk7d2mcrutzo0dhqah6qlgpp2c1tvihad1dchrc0.png"
|
||||||
<h1 class="title">
|
style="float: right;" alt="vel logo">
|
||||||
VelNet Control Panel
|
<h1 class="title">
|
||||||
</h1>
|
VelNet Control Panel
|
||||||
<p class="subtitle">
|
</h1>
|
||||||
Log output and utilites for <strong>VelNet</strong>
|
<p class="subtitle">
|
||||||
</p>
|
Log output and utilities for <strong>VelNet</strong>
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
{{!-- <section>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<button class="button" id="restart-button">Restart Server</button>
|
<button class="button" id="restart-button">Restart Server</button>
|
||||||
<button class="button" id="pull-button">Git Pull</button>
|
<button class="button" id="pull-button">Git Pull</button>
|
||||||
<button class="button" id="compile-button">Compile</button>
|
<button class="button" id="compile-button">Compile</button>
|
||||||
|
</div>
|
||||||
|
<pre style="font-size: 0.8em;">{{uptime}}</pre>
|
||||||
|
</div>
|
||||||
|
</section> --}}
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<nav class="panel">
|
||||||
|
<p class="panel-heading">
|
||||||
|
Server Log
|
||||||
|
<code>server.log</code>
|
||||||
|
|
||||||
|
<!-- <label for="debug_checkbox">DEBUG</label> -->
|
||||||
|
<!-- <input id="debug_checkbox" type="checkbox"> -->
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="content bottom-scroller">
|
||||||
|
<code class="log-output">
|
||||||
|
{{#each log_output}}
|
||||||
|
<div class="panel-block">
|
||||||
|
{{this}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="block">Uptime: {{uptime}}</div>
|
|
||||||
<pre>{{onefetch}}</pre>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section">
|
</nav>
|
||||||
<div class="container">
|
|
||||||
<nav class="panel">
|
|
||||||
<p class="panel-heading">
|
|
||||||
Server Log
|
|
||||||
<code>nohup.out</code>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="content bottom-scroller">
|
</div>
|
||||||
<code class="log-output">
|
|
||||||
<div>
|
|
||||||
{{#each log_output}}
|
|
||||||
<div class="panel-block">
|
|
||||||
{{this}}
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</nav>
|
</section>
|
||||||
|
|
||||||
<nav class="panel">
|
<script>
|
||||||
<p class="panel-heading">
|
"use strict";
|
||||||
Server Restarts
|
document.getElementById('restart-button').addEventListener('click', _ => {
|
||||||
<code>restarts.log</code>
|
fetch('/restart_server').then(_ => {
|
||||||
</p>
|
setTimeout(location.reload(), 1000);
|
||||||
|
|
||||||
<div class="content bottom-scroller">
|
|
||||||
<code class="log-output">
|
|
||||||
<div>
|
|
||||||
{{#each restarts_output}}
|
|
||||||
<div class="panel-block">
|
|
||||||
{{this}}
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
"use strict";
|
|
||||||
document.getElementById('restart-button').addEventListener('click', c=> {
|
|
||||||
fetch('/restart_server').then(r=> {
|
|
||||||
setTimeout(location.reload(), 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
document.getElementById('pull-button').addEventListener('click', c=> {
|
|
||||||
fetch('/git_pull').then(r=> {
|
});
|
||||||
location.reload();
|
document.getElementById('pull-button').addEventListener('click', _ => {
|
||||||
});
|
fetch('/git_pull').then(_ => {
|
||||||
|
location.reload();
|
||||||
});
|
});
|
||||||
document.getElementById('compile-button').addEventListener('click', c=> {
|
|
||||||
fetch('/compile').then(r=> {
|
});
|
||||||
location.reload();
|
document.getElementById('compile-button').addEventListener('click', _ => {
|
||||||
});
|
fetch('/compile').then(_ => {
|
||||||
|
location.reload();
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
version: "3.9"
|
||||||
|
services:
|
||||||
|
control-panel:
|
||||||
|
build: control-panel
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- logs-volume:/logs
|
||||||
|
server:
|
||||||
|
build: server
|
||||||
|
ports:
|
||||||
|
- "5000:5000/tcp"
|
||||||
|
- "5000:5000/udp"
|
||||||
|
volumes:
|
||||||
|
- logs-volume:/logs
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
logs-volume:
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Building VelNet Server..."
|
||||||
|
cargo build --release
|
||||||
|
(
|
||||||
|
cd control-panel || exit
|
||||||
|
echo "Building VelNet Control Panel..."
|
||||||
|
cargo build --release
|
||||||
|
)
|
||||||
|
echo "Copying systemd configs..."
|
||||||
|
|
||||||
|
(
|
||||||
|
sudo cp velnet.service /etc/systemd/system/
|
||||||
|
service="/etc/systemd/system/velnet.service"
|
||||||
|
wd="$(pwd)"
|
||||||
|
exe="$(pwd)/target/release/velnet_server"
|
||||||
|
sudo sed -i -e "s+WorkingDirectory=+WorkingDirectory=$wd+g" "$service"
|
||||||
|
sudo sed -i -e "s+ExecStart=+ExecStart=$exe+g" "$service"
|
||||||
|
systemctl enable velnet
|
||||||
|
systemctl restart velnet
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
cd control-panel || exit
|
||||||
|
sudo cp ../velnet.service /etc/systemd/system/velnet-control-panel.service
|
||||||
|
service="/etc/systemd/system/velnet-control-panel.service"
|
||||||
|
wd="$(pwd)"
|
||||||
|
exe="$(pwd)/target/release/velnet_control_panel"
|
||||||
|
sudo sed -i -e "s+WorkingDirectory=+WorkingDirectory=$wd+g" "$service"
|
||||||
|
sudo sed -i -e "s+ExecStart=+ExecStart=$exe+g" "$service"
|
||||||
|
systemctl enable velnet-control-panel
|
||||||
|
systemctl restart velnet-control-panel
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "'velnet' and 'velnet-control-panel' systemd services enabled."
|
||||||
15
run.sh
|
|
@ -1,15 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
BASEDIR=$(dirname "$0")
|
|
||||||
echo $(date +"%Y-%m-%dT%T.%3N%z") >> $BASEDIR/restarts.log
|
|
||||||
echo "Before: " >> $BASEDIR/restarts.log
|
|
||||||
pgrep -f "VelNetServerRust -v2" >> $BASEDIR/restarts.log
|
|
||||||
#pgrep -f "VelNetServerRust -v2" | xargs sudo kill >> $BASEDIRE/restarts.log
|
|
||||||
#kill $(pgrep -f "VelNetServerRust -v2")
|
|
||||||
pkill -f "VelNetServerRust -v2"
|
|
||||||
echo "\nAfter: " >> $BASEDIR/restarts.log
|
|
||||||
pgrep -f "VelNetServerRust -v2" >> $BASEDIR/restarts.log
|
|
||||||
echo "\n"
|
|
||||||
(cd $BASEDIR && nohup ./target/release/VelNetServerRust -v2 >> nohup.out &)
|
|
||||||
# nohup "$BASEDIR/target/release/VelNetServerRust" &
|
|
||||||
echo "Starting..." >> $BASEDIR/restarts.log
|
|
||||||
|
|
@ -0,0 +1,798 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-attributes"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"event-listener",
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-executor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
|
||||||
|
dependencies = [
|
||||||
|
"async-task",
|
||||||
|
"concurrent-queue",
|
||||||
|
"fastrand",
|
||||||
|
"futures-lite",
|
||||||
|
"once_cell",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-global-executor"
|
||||||
|
version = "2.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c026b7e44f1316b567ee750fea85103f87fcb80792b860e979f221259796ca0a"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-executor",
|
||||||
|
"async-io",
|
||||||
|
"async-mutex",
|
||||||
|
"blocking",
|
||||||
|
"futures-lite",
|
||||||
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-io"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"futures-lite",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"parking",
|
||||||
|
"polling",
|
||||||
|
"slab",
|
||||||
|
"socket2",
|
||||||
|
"waker-fn",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-lock"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-mutex"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-notify"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8356f653934a654817bceada97a857ef8d68ab8992753d23ed8e8ccd5fc8fa31"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-std"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952"
|
||||||
|
dependencies = [
|
||||||
|
"async-attributes",
|
||||||
|
"async-channel",
|
||||||
|
"async-global-executor",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"gloo-timers",
|
||||||
|
"kv-log-macro",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-task"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic-waker"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blocking"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-task",
|
||||||
|
"atomic-waker",
|
||||||
|
"fastrand",
|
||||||
|
"futures-lite",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cache-padded"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.73"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||||
|
|
||||||
|
[[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.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"time 0.1.44",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concurrent-queue"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
|
||||||
|
dependencies = [
|
||||||
|
"cache-padded",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctor"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "2.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
|
||||||
|
dependencies = [
|
||||||
|
"instant",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-lite"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"memchr",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
"waker-fn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gloo-timers"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instant"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kv-log-macro"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.117"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"value-bag",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polling"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wepoll-ffi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.136"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.136"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simplelog"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48dfff04aade74dd495b007c831cd6f4e0cee19c344dd9dc0884c0289b70a786"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"termcolor",
|
||||||
|
"time 0.3.11",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "value-bag"
|
||||||
|
version = "1.0.0-alpha.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f"
|
||||||
|
dependencies = [
|
||||||
|
"ctor",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "velnet_server"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-notify",
|
||||||
|
"async-std",
|
||||||
|
"chrono",
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simplelog",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "waker-fn"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wepoll-ffi"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "VelNetServerRust"
|
name = "velnet_server"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
@ -9,3 +9,8 @@ edition = "2021"
|
||||||
chrono = "*"
|
chrono = "*"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
async-std = { version = "1.6", features = ["attributes"]}
|
||||||
|
futures = "*"
|
||||||
|
async-notify = "*"
|
||||||
|
simplelog = "^0.12.0"
|
||||||
|
log = "*"
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
FROM rust:1.64 as build
|
||||||
|
|
||||||
|
# 1. Create a new empty shell project
|
||||||
|
RUN USER=root cargo new --bin velnet_server
|
||||||
|
WORKDIR /velnet_server
|
||||||
|
|
||||||
|
# 2. Copy our manifests
|
||||||
|
COPY ./Cargo.lock ./Cargo.lock
|
||||||
|
COPY ./Cargo.toml ./Cargo.toml
|
||||||
|
|
||||||
|
# 3. Build only the dependencies to cache them
|
||||||
|
RUN cargo build --release && rm src/*.rs
|
||||||
|
|
||||||
|
# 4. Now that the dependency is built, copy your source code
|
||||||
|
COPY ./src ./src
|
||||||
|
|
||||||
|
# 5. Build for release.
|
||||||
|
RUN rm ./target/release/deps/velnet_server*
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
# our final base
|
||||||
|
FROM debian:buster-slim
|
||||||
|
|
||||||
|
# copy the build artifact from the build stage
|
||||||
|
COPY --from=build /velnet_server/target/release/velnet_server .
|
||||||
|
COPY config.json .
|
||||||
|
|
||||||
|
EXPOSE 5000/tcp
|
||||||
|
EXPOSE 5000/udp
|
||||||
|
|
||||||
|
# run
|
||||||
|
CMD ["./velnet_server"]
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"port": 5000,
|
||||||
|
"tcp_timeout": 30,
|
||||||
|
"log_file": "logs/server.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,993 @@
|
||||||
|
use async_std::prelude::*;
|
||||||
|
use async_std::future;
|
||||||
|
use async_std::net::TcpListener;
|
||||||
|
use async_std::net::TcpStream;
|
||||||
|
use async_std::net::UdpSocket;
|
||||||
|
use async_notify::Notify;
|
||||||
|
use futures::stream::StreamExt;
|
||||||
|
use futures::join;
|
||||||
|
use futures::select;
|
||||||
|
use futures::pin_mut;
|
||||||
|
use futures::future::FutureExt;
|
||||||
|
use async_std::net::{IpAddr, SocketAddr};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::rc::{Rc};
|
||||||
|
use std::cell::{RefCell};
|
||||||
|
use std::fs;
|
||||||
|
use chrono::Local;
|
||||||
|
use std::time::Duration;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::BufReader;
|
||||||
|
use std::ptr;
|
||||||
|
use simplelog::*;
|
||||||
|
|
||||||
|
enum ToClientTCPMessageType {
|
||||||
|
LoggedIn = 0,
|
||||||
|
RoomList = 1,
|
||||||
|
PlayerJoined = 2,
|
||||||
|
DataMessage = 3,
|
||||||
|
MasterMessage = 4,
|
||||||
|
YouJoined = 5,
|
||||||
|
PlayerLeft = 6,
|
||||||
|
YouLeft = 7,
|
||||||
|
RoomData = 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FromClientTCPMessageType {
|
||||||
|
LogIn = 0,
|
||||||
|
GetRooms = 1,
|
||||||
|
JoinRoom = 2,
|
||||||
|
SendMessageOthersUnbuffered = 3,
|
||||||
|
SendMessageAllUnbuffered = 4,
|
||||||
|
SendMessageGroupUnbuffered = 5,
|
||||||
|
CreateGroup = 6,
|
||||||
|
SendMessageOthersBuffered = 7,
|
||||||
|
SendMessageAllBuffered = 8,
|
||||||
|
GetRoomData = 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ToClientUDPMessageType {
|
||||||
|
Connected = 0,
|
||||||
|
DataMessage = ToClientTCPMessageType::DataMessage as isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FromClientUDPMessageType {
|
||||||
|
Connect = 0,
|
||||||
|
SendMessageOthersUnbuffered = FromClientTCPMessageType::SendMessageOthersUnbuffered as isize,
|
||||||
|
SendMessageAllUnbuffered = FromClientTCPMessageType::SendMessageAllUnbuffered as isize,
|
||||||
|
SendMessageGroupUnbuffered = FromClientTCPMessageType::SendMessageGroupUnbuffered as isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Client {
|
||||||
|
logged_in: bool,
|
||||||
|
id: u32,
|
||||||
|
username: String,
|
||||||
|
room_name: String,
|
||||||
|
application: String,
|
||||||
|
groups: HashMap<String, Vec<Rc<RefCell<Client>>>>,
|
||||||
|
ip: IpAddr,
|
||||||
|
udp_port: u16,
|
||||||
|
message_queue: Vec<u8>,
|
||||||
|
message_queue_udp: Vec<Vec<u8>>,
|
||||||
|
notify: Rc<Notify>,
|
||||||
|
notify_udp: Rc<Notify>,
|
||||||
|
is_master: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Room {
|
||||||
|
name: String,
|
||||||
|
clients: HashMap<u32, Rc<RefCell<Client>>>,
|
||||||
|
master_client: Rc<RefCell<Client>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct VelNetConfig {
|
||||||
|
port: u16,
|
||||||
|
tcp_timeout: u64,
|
||||||
|
log_file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[async_std::main]
|
||||||
|
async fn main() {
|
||||||
|
let config = read_config_file().unwrap();
|
||||||
|
|
||||||
|
let f = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(config.log_file)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
CombinedLogger::init(
|
||||||
|
vec![
|
||||||
|
TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed, ColorChoice::Auto),
|
||||||
|
WriteLogger::new(LevelFilter::Debug, Config::default(), f),
|
||||||
|
]
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
log::info!("----------------------");
|
||||||
|
log::info!("VelNet Server Starting");
|
||||||
|
|
||||||
|
// read the config file
|
||||||
|
let foo = fs::read_to_string("config.json").unwrap();
|
||||||
|
let config: VelNetConfig = serde_json::from_str(&foo).unwrap();
|
||||||
|
log::info!("Running on port: {}", config.port);
|
||||||
|
|
||||||
|
let tcp_listener = TcpListener::bind(format!("0.0.0.0:{}", config.port)).await.unwrap();
|
||||||
|
let udp_socket = Rc::new(RefCell::new(UdpSocket::bind(format!("0.0.0.0:{}", config.port)).await.unwrap()));
|
||||||
|
|
||||||
|
let clients: Rc<RefCell<HashMap<u32, Rc<RefCell<Client>>>>> = Rc::new(RefCell::new(HashMap::new()));
|
||||||
|
let rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>> = Rc::new(RefCell::new(HashMap::new()));
|
||||||
|
let last_client_id = Rc::new(RefCell::new(0));
|
||||||
|
|
||||||
|
let tcp_future = tcp_listener
|
||||||
|
.incoming()
|
||||||
|
.for_each_concurrent(None, |tcpstream| process_client(tcpstream.unwrap(), udp_socket.clone(), clients.clone(), rooms.clone(), last_client_id.clone(), &config));
|
||||||
|
let udp_future = process_udp(udp_socket.clone(), clients.clone(), rooms.clone());
|
||||||
|
join!(tcp_future,udp_future);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn read_config_file() -> Result<VelNetConfig, Box<dyn Error>> {
|
||||||
|
// Open the file in read-only mode with buffer.
|
||||||
|
let file = fs::File::open("config.json")?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
let config = serde_json::from_reader(reader)?;
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_client(socket: TcpStream, udp_socket: Rc<RefCell<UdpSocket>>, clients: Rc<RefCell<HashMap<u32, Rc<RefCell<Client>>>>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, last_client_id: Rc<RefCell<u32>>, config: &VelNetConfig) {
|
||||||
|
log::info!("Started TCP");
|
||||||
|
|
||||||
|
match socket.set_nodelay(true) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("Could not set no delay");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//socket.set_read_timeout(Some(time::Duration::new(config.tcp_timeout,0))).unwrap();
|
||||||
|
//socket.set_write_timeout(Some(time::Duration::new(config.tcp_timeout,0))).unwrap();
|
||||||
|
|
||||||
|
let my_id;
|
||||||
|
{
|
||||||
|
let mut reference = last_client_id.borrow_mut();
|
||||||
|
*reference = *reference + 1;
|
||||||
|
my_id = *reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let client_notify = Rc::new(Notify::new());
|
||||||
|
let client_notify_udp = Rc::new(Notify::new());
|
||||||
|
|
||||||
|
let ip;
|
||||||
|
|
||||||
|
match socket.peer_addr() {
|
||||||
|
Ok(p) => ip = p.ip(),
|
||||||
|
Err(_) => { return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = Rc::new(RefCell::new(Client {
|
||||||
|
id: my_id,
|
||||||
|
username: String::from(""),
|
||||||
|
logged_in: false,
|
||||||
|
room_name: String::from(""),
|
||||||
|
application: String::from(""),
|
||||||
|
groups: HashMap::new(),
|
||||||
|
ip: ip,
|
||||||
|
udp_port: 0 as u16,
|
||||||
|
message_queue: vec![],
|
||||||
|
message_queue_udp: vec![],
|
||||||
|
notify: client_notify.clone(),
|
||||||
|
notify_udp: client_notify_udp.clone(),
|
||||||
|
is_master: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
log::info!("Spawned client handler = {}", my_id);
|
||||||
|
|
||||||
|
{
|
||||||
|
let temp_client = client.clone();
|
||||||
|
let mut clients_temp = clients.borrow_mut();
|
||||||
|
clients_temp.insert(my_id, temp_client);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let read_async = client_read(client.clone(), socket.clone(), clients.clone(), rooms.clone(), config.tcp_timeout * 1000).fuse();
|
||||||
|
let write_async = client_write(client.clone(), socket, client_notify.clone(), config.tcp_timeout * 1000).fuse();
|
||||||
|
let write_async_udp = client_write_udp(client.clone(), udp_socket.clone(), client_notify_udp.clone()).fuse();
|
||||||
|
|
||||||
|
pin_mut!(read_async,write_async,write_async_udp); //not sure why this is necessary, since select
|
||||||
|
|
||||||
|
select! { //
|
||||||
|
() = read_async => log::debug!("read async ended"),
|
||||||
|
() = write_async => log::debug!("write async ended"),
|
||||||
|
() = write_async_udp => log::debug!("write async udp ended")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
client_leave_room(client.clone(), false, rooms.clone());
|
||||||
|
let mut clients_temp = clients.borrow_mut();
|
||||||
|
clients_temp.remove(&client.borrow().id);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
log::info!("Client {} left", client.borrow().id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_timeout(mut socket: &TcpStream, buf: &mut [u8], duration: u64) -> Result<usize, Box<dyn Error>> {
|
||||||
|
|
||||||
|
//this is a read exact function. The buffer passed should be the exact size wanted
|
||||||
|
|
||||||
|
let num_to_read = buf.len();
|
||||||
|
|
||||||
|
let mut num_read = 0;
|
||||||
|
|
||||||
|
while num_read < num_to_read {
|
||||||
|
match future::timeout(Duration::from_millis(duration), socket.read(&mut buf[num_read..])).await {
|
||||||
|
Ok(r) => {
|
||||||
|
match r {
|
||||||
|
Ok(n) if n == 0 => {
|
||||||
|
return Err(format!("{}", "no bytes read"))?;
|
||||||
|
}
|
||||||
|
Ok(n) => {
|
||||||
|
num_read += n;
|
||||||
|
}
|
||||||
|
Err(e) => { return Err(format!("{}", e.to_string()))?; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => { return Err(format!("{}", e.to_string()))?; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(num_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn client_read(client: Rc<RefCell<Client>>, mut socket: TcpStream, clients: Rc<RefCell<HashMap<u32, Rc<RefCell<Client>>>>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, duration: u64) {
|
||||||
|
let mut buf = [0; 1];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match read_timeout(&mut socket, &mut buf, duration).await {
|
||||||
|
Ok(_) => {
|
||||||
|
let t = buf[0];
|
||||||
|
if t == FromClientTCPMessageType::LogIn as u8 { //[0:u8][username.length():u8][username:shortstring][password.length():u8][password:shortstring]
|
||||||
|
match read_login_message(socket.clone(), client.clone(), duration).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("failed to read from socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if t == FromClientTCPMessageType::GetRooms as u8 {//[1:u8]
|
||||||
|
match read_rooms_message(socket.clone(), client.clone(), rooms.clone()).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("failed to read from socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if t == FromClientTCPMessageType::GetRoomData as u8 {
|
||||||
|
match read_roomdata_message(socket.clone(), client.clone(), rooms.clone(), duration).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("failed to read from socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if t == FromClientTCPMessageType::JoinRoom as u8 {//[2:u8][roomname.length():u8][roomname:shortstring]
|
||||||
|
match read_join_message(socket.clone(), client.clone(), rooms.clone(), duration).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("failed to read from socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if t == FromClientTCPMessageType::SendMessageOthersUnbuffered as u8 ||
|
||||||
|
t == FromClientTCPMessageType::SendMessageAllUnbuffered as u8 ||
|
||||||
|
t == FromClientTCPMessageType::SendMessageGroupUnbuffered as u8 ||
|
||||||
|
t == FromClientTCPMessageType::SendMessageOthersBuffered as u8 ||
|
||||||
|
t == FromClientTCPMessageType::SendMessageAllBuffered as u8 { //others,all,group[t:u8][message.length():i32][message:u8array]
|
||||||
|
match read_send_message(socket.clone(), client.clone(), rooms.clone(), t, duration).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("failed to read from socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if t == FromClientTCPMessageType::CreateGroup as u8 { //[t:u8][list.lengthbytes:i32][clients:i32array]
|
||||||
|
match read_group_message(socket.clone(), client.clone(), clients.clone(), duration).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("failed to read from socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
//die...not correct protocol
|
||||||
|
log::error!("Incorrect protocol, killing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("failed to read from socket; err = {:?}", e);
|
||||||
|
//remove the client
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn write_timeout(mut socket: &TcpStream, buf: &[u8], duration: u64) -> Result<usize, Box<dyn Error>> {
|
||||||
|
return match future::timeout(Duration::from_millis(duration), socket.write(buf)).await {
|
||||||
|
Ok(r) => {
|
||||||
|
match r {
|
||||||
|
Ok(n) => {
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
Err(e) => { Err(format!("{}", e.to_string()))? }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => { Err(format!("{}", e.to_string()))? }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn client_write(client: Rc<RefCell<Client>>, mut socket: TcpStream, notify: Rc<Notify>, duration: u64) {
|
||||||
|
|
||||||
|
//wait on messages in my queue
|
||||||
|
loop {
|
||||||
|
notify.notified().await; //there is something to write
|
||||||
|
|
||||||
|
let mut to_write = vec![];
|
||||||
|
{
|
||||||
|
let client_ref = client.borrow();
|
||||||
|
to_write.extend_from_slice(&client_ref.message_queue); //must do this so that the borrow ends
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut client_ref = client.borrow_mut();
|
||||||
|
client_ref.message_queue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
match write_timeout(&mut socket, &to_write, duration).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("failed to write to the tcp socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn client_write_udp(client: Rc<RefCell<Client>>, socket: Rc<RefCell<UdpSocket>>, notify: Rc<Notify>) {
|
||||||
|
loop {
|
||||||
|
notify.notified().await; //there is something to write
|
||||||
|
|
||||||
|
let ip;
|
||||||
|
let port;
|
||||||
|
let mut messages = vec![];
|
||||||
|
{
|
||||||
|
let mut client_ref = client.borrow_mut();
|
||||||
|
ip = client_ref.ip;
|
||||||
|
port = client_ref.udp_port;
|
||||||
|
for msg in client_ref.message_queue_udp.iter() {
|
||||||
|
messages.push(msg.clone());
|
||||||
|
}
|
||||||
|
client_ref.message_queue_udp.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for msg in messages.iter() {
|
||||||
|
let socket = socket.borrow();
|
||||||
|
match socket.send_to(&msg, SocketAddr::new(ip, port)).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("failed to write to the udp socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_udp(socket: Rc<RefCell<UdpSocket>>, clients: Rc<RefCell<HashMap<u32, Rc<RefCell<Client>>>>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>) {
|
||||||
|
let mut buf = [0u8; 1024];
|
||||||
|
loop {
|
||||||
|
let socket = socket.borrow();
|
||||||
|
let res = socket.recv_from(&mut buf).await;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let (packet_size, addr) = res.unwrap();
|
||||||
|
let t = buf[0];
|
||||||
|
if packet_size >= 5 {
|
||||||
|
//get the client id, which has to be sent with every udp message, because you don't know where udp messages are coming from
|
||||||
|
let client_id_bytes = [buf[1], buf[2], buf[3], buf[4]];
|
||||||
|
let client_id = u32::from_be_bytes(client_id_bytes);
|
||||||
|
|
||||||
|
|
||||||
|
if t == FromClientUDPMessageType::Connect as u8 { //1 byte, 0. Nothing else. This is just to establish the udp port, Echos back the same thing sent
|
||||||
|
//connect message, respond back
|
||||||
|
|
||||||
|
|
||||||
|
let clients = clients.borrow();
|
||||||
|
if clients.contains_key(&client_id) {
|
||||||
|
let mut client = clients.get(&client_id).unwrap().borrow_mut();
|
||||||
|
client.udp_port = addr.port(); //set the udp port to send data to
|
||||||
|
client.message_queue_udp.push(vec![0]);
|
||||||
|
client.notify_udp.notify();
|
||||||
|
}
|
||||||
|
} else if t == FromClientUDPMessageType::SendMessageOthersUnbuffered as u8 { //[3:u8][from:i32][contents:u8array] note that it must fit into the packet of 1024 bytes
|
||||||
|
|
||||||
|
|
||||||
|
let clients = clients.borrow();
|
||||||
|
if clients.contains_key(&client_id) {
|
||||||
|
let client = clients.get(&client_id).unwrap().borrow();
|
||||||
|
let rooms_ref = rooms.borrow();
|
||||||
|
if client.room_name != "" {
|
||||||
|
let room = rooms_ref[&client.room_name].borrow();
|
||||||
|
buf[0] = ToClientUDPMessageType::DataMessage as u8; //technically unecessary, unless we change this number
|
||||||
|
for (_k, v) in room.clients.iter() {
|
||||||
|
if *_k != client_id {
|
||||||
|
let mut msg = vec![];
|
||||||
|
let mut v_ref = v.borrow_mut();
|
||||||
|
msg.extend_from_slice(&buf[0..packet_size]);
|
||||||
|
v_ref.message_queue_udp.push(msg);
|
||||||
|
v_ref.notify_udp.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if t == FromClientUDPMessageType::SendMessageAllUnbuffered as u8 { //see above
|
||||||
|
let clients = clients.borrow();
|
||||||
|
if clients.contains_key(&client_id) {
|
||||||
|
let mut client = clients.get(&client_id).unwrap().borrow_mut();
|
||||||
|
let rooms_ref = rooms.borrow();
|
||||||
|
if client.room_name != "" {
|
||||||
|
let room = rooms_ref[&client.room_name].borrow();
|
||||||
|
buf[0] = ToClientUDPMessageType::DataMessage as u8; //technically unecessary, unless we change this number
|
||||||
|
for (_k, v) in room.clients.iter() {
|
||||||
|
if *_k != client_id {
|
||||||
|
let mut msg = vec![];
|
||||||
|
let mut v_ref = v.borrow_mut();
|
||||||
|
msg.extend_from_slice(&buf[0..packet_size]);
|
||||||
|
v_ref.message_queue_udp.push(msg);
|
||||||
|
v_ref.notify_udp.notify();
|
||||||
|
} else {
|
||||||
|
let mut msg = vec![];
|
||||||
|
msg.extend_from_slice(&buf[0..packet_size]);
|
||||||
|
client.message_queue_udp.push(msg);
|
||||||
|
client.notify_udp.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if t == FromClientUDPMessageType::SendMessageGroupUnbuffered as u8 { //[5:byte][from:i32][group.length():u8][message:u8array]
|
||||||
|
//this one is a little different, because we don't send the group in the message, so we have to formulate another message (like a 3 message)
|
||||||
|
//send a message to a group
|
||||||
|
//read the group name
|
||||||
|
|
||||||
|
let group_name_size = buf[5];
|
||||||
|
let message_vec = buf[6..packet_size].to_vec();
|
||||||
|
let (group_name_bytes, message_bytes) = message_vec.split_at(group_name_size as usize);
|
||||||
|
|
||||||
|
let res = String::from_utf8(group_name_bytes.to_vec());
|
||||||
|
match res {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(_) => {
|
||||||
|
log::error!("Could not convert group name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let group_name = res.unwrap();
|
||||||
|
|
||||||
|
let clients = clients.borrow();
|
||||||
|
|
||||||
|
let mut message_to_send = vec![];
|
||||||
|
message_to_send.push(ToClientUDPMessageType::DataMessage as u8);
|
||||||
|
message_to_send.extend([buf[1], buf[2], buf[3], buf[4]]);
|
||||||
|
message_to_send.extend(message_bytes);
|
||||||
|
|
||||||
|
let mut send_to_client = false;
|
||||||
|
if clients.contains_key(&client_id) {
|
||||||
|
{
|
||||||
|
{
|
||||||
|
let client = clients.get(&client_id).unwrap().borrow();
|
||||||
|
if client.groups.contains_key(&group_name) {
|
||||||
|
let clients = client.groups.get(&group_name).unwrap();
|
||||||
|
//we need to form a new message without the group name
|
||||||
|
|
||||||
|
|
||||||
|
for v in clients.iter() {
|
||||||
|
let mut skip_client = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
let v_ref = v.borrow();
|
||||||
|
if v_ref.id == client.id {
|
||||||
|
skip_client = true;
|
||||||
|
send_to_client = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !skip_client {
|
||||||
|
let mut v_ref = v.borrow_mut();
|
||||||
|
v_ref.message_queue_udp.push(message_to_send.clone());
|
||||||
|
v_ref.notify_udp.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if send_to_client {
|
||||||
|
let mut client = clients.get(&client_id).unwrap().borrow_mut();
|
||||||
|
client.message_queue_udp.push(message_to_send.clone());
|
||||||
|
client.notify_udp.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//this is in response to someone asking to login (this is where usernames and passwords would be processed, in theory)
|
||||||
|
async fn read_login_message(mut stream: TcpStream, client: Rc<RefCell<Client>>, duration: u64) -> Result<(), Box<dyn Error>> {
|
||||||
|
//byte,shortstring,byte,shortstring
|
||||||
|
|
||||||
|
let username = read_short_string(&mut stream, duration).await?;
|
||||||
|
let application = read_short_string(&mut stream, duration).await?;
|
||||||
|
log::info!("{}: Got application {} and userid {}", Local::now().format("%Y-%m-%d %H:%M:%S"), application, username);
|
||||||
|
{
|
||||||
|
let mut client = client.borrow_mut();
|
||||||
|
client.username = username;
|
||||||
|
client.application = application;
|
||||||
|
client.logged_in = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut client = client.borrow_mut();
|
||||||
|
let mut write_buf = vec![];
|
||||||
|
write_buf.push(ToClientTCPMessageType::LoggedIn as u8);
|
||||||
|
write_buf.extend_from_slice(&(client.id).to_be_bytes()); //send the client the id
|
||||||
|
client.message_queue.extend(write_buf);
|
||||||
|
client.notify.notify();
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_rooms_message(mut _stream: TcpStream, client: Rc<RefCell<Client>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut write_buf = vec![];
|
||||||
|
write_buf.push(ToClientTCPMessageType::RoomList as u8);
|
||||||
|
//first we need to get the room names
|
||||||
|
|
||||||
|
let rooms = rooms.borrow();
|
||||||
|
let mut client = client.borrow_mut();
|
||||||
|
let mut rooms_vec = vec![];
|
||||||
|
for (k, v) in rooms.iter() {
|
||||||
|
if !k.starts_with(&client.application.to_string()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut iter = k.chars();
|
||||||
|
|
||||||
|
iter.by_ref().nth(client.application.len());
|
||||||
|
let application_stripped_room = iter.as_str();
|
||||||
|
|
||||||
|
let room_string = format!("{}:{}", application_stripped_room, v.borrow().clients.len());
|
||||||
|
rooms_vec.push(room_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rooms_message = rooms_vec.join(",");
|
||||||
|
let message_bytes = rooms_message.as_bytes();
|
||||||
|
let message_len = message_bytes.len() as u32;
|
||||||
|
write_buf.extend_from_slice(&(message_len).to_be_bytes());
|
||||||
|
write_buf.extend_from_slice(message_bytes);
|
||||||
|
client.message_queue.extend_from_slice(&write_buf);
|
||||||
|
client.notify.notify();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_join_message(mut stream: TcpStream, client: Rc<RefCell<Client>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, duration: u64) -> Result<(), Box<dyn Error>> {
|
||||||
|
//byte,shortstring
|
||||||
|
|
||||||
|
let short_room_name = read_short_string(&mut stream, duration).await?;
|
||||||
|
let extended_room_name;
|
||||||
|
let mut leave_room = false;
|
||||||
|
{
|
||||||
|
let client_ref = client.borrow();
|
||||||
|
extended_room_name = format!("{}_{}", client_ref.application, short_room_name);
|
||||||
|
if client_ref.room_name != "" {
|
||||||
|
leave_room = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if leave_room {
|
||||||
|
//todo
|
||||||
|
client_leave_room(client.clone(), true, rooms.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut client_ref = client.borrow_mut();
|
||||||
|
let mut rooms_ref = rooms.borrow_mut();
|
||||||
|
|
||||||
|
if short_room_name.trim() == "" || short_room_name == "-1" {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rooms_ref.contains_key(&extended_room_name) { //new room, must create it
|
||||||
|
let map: HashMap<u32, Rc<RefCell<Client>>> = HashMap::new();
|
||||||
|
let r = Rc::new(RefCell::new(Room {
|
||||||
|
name: extended_room_name.to_string(),
|
||||||
|
clients: map,
|
||||||
|
master_client: client.clone(), //client is the master, since they joined first
|
||||||
|
}));
|
||||||
|
client_ref.is_master = true;
|
||||||
|
rooms_ref.insert(String::from(&extended_room_name), r);
|
||||||
|
log::info!("{}: {}: New room {} created", Local::now().format("%Y-%m-%d %H:%M:%S"), client_ref.application, &extended_room_name);
|
||||||
|
} else {
|
||||||
|
client_ref.is_master = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//the room is guaranteed to exist now, so this call can't fail
|
||||||
|
let mut room_to_join = rooms_ref[&extended_room_name].borrow_mut();
|
||||||
|
room_to_join.clients.insert(client_ref.id, client.clone());
|
||||||
|
log::info!("{}: {}: Client {} joined {}", Local::now().format("%Y-%m-%d %H:%M:%S"), client_ref.application, client_ref.id, &extended_room_name);
|
||||||
|
|
||||||
|
client_ref.room_name = extended_room_name; //we create an option and assign it back to the room
|
||||||
|
|
||||||
|
//send a join message to everyone in the room (except the client)
|
||||||
|
for (_k, v) in room_to_join.clients.iter() {
|
||||||
|
if *_k != client_ref.id {
|
||||||
|
send_client_join_message(v, client_ref.id, &short_room_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//send a join message to the client that has all of the ids in the room
|
||||||
|
let mut ids_in_room = vec![];
|
||||||
|
for (_k, _v) in room_to_join.clients.iter() {
|
||||||
|
ids_in_room.push(*_k);
|
||||||
|
}
|
||||||
|
send_you_joined_message(&mut *client_ref, ids_in_room, &short_room_name);
|
||||||
|
|
||||||
|
if client_ref.is_master {
|
||||||
|
let temp = client_ref.id;
|
||||||
|
send_client_master_message(&mut *client_ref, temp);
|
||||||
|
} else {
|
||||||
|
send_client_master_message(&mut *client_ref, room_to_join.master_client.borrow().id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_roomdata_message(mut stream: TcpStream, client: Rc<RefCell<Client>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, duration: u64) -> Result<(), Box<dyn Error>> {
|
||||||
|
//type, room_name
|
||||||
|
//will respond with type, numclients u32, id1 u32, name_len u8, name_bytes ...
|
||||||
|
|
||||||
|
//read the room name and append the client application
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let short_room_name = read_short_string(&mut stream, duration).await?;
|
||||||
|
let mut client_ref = client.borrow_mut();
|
||||||
|
let room_name = format!("{}_{}", client_ref.application, short_room_name);
|
||||||
|
|
||||||
|
//we need to access the rooms list
|
||||||
|
let rooms_ref = rooms.borrow();
|
||||||
|
if rooms_ref.contains_key(&room_name) {
|
||||||
|
let room = rooms_ref.get(&room_name).unwrap();
|
||||||
|
//form and send the message
|
||||||
|
let mut write_buf = vec![];
|
||||||
|
write_buf.push(ToClientTCPMessageType::RoomData as u8);
|
||||||
|
|
||||||
|
|
||||||
|
let roomname_bytes = short_room_name.as_bytes();
|
||||||
|
write_buf.push(roomname_bytes.len() as u8);
|
||||||
|
write_buf.extend_from_slice(&roomname_bytes);
|
||||||
|
|
||||||
|
let clients = &room.borrow().clients;
|
||||||
|
write_buf.extend_from_slice(&(clients.len() as u32).to_be_bytes());
|
||||||
|
for (_k, c) in clients.iter() {
|
||||||
|
write_buf.extend_from_slice(&(_k).to_be_bytes());
|
||||||
|
|
||||||
|
if *_k != client_ref.id {
|
||||||
|
let c_ref = c.borrow();
|
||||||
|
let username_bytes = c_ref.username.as_bytes();
|
||||||
|
write_buf.push(username_bytes.len() as u8);
|
||||||
|
write_buf.extend_from_slice(&username_bytes);
|
||||||
|
} else {
|
||||||
|
let username_bytes = client_ref.username.as_bytes();
|
||||||
|
write_buf.push(username_bytes.len() as u8);
|
||||||
|
write_buf.extend_from_slice(&username_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client_ref.message_queue.extend_from_slice(&write_buf);
|
||||||
|
client_ref.notify.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_send_message(mut stream: TcpStream, client: Rc<RefCell<Client>>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, message_type: u8, duration: u64) -> Result<(), Box<dyn Error>> {
|
||||||
|
//4 byte length, array
|
||||||
|
//this is a message for everyone in the room (maybe)
|
||||||
|
let to_send = read_vec(&mut stream, duration).await?;
|
||||||
|
if message_type == FromClientTCPMessageType::SendMessageOthersUnbuffered as u8 {
|
||||||
|
send_room_message(client, &to_send, rooms.clone(), false, false);
|
||||||
|
} else if message_type == FromClientTCPMessageType::SendMessageAllUnbuffered as u8 {
|
||||||
|
send_room_message(client, &to_send, rooms.clone(), true, false);
|
||||||
|
} else if message_type == FromClientTCPMessageType::SendMessageOthersBuffered as u8 { //ordered
|
||||||
|
send_room_message(client, &to_send, rooms.clone(), false, true);
|
||||||
|
} else if message_type == FromClientTCPMessageType::SendMessageAllBuffered as u8 { //ordered
|
||||||
|
send_room_message(client, &to_send, rooms.clone(), true, true);
|
||||||
|
} else if message_type == FromClientTCPMessageType::SendMessageGroupUnbuffered as u8 {
|
||||||
|
let group = read_short_string(&mut stream, duration).await?;
|
||||||
|
send_group_message(client, &to_send, &group);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_group_message(mut stream: TcpStream, client: Rc<RefCell<Client>>, clients: Rc<RefCell<HashMap<u32, Rc<RefCell<Client>>>>>, duration: u64) -> Result<(), Box<dyn Error>> {
|
||||||
|
let group = read_short_string(&mut stream, duration).await?;
|
||||||
|
let id_bytes = read_vec(&mut stream, duration).await?;
|
||||||
|
let num = id_bytes.len();
|
||||||
|
|
||||||
|
let mut client_ref = client.borrow_mut();
|
||||||
|
|
||||||
|
let clients_ref = clients.borrow();
|
||||||
|
let mut group_clients = vec![];
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
if i >= num {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let mut slice = [0u8; 4];
|
||||||
|
slice[0] = id_bytes[i];
|
||||||
|
slice[1] = id_bytes[i + 1];
|
||||||
|
slice[2] = id_bytes[i + 2];
|
||||||
|
slice[3] = id_bytes[i + 3]; //probably a better way to do this
|
||||||
|
let id = u32::from_be_bytes(slice);
|
||||||
|
|
||||||
|
|
||||||
|
match clients_ref.get(&id) {
|
||||||
|
Some(client) => { group_clients.push(client.clone()); }
|
||||||
|
None => () //not there, so don't add it
|
||||||
|
}
|
||||||
|
|
||||||
|
i = i + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete the group if it exists
|
||||||
|
if client_ref.groups.contains_key(&group) {
|
||||||
|
client_ref.groups.remove(&group); //ensures the client references go away
|
||||||
|
}
|
||||||
|
|
||||||
|
client_ref.groups.insert(group.clone(), group_clients);
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_leave_room(client: Rc<RefCell<Client>>, send_to_client: bool, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>) {
|
||||||
|
//first remove the client from the room they are in
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut client_ref = client.borrow_mut();
|
||||||
|
let roomname = String::from(client_ref.room_name.clone());
|
||||||
|
|
||||||
|
if roomname == "" { //client not in room, leave
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut new_master_id = 0;
|
||||||
|
{
|
||||||
|
log::info!("{}: {}: Client {} in room, leaving", Local::now().format("%Y-%m-%d %H:%M:%S"), client_ref.application, client_ref.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let mut destroy_room = false;
|
||||||
|
{
|
||||||
|
let rooms_ref = rooms.borrow_mut();
|
||||||
|
let mut room_ref = rooms_ref.get(&roomname).unwrap().borrow_mut();
|
||||||
|
|
||||||
|
for (_k, v) in room_ref.clients.iter() {
|
||||||
|
if *_k != client_ref.id {
|
||||||
|
send_client_left_message(v, client_ref.id, &roomname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if send_to_client && client_ref.room_name != "" {
|
||||||
|
send_you_left_message(&mut *client_ref, &roomname);
|
||||||
|
}
|
||||||
|
|
||||||
|
room_ref.clients.remove(&client_ref.id); //remove the client from that list in the room
|
||||||
|
if room_ref.clients.len() == 0 {
|
||||||
|
destroy_room = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if the room is empty, destroy it as well
|
||||||
|
let mut rooms_ref = rooms.borrow_mut();
|
||||||
|
if destroy_room {
|
||||||
|
rooms_ref.remove(&roomname);
|
||||||
|
log::info!("{}: {}: Destroyed room {}", Local::now().format("%Y-%m-%d %H:%M:%S"), client_ref.application, &roomname)
|
||||||
|
} else if client_ref.is_master { //we need to change the master!
|
||||||
|
let mut room_ref = rooms_ref.get(&roomname).unwrap().borrow_mut();
|
||||||
|
for (_k, v) in room_ref.clients.iter() {
|
||||||
|
if *_k != client_ref.id {
|
||||||
|
new_master_id = v.borrow().id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("{}: {}: Changing master to {}", Local::now().format("%Y-%m-%d %H:%M:%S"), client_ref.application, new_master_id);
|
||||||
|
|
||||||
|
for (_k, v) in room_ref.clients.iter() {
|
||||||
|
let mut c = v.borrow_mut();
|
||||||
|
send_client_master_message(&mut *c, new_master_id);
|
||||||
|
}
|
||||||
|
room_ref.master_client = room_ref.clients.get(&new_master_id).unwrap().clone();
|
||||||
|
room_ref.master_client.borrow_mut().is_master = true;
|
||||||
|
client_ref.is_master = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut client_ref = client.borrow_mut();
|
||||||
|
client_ref.room_name = String::from("");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_you_left_message(client_ref: &mut Client, room: &str) {
|
||||||
|
let mut write_buf = vec![];
|
||||||
|
write_buf.push(ToClientTCPMessageType::YouLeft as u8);
|
||||||
|
write_buf.push(room.as_bytes().len() as u8);
|
||||||
|
write_buf.extend_from_slice(room.as_bytes());
|
||||||
|
client_ref.message_queue.extend_from_slice(&write_buf);
|
||||||
|
client_ref.notify.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_client_left_message(to: &Rc<RefCell<Client>>, from: u32, room: &str) {
|
||||||
|
let mut write_buf = vec![];
|
||||||
|
write_buf.push(ToClientTCPMessageType::PlayerLeft as u8);
|
||||||
|
write_buf.extend_from_slice(&(from).to_be_bytes()); //send everyone that the client id left the room
|
||||||
|
write_buf.push(room.as_bytes().len() as u8);
|
||||||
|
write_buf.extend_from_slice(room.as_bytes());
|
||||||
|
let mut client_ref = to.borrow_mut();
|
||||||
|
client_ref.message_queue.extend_from_slice(&write_buf);
|
||||||
|
client_ref.notify.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_client_join_message(to: &Rc<RefCell<Client>>, from: u32, room: &str) {
|
||||||
|
//2u8, person_id_u32, room_name_len_u8, room_name_bytes
|
||||||
|
let mut write_buf = vec![];
|
||||||
|
write_buf.push(ToClientTCPMessageType::PlayerJoined as u8);
|
||||||
|
write_buf.extend_from_slice(&(from).to_be_bytes()); //send everyone that the client id joined the room
|
||||||
|
write_buf.push(room.as_bytes().len() as u8);
|
||||||
|
write_buf.extend_from_slice(room.as_bytes());
|
||||||
|
let mut client_ref = to.borrow_mut();
|
||||||
|
client_ref.message_queue.extend_from_slice(&write_buf);
|
||||||
|
client_ref.notify.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_you_joined_message(client_ref: &mut Client, in_room: Vec<u32>, room: &str) {
|
||||||
|
//you_joined_u8, ids_len_u32, id_list_array_u32, room_name_len_u8, room_name_bytes
|
||||||
|
let mut write_buf = vec![];
|
||||||
|
write_buf.push(ToClientTCPMessageType::YouJoined as u8);
|
||||||
|
write_buf.extend_from_slice(&(in_room.len() as u32).to_be_bytes());
|
||||||
|
for id in in_room {
|
||||||
|
write_buf.extend_from_slice(&(id).to_be_bytes());
|
||||||
|
}
|
||||||
|
write_buf.push(room.as_bytes().len() as u8);
|
||||||
|
write_buf.extend_from_slice(room.as_bytes());
|
||||||
|
client_ref.message_queue.extend_from_slice(&write_buf);
|
||||||
|
client_ref.notify.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_client_master_message(client_ref: &mut Client, master_id: u32) {
|
||||||
|
//2u8, person_id_u32, room_name_len_u8, room_name_bytes
|
||||||
|
let mut write_buf = vec![];
|
||||||
|
write_buf.push(ToClientTCPMessageType::MasterMessage as u8);
|
||||||
|
write_buf.extend_from_slice(&(master_id).to_be_bytes()); //send everyone that the client id joined the room
|
||||||
|
client_ref.message_queue.extend_from_slice(&write_buf);
|
||||||
|
client_ref.notify.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_room_message(sender: Rc<RefCell<Client>>, message: &Vec<u8>, rooms: Rc<RefCell<HashMap<String, Rc<RefCell<Room>>>>>, include_sender: bool, _ordered: bool) {
|
||||||
|
//this message is 3u8, sender_id_u32, message_len_u32, message_bytes
|
||||||
|
|
||||||
|
let mut write_buf = vec![];
|
||||||
|
|
||||||
|
|
||||||
|
let mut sender_ref = sender.borrow_mut();
|
||||||
|
write_buf.push(ToClientTCPMessageType::DataMessage as u8);
|
||||||
|
write_buf.extend_from_slice(&sender_ref.id.to_be_bytes());
|
||||||
|
write_buf.extend_from_slice(&(message.len() as u32).to_be_bytes());
|
||||||
|
write_buf.extend_from_slice(message);
|
||||||
|
|
||||||
|
|
||||||
|
if sender_ref.room_name == "" {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if include_sender {
|
||||||
|
sender_ref.message_queue.extend_from_slice(&write_buf);
|
||||||
|
sender_ref.notify.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
let rooms_ref = rooms.borrow();
|
||||||
|
let room_ref = rooms_ref[&sender_ref.room_name].borrow();
|
||||||
|
|
||||||
|
for (_k, v) in room_ref.clients.iter() {
|
||||||
|
if *_k != sender_ref.id {
|
||||||
|
let mut temp_mut = v.borrow_mut();
|
||||||
|
temp_mut.message_queue.extend_from_slice(&write_buf);
|
||||||
|
temp_mut.notify.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_group_message(sender: Rc<RefCell<Client>>, message: &Vec<u8>, group: &String) {
|
||||||
|
let mut write_buf = vec![];
|
||||||
|
let mut sender_ref = sender.borrow_mut();
|
||||||
|
write_buf.push(ToClientTCPMessageType::DataMessage as u8);
|
||||||
|
write_buf.extend_from_slice(&sender_ref.id.to_be_bytes());
|
||||||
|
write_buf.extend_from_slice(&(message.len() as u32).to_be_bytes());
|
||||||
|
write_buf.extend_from_slice(message);
|
||||||
|
|
||||||
|
//get the list of client ids for this group
|
||||||
|
let mut send_to_client = false;
|
||||||
|
if sender_ref.groups.contains_key(group) {
|
||||||
|
let group = sender_ref.groups.get(group).unwrap();
|
||||||
|
for c in group {
|
||||||
|
if ptr::eq(sender.as_ref(), c.as_ref()) {
|
||||||
|
send_to_client = true;
|
||||||
|
} else {
|
||||||
|
let mut temp_mut = c.borrow_mut();
|
||||||
|
temp_mut.message_queue.extend_from_slice(&write_buf);
|
||||||
|
temp_mut.notify.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if send_to_client {
|
||||||
|
sender_ref.message_queue.extend_from_slice(&write_buf);
|
||||||
|
sender_ref.notify.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_u8(stream: &mut TcpStream, duration: u64) -> Result<u8, Box<dyn Error>> {
|
||||||
|
let mut buf = [0; 1];
|
||||||
|
read_timeout(stream, &mut buf, duration).await?;
|
||||||
|
return Ok(buf[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_u32(stream: &mut TcpStream, duration: u64) -> Result<u32, Box<dyn Error>> {
|
||||||
|
let mut buf: [u8; 4] = [0; 4];
|
||||||
|
read_timeout(stream, &mut buf, duration).await?;
|
||||||
|
let size = u32::from_be_bytes(buf);
|
||||||
|
return Ok(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _read_string(stream: &mut TcpStream, duration: u64) -> Result<String, Box<dyn Error>> {
|
||||||
|
let size = read_u32(stream, duration).await?;
|
||||||
|
let mut string_bytes = vec![0; size as usize];
|
||||||
|
read_timeout(stream, &mut string_bytes, duration).await?;
|
||||||
|
return Ok(String::from_utf8(string_bytes)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_short_string(stream: &mut TcpStream, duration: u64) -> Result<String, Box<dyn Error>> {
|
||||||
|
let size = read_u8(stream, duration).await?;
|
||||||
|
let mut string_bytes = vec![0; size as usize];
|
||||||
|
read_timeout(stream, &mut string_bytes, duration).await?;
|
||||||
|
return Ok(String::from_utf8(string_bytes)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_vec(stream: &mut TcpStream, duration: u64) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let message_size = read_u32(stream, duration).await?;
|
||||||
|
let mut message = vec![0u8; message_size as usize];
|
||||||
|
read_timeout(stream, &mut message, duration).await?;
|
||||||
|
return Ok(message);
|
||||||
|
}
|
||||||
|
|
||||||
846
src/main.rs
|
|
@ -1,846 +0,0 @@
|
||||||
extern crate chrono;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::thread;
|
|
||||||
use std::net::{TcpListener, TcpStream,UdpSocket,IpAddr,SocketAddr};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::{Arc,RwLock};
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::sync::mpsc::{SyncSender,Receiver};
|
|
||||||
use chrono::Local;
|
|
||||||
use std::fs;
|
|
||||||
use std::time;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
enum ToClientTCPMessageType {
|
|
||||||
LoggedIn = 0,
|
|
||||||
RoomList = 1,
|
|
||||||
PlayerJoined = 2,
|
|
||||||
DataMessage = 3,
|
|
||||||
MasterMessage = 4,
|
|
||||||
YouJoined = 5,
|
|
||||||
PlayerLeft = 6,
|
|
||||||
YouLeft = 7,
|
|
||||||
RoomData = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
enum FromClientTCPMessageType {
|
|
||||||
LogIn = 0,
|
|
||||||
GetRooms = 1,
|
|
||||||
JoinRoom = 2,
|
|
||||||
SendMessageOthersUnbuffered = 3,
|
|
||||||
SendMessageAllUnbuffered = 4,
|
|
||||||
SendMessageGroupUnbuffered = 5,
|
|
||||||
CreateGroup = 6,
|
|
||||||
SendMessageOthersBuffered = 7,
|
|
||||||
SendMessageAllBuffered = 8,
|
|
||||||
GetRoomData =9
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ToClientUDPMessageType {
|
|
||||||
Connected = 0,
|
|
||||||
DataMessage = ToClientTCPMessageType::DataMessage as isize
|
|
||||||
}
|
|
||||||
enum FromClientUDPMessageType {
|
|
||||||
Connect = 0,
|
|
||||||
SendMesssageOthersUnbuffered = FromClientTCPMessageType::SendMessageOthersUnbuffered as isize,
|
|
||||||
SendMessageAllUnbuffered = FromClientTCPMessageType::SendMessageAllUnbuffered as isize,
|
|
||||||
SendMessageGroupUnbuffered = FromClientTCPMessageType::SendMessageGroupUnbuffered as isize
|
|
||||||
}
|
|
||||||
struct Client {
|
|
||||||
logged_in: Arc<RwLock<bool>>,
|
|
||||||
id: u32,
|
|
||||||
username: Arc<RwLock<String>>,
|
|
||||||
application: Arc<RwLock<String>>,
|
|
||||||
room: Arc<RwLock<Option<Arc<Room>>>>,
|
|
||||||
sender: SyncSender<Vec<u8>>,
|
|
||||||
rooms_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,
|
|
||||||
clients_mutex: Arc<RwLock<HashMap<u32,Arc<Client>>>>,
|
|
||||||
groups: Arc<RwLock<HashMap<String,Vec<Arc<Client>>>>>,
|
|
||||||
ip: Arc<RwLock<IpAddr>>,
|
|
||||||
port: Arc<RwLock<u16>>
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Room {
|
|
||||||
name: String,
|
|
||||||
clients: RwLock<HashMap<u32,Arc<Client>>>,
|
|
||||||
master_client: Arc<RwLock<Arc<Client>>>
|
|
||||||
}
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct Config {
|
|
||||||
port: u16,
|
|
||||||
tcp_timeout: u64,
|
|
||||||
tcp_send_buffer: usize
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn read_u8(stream: &mut TcpStream) -> u8 {
|
|
||||||
let mut buf = [0; 1];
|
|
||||||
stream.read_exact(&mut buf).unwrap();
|
|
||||||
return buf[0];
|
|
||||||
}
|
|
||||||
fn read_u32(stream: &mut TcpStream) -> u32 {
|
|
||||||
let mut buf:[u8;4] = [0; 4];
|
|
||||||
stream.read_exact(&mut buf).unwrap();
|
|
||||||
let size = u32::from_be_bytes(buf);
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
fn _read_string(stream: &mut TcpStream) -> String {
|
|
||||||
let size = read_u32(stream);
|
|
||||||
let mut string_bytes = vec![0;size as usize];
|
|
||||||
stream.read_exact(&mut string_bytes).unwrap();
|
|
||||||
return String::from_utf8(string_bytes).unwrap();
|
|
||||||
}
|
|
||||||
fn read_short_string(stream: &mut TcpStream) -> String {
|
|
||||||
let size = read_u8(stream);
|
|
||||||
let mut string_bytes = vec![0;size as usize];
|
|
||||||
stream.read_exact(&mut string_bytes).unwrap();
|
|
||||||
return String::from_utf8(string_bytes).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_vec(stream: &mut TcpStream) -> Vec<u8> {
|
|
||||||
let message_size = read_u32(stream);
|
|
||||||
let mut message = vec![0u8;message_size as usize];
|
|
||||||
stream.read_exact(&mut message).unwrap();
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
//this is in response to someone asking to login (this is where usernames and passwords would be processed, in theory)
|
|
||||||
fn read_login_message(stream: &mut TcpStream, client: &Arc<Client>) {
|
|
||||||
//byte,shortstring,byte,shortstring
|
|
||||||
|
|
||||||
let username = read_short_string(stream);
|
|
||||||
let application = read_short_string(stream);
|
|
||||||
|
|
||||||
println!("{}: Got application {} and userid {}",Local::now().format("%Y-%m-%d %H:%M:%S"),application,username);
|
|
||||||
let mut client_user = client.username.write().unwrap();
|
|
||||||
*client_user = username;
|
|
||||||
let mut client_application = client.application.write().unwrap();
|
|
||||||
*client_application = application;
|
|
||||||
let mut client_loggedin = client.logged_in.write().unwrap();
|
|
||||||
*client_loggedin = true;
|
|
||||||
let mut write_buf = vec![];
|
|
||||||
write_buf.push(ToClientTCPMessageType::LoggedIn as u8);
|
|
||||||
write_buf.extend_from_slice(&(client.id).to_be_bytes()); //send the client the id
|
|
||||||
|
|
||||||
client.sender.try_send(write_buf).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
//this is in response to a request for rooms.
|
|
||||||
fn read_rooms_message(_stream: &mut TcpStream, client: &Arc<Client>){
|
|
||||||
|
|
||||||
let mut write_buf = vec![];
|
|
||||||
write_buf.push(ToClientTCPMessageType::RoomList as u8);
|
|
||||||
//first we need to get the room names
|
|
||||||
|
|
||||||
let rooms = client.rooms_mutex.read().unwrap();
|
|
||||||
let mut rooms_vec = vec![];
|
|
||||||
for (k,v) in rooms.iter() {
|
|
||||||
let app_name = client.application.read().unwrap();
|
|
||||||
|
|
||||||
if !k.starts_with(&app_name.to_string()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let clients = v.clients.read().unwrap();
|
|
||||||
let mut iter = k.chars();
|
|
||||||
|
|
||||||
iter.by_ref().nth(app_name.len());
|
|
||||||
let application_stripped_room = iter.as_str();
|
|
||||||
|
|
||||||
let room_string = format!("{}:{}",application_stripped_room,clients.len());
|
|
||||||
rooms_vec.push(room_string);
|
|
||||||
}
|
|
||||||
|
|
||||||
let rooms_message = rooms_vec.join(",");
|
|
||||||
let message_bytes = rooms_message.as_bytes();
|
|
||||||
let message_len = message_bytes.len() as u32;
|
|
||||||
write_buf.extend_from_slice(&(message_len).to_be_bytes());
|
|
||||||
write_buf.extend_from_slice(message_bytes);
|
|
||||||
client.sender.try_send(write_buf).unwrap();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_roomdata_message(stream: &mut TcpStream, client: &Arc<Client>){
|
|
||||||
//type, room_name
|
|
||||||
//will respond with type, numclients u32, id1 u32, name_len u8, name_bytes ...
|
|
||||||
|
|
||||||
//read the room name and append the client application
|
|
||||||
|
|
||||||
let short_room_name = read_short_string(stream);
|
|
||||||
let application = client.application.read().unwrap().to_string();
|
|
||||||
let room_name = format!("{}_{}", application, short_room_name);
|
|
||||||
|
|
||||||
//we need to access the rooms list
|
|
||||||
let rooms = client.rooms_mutex.read().unwrap();
|
|
||||||
if rooms.contains_key(&room_name) {
|
|
||||||
|
|
||||||
let room = rooms.get(&room_name).unwrap();
|
|
||||||
//form and send the message
|
|
||||||
let mut write_buf = vec![];
|
|
||||||
write_buf.push(ToClientTCPMessageType::RoomData as u8);
|
|
||||||
|
|
||||||
|
|
||||||
let roomname_bytes = short_room_name.as_bytes();
|
|
||||||
write_buf.push(roomname_bytes.len() as u8);
|
|
||||||
write_buf.extend_from_slice(&roomname_bytes);
|
|
||||||
|
|
||||||
let clients = room.clients.read().unwrap();
|
|
||||||
write_buf.extend_from_slice(&(clients.len() as u32).to_be_bytes());
|
|
||||||
for (_k,c) in clients.iter() {
|
|
||||||
//write out the client id (u32) and the username (short string)
|
|
||||||
write_buf.extend_from_slice(&(c.id).to_be_bytes());
|
|
||||||
let username = c.username.read().unwrap();
|
|
||||||
let username_bytes = username.as_bytes();
|
|
||||||
write_buf.push(username_bytes.len() as u8);
|
|
||||||
write_buf.extend_from_slice(&username_bytes);
|
|
||||||
}
|
|
||||||
client.sender.try_send(write_buf).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn send_client_master_message(to: &Arc<Client>, master_id: u32){
|
|
||||||
//2u8, person_id_u32, room_name_len_u8, room_name_bytes
|
|
||||||
let mut write_buf = vec![];
|
|
||||||
write_buf.push(ToClientTCPMessageType::MasterMessage as u8);
|
|
||||||
write_buf.extend_from_slice(&(master_id).to_be_bytes()); //send everyone that the client id joined the room
|
|
||||||
let res = to.sender.try_send(write_buf);
|
|
||||||
match res {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(_) => ()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_client_join_message(to: &Arc<Client>, from: u32, room: &str){
|
|
||||||
//2u8, person_id_u32, room_name_len_u8, room_name_bytes
|
|
||||||
let mut write_buf = vec![];
|
|
||||||
write_buf.push(ToClientTCPMessageType::PlayerJoined as u8);
|
|
||||||
write_buf.extend_from_slice(&(from).to_be_bytes()); //send everyone that the client id joined the room
|
|
||||||
write_buf.push(room.as_bytes().len() as u8);
|
|
||||||
write_buf.extend_from_slice(room.as_bytes());
|
|
||||||
let res = to.sender.try_send(write_buf);
|
|
||||||
match res {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(_) => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_you_joined_message(to: &Arc<Client>, in_room: Vec<u32>, room: &str){
|
|
||||||
//you_joined_u8, ids_len_u32, id_list_array_u32, room_name_len_u8, room_name_bytes
|
|
||||||
let mut write_buf = vec![];
|
|
||||||
write_buf.push(ToClientTCPMessageType::YouJoined as u8);
|
|
||||||
write_buf.extend_from_slice(&(in_room.len() as u32).to_be_bytes());
|
|
||||||
for id in in_room {
|
|
||||||
write_buf.extend_from_slice(&(id).to_be_bytes());
|
|
||||||
}
|
|
||||||
write_buf.push(room.as_bytes().len() as u8);
|
|
||||||
write_buf.extend_from_slice(room.as_bytes());
|
|
||||||
let res = to.sender.try_send(write_buf);
|
|
||||||
match res {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(_) => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_you_left_message(to: &Arc<Client>, room: &str){
|
|
||||||
let mut write_buf = vec![];
|
|
||||||
write_buf.push(ToClientTCPMessageType::YouLeft as u8);
|
|
||||||
write_buf.push(room.as_bytes().len() as u8);
|
|
||||||
write_buf.extend_from_slice(room.as_bytes());
|
|
||||||
let res = to.sender.try_send(write_buf);
|
|
||||||
match res {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(_) => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn send_client_left_message(to: &Arc<Client>, from: u32, room: &str){
|
|
||||||
let mut write_buf = vec![];
|
|
||||||
write_buf.push(ToClientTCPMessageType::PlayerLeft as u8);
|
|
||||||
write_buf.extend_from_slice(&(from).to_be_bytes()); //send everyone that the client id left the room
|
|
||||||
write_buf.push(room.as_bytes().len() as u8);
|
|
||||||
write_buf.extend_from_slice(room.as_bytes());
|
|
||||||
let res = to.sender.try_send(write_buf);
|
|
||||||
match res {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(_) => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//helper function, because clients leave room in multiple places
|
|
||||||
fn client_leave_room(client: &Arc<Client>, send_to_client: bool){
|
|
||||||
//first remove the client from the room they are in
|
|
||||||
|
|
||||||
{
|
|
||||||
let room = client.room.read().unwrap(); //I need to get the room, because I'll be modifying the clients in it
|
|
||||||
if room.is_some(){
|
|
||||||
{
|
|
||||||
let mut change_master = false;
|
|
||||||
let mut new_master_id = 0;
|
|
||||||
{
|
|
||||||
println!("{}: {}: Client {} in room, leaving",Local::now().format("%Y-%m-%d %H:%M:%S"), client.application.read().unwrap().to_string(),client.id);
|
|
||||||
}
|
|
||||||
let room = room.as_ref().unwrap();
|
|
||||||
|
|
||||||
//may have to choose a new master
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
let clients = room.clients.read().unwrap();
|
|
||||||
let master_client = room.master_client.read().unwrap();
|
|
||||||
if master_client.id == client.id {
|
|
||||||
//change the master
|
|
||||||
change_master = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
for (_k,v) in clients.iter() {
|
|
||||||
if !send_to_client && v.id == client.id{
|
|
||||||
continue;
|
|
||||||
}else if v.id == client.id {
|
|
||||||
send_you_left_message(v, &room.name);
|
|
||||||
}else{
|
|
||||||
send_client_left_message(v, client.id, &room.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut clients = room.clients.write().unwrap();
|
|
||||||
clients.remove(&client.id); //remove the client from that list in the room
|
|
||||||
}
|
|
||||||
|
|
||||||
let clients = room.clients.read().unwrap();
|
|
||||||
|
|
||||||
//if the room is empty, destroy it as well
|
|
||||||
|
|
||||||
if clients.len() == 0 {
|
|
||||||
let mut rooms = client.rooms_mutex.write().unwrap();
|
|
||||||
rooms.remove(&room.name);
|
|
||||||
{
|
|
||||||
println!("{}: {}: Destroyed room {}",Local::now().format("%Y-%m-%d %H:%M:%S"), client.application.read().unwrap().to_string(), &room.name)
|
|
||||||
}
|
|
||||||
}else if change_master{
|
|
||||||
|
|
||||||
for (_k,v) in clients.iter() {
|
|
||||||
if v.id != client.id {
|
|
||||||
new_master_id = v.id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
println!("{}: {}: Changing master to {}",Local::now().format("%Y-%m-%d %H:%M:%S"),client.application.read().unwrap().to_string(), new_master_id);
|
|
||||||
}
|
|
||||||
for (_k,v) in clients.iter() {
|
|
||||||
send_client_master_message(&v, new_master_id);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut master_client = room.master_client.write().unwrap();
|
|
||||||
*master_client = clients.get(&new_master_id).unwrap().clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut room = client.room.write().unwrap();
|
|
||||||
*room = Option::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_join_message(stream: &mut TcpStream, client: &Arc<Client>){
|
|
||||||
//byte,shortstring
|
|
||||||
let short_room_name = read_short_string(stream);
|
|
||||||
let application = client.application.read().unwrap().to_string();
|
|
||||||
let extended_room_name = format!("{}_{}", application, short_room_name);
|
|
||||||
|
|
||||||
|
|
||||||
//if the client is in a room, leave it
|
|
||||||
let mut leave_room = false;
|
|
||||||
{
|
|
||||||
let room = client.room.read().unwrap(); //must release this mutex before calling into a function that uses it
|
|
||||||
if room.as_ref().is_some(){
|
|
||||||
leave_room = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if leave_room {
|
|
||||||
client_leave_room(client, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if short_room_name.trim() == "" || short_room_name == "-1" {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//join room_name
|
|
||||||
{
|
|
||||||
{
|
|
||||||
let mut rooms = client.rooms_mutex.write().unwrap();
|
|
||||||
if !rooms.contains_key(&extended_room_name) { //new room, must create it
|
|
||||||
let map: HashMap<u32, Arc<Client>> = HashMap::new();
|
|
||||||
let r = Arc::new(Room {
|
|
||||||
name: extended_room_name.to_string(),
|
|
||||||
clients: RwLock::new(map),
|
|
||||||
master_client: Arc::new(RwLock::new(client.clone())) //client is the master, since they joined first
|
|
||||||
});
|
|
||||||
|
|
||||||
rooms.insert(String::from(&extended_room_name),r);
|
|
||||||
println!("{}: {}: New room {} created",Local::now().format("%Y-%m-%d %H:%M:%S"), application,&extended_room_name);
|
|
||||||
}
|
|
||||||
//the room is guaranteed to exist now, so this call can't fail
|
|
||||||
let room_to_join = &rooms[&extended_room_name];
|
|
||||||
let mut clients = room_to_join.clients.write().unwrap();
|
|
||||||
clients.insert(client.id,client.clone());
|
|
||||||
println!("{}: {}: Client {} joined {}",Local::now().format("%Y-%m-%d %H:%M:%S"), application, client.id,&extended_room_name);
|
|
||||||
let mut room = client.room.write().unwrap();
|
|
||||||
*room = Some(room_to_join.clone()); //we create an option and assign it back to the room
|
|
||||||
}
|
|
||||||
|
|
||||||
//once the client is in the room, it can't suddenly die, so we can release the write lock, and can use the extended_room name without issue
|
|
||||||
|
|
||||||
{
|
|
||||||
let rooms = client.rooms_mutex.read().unwrap();
|
|
||||||
let clients = rooms[&extended_room_name].clients.read().unwrap(); //only need a read lock now
|
|
||||||
//send a join message to everyone in the room (except the client)
|
|
||||||
for (_k,v) in clients.iter() {
|
|
||||||
if v.id != client.id {
|
|
||||||
send_client_join_message(v, client.id, &short_room_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//send a join message to the client that has all of the ids in the room
|
|
||||||
let mut ids_in_room = vec![];
|
|
||||||
for (_k,v) in clients.iter() {
|
|
||||||
ids_in_room.push(v.id);
|
|
||||||
|
|
||||||
}
|
|
||||||
send_you_joined_message(client, ids_in_room, &short_room_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let room = client.room.read().unwrap();
|
|
||||||
//tell the client who the master is
|
|
||||||
let master_client = room.as_ref().unwrap().master_client.read().unwrap();
|
|
||||||
send_client_master_message(client, master_client.id);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// function send_message_to_clients_dictionary(clients: message: &Vec<u8>, include_sender: bool){
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn send_room_message(sender: &Arc<Client>, message: &Vec<u8>, include_sender: bool, ordered: bool){
|
|
||||||
//this message is 3u8, sender_id_u32, message_len_u32, message_bytes
|
|
||||||
let mut write_buf = vec![];
|
|
||||||
write_buf.push(ToClientTCPMessageType::DataMessage as u8);
|
|
||||||
write_buf.extend_from_slice(&sender.id.to_be_bytes());
|
|
||||||
write_buf.extend_from_slice(&(message.len() as u32).to_be_bytes());
|
|
||||||
write_buf.extend_from_slice(message);
|
|
||||||
//println!("sending {} bytes from {}",message.len(),sender.id);
|
|
||||||
{
|
|
||||||
|
|
||||||
if !ordered {
|
|
||||||
let room = sender.room.read().unwrap();
|
|
||||||
|
|
||||||
if room.is_some() {
|
|
||||||
|
|
||||||
let clients = room.as_ref().unwrap().clients.read().unwrap();
|
|
||||||
for (_k,v) in clients.iter(){
|
|
||||||
if !include_sender && v.id == sender.id {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
match v.sender.try_send(write_buf.clone()){
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(x) => println!("{}: {}: Error sending to client {}: {}",Local::now().format("%Y-%m-%d %H:%M:%S"), v.application.read().unwrap().to_string(),v.id,x)
|
|
||||||
} //this sometimes fails.
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{ //I'm bad at rust, so I don't know how else to do this other than repeat the code above because the types are so different
|
|
||||||
let room = sender.room.write().unwrap();
|
|
||||||
|
|
||||||
if room.is_some() {
|
|
||||||
|
|
||||||
let clients = room.as_ref().unwrap().clients.read().unwrap();
|
|
||||||
for (_k,v) in clients.iter(){
|
|
||||||
if !include_sender && v.id == sender.id {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
match v.sender.try_send(write_buf.clone()){
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(x) => println!("{}: {}: Error sending to client {}: {}",Local::now().format("%Y-%m-%d %H:%M:%S"),v.application.read().unwrap().to_string(),v.id,x)
|
|
||||||
} //this sometimes fails.
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn send_group_message(sender: &Arc<Client>, message: &Vec<u8>, group: &String){
|
|
||||||
let mut write_buf = vec![];
|
|
||||||
write_buf.push(ToClientTCPMessageType::DataMessage as u8);
|
|
||||||
write_buf.extend_from_slice(&sender.id.to_be_bytes());
|
|
||||||
write_buf.extend_from_slice(&(message.len() as u32).to_be_bytes());
|
|
||||||
write_buf.extend_from_slice(message);
|
|
||||||
|
|
||||||
//get the list of client ids for this group
|
|
||||||
let groups = sender.groups.read().unwrap();
|
|
||||||
if groups.contains_key(group) {
|
|
||||||
let group = groups.get(group).unwrap();
|
|
||||||
for c in group {
|
|
||||||
//there may be a leftover when a client leaves...will fix itself
|
|
||||||
|
|
||||||
match c.sender.try_send(write_buf.clone()) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(_) => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
fn read_send_message(stream: &mut TcpStream, client: &Arc<Client>, message_type: u8){
|
|
||||||
//4 byte length, array
|
|
||||||
//this is a message for everyone in the room (maybe)
|
|
||||||
let to_send = read_vec(stream);
|
|
||||||
if message_type == FromClientTCPMessageType::SendMessageOthersUnbuffered as u8 {
|
|
||||||
send_room_message(client,&to_send,false,false);
|
|
||||||
}else if message_type == FromClientTCPMessageType::SendMessageAllUnbuffered as u8 {
|
|
||||||
send_room_message(client,&to_send,true,false);
|
|
||||||
} else if message_type == FromClientTCPMessageType::SendMessageOthersBuffered as u8 { //ordered
|
|
||||||
send_room_message(client,&to_send,false,true);
|
|
||||||
}else if message_type == FromClientTCPMessageType::SendMessageAllBuffered as u8 { //ordered
|
|
||||||
send_room_message(client,&to_send,true,true);
|
|
||||||
}else if message_type == FromClientTCPMessageType::SendMessageGroupUnbuffered as u8 {
|
|
||||||
let group = read_short_string(stream);
|
|
||||||
send_group_message(client,&to_send, &group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_group_message(stream: &mut TcpStream, client: &Arc<Client>){
|
|
||||||
|
|
||||||
let group = read_short_string(stream);
|
|
||||||
let id_bytes = read_vec(stream);
|
|
||||||
let num = id_bytes.len();
|
|
||||||
|
|
||||||
let mut groups = client.groups.write().unwrap();
|
|
||||||
let clients = client.clients_mutex.read().unwrap();
|
|
||||||
let mut group_clients = vec![];
|
|
||||||
let mut i = 0;
|
|
||||||
loop {
|
|
||||||
if i >= num {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let mut slice = [0u8;4];
|
|
||||||
slice[0] = id_bytes[i];
|
|
||||||
slice[1] = id_bytes[i+1];
|
|
||||||
slice[2] = id_bytes[i+2];
|
|
||||||
slice[3] = id_bytes[i+3]; //probably a better way to do this
|
|
||||||
let id = u32::from_be_bytes(slice);
|
|
||||||
|
|
||||||
|
|
||||||
match clients.get(&id) {
|
|
||||||
Some(client) => {group_clients.push(client.clone());},
|
|
||||||
None => () //not there, so don't add it
|
|
||||||
}
|
|
||||||
|
|
||||||
i = i + 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
//delete the group if it exists
|
|
||||||
if groups.contains_key(&group) {
|
|
||||||
groups.remove(&group); //ensures the client references go away
|
|
||||||
}
|
|
||||||
|
|
||||||
groups.insert(group.clone(),group_clients);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn client_read_thread(mut stream: TcpStream, mut client: Arc<Client>) {
|
|
||||||
let mut read_buf:[u8;1] = [0; 1];
|
|
||||||
//messages come through as a 1 byte type identifier, that can be one of 0 (login) 1 (get rooms), 2 (join/leave room) 3 (send message to room), 4 (send message to room including me), 5 (send message to group), 6 (establish group)
|
|
||||||
loop {
|
|
||||||
|
|
||||||
//read exactly 1 byte
|
|
||||||
stream.read_exact(&mut read_buf).unwrap();
|
|
||||||
//println!("Got a message {}",read_buf[0]);
|
|
||||||
let t = read_buf[0];
|
|
||||||
if t == FromClientTCPMessageType::LogIn as u8 { //[0:u8][username.length():u8][username:shortstring][password.length():u8][password:shortstring]
|
|
||||||
read_login_message(&mut stream, &mut client);
|
|
||||||
} else if t == FromClientTCPMessageType::GetRooms as u8 {//[1:u8]
|
|
||||||
read_rooms_message(&mut stream, &mut client);
|
|
||||||
} else if t == FromClientTCPMessageType::GetRoomData as u8 {
|
|
||||||
read_roomdata_message(&mut stream, &mut client);
|
|
||||||
} else if t == FromClientTCPMessageType::JoinRoom as u8 {//[2:u8][roomname.length():u8][roomname:shortstring]
|
|
||||||
read_join_message(&mut stream, &mut client);
|
|
||||||
} else if t == FromClientTCPMessageType::SendMessageOthersUnbuffered as u8 ||
|
|
||||||
t == FromClientTCPMessageType::SendMessageAllUnbuffered as u8 ||
|
|
||||||
t == FromClientTCPMessageType::SendMessageGroupUnbuffered as u8 ||
|
|
||||||
t == FromClientTCPMessageType::SendMessageOthersBuffered as u8 ||
|
|
||||||
t == FromClientTCPMessageType::SendMessageAllBuffered as u8 { //others,all,group[t:u8][message.length():i32][message:u8array]
|
|
||||||
read_send_message(&mut stream, &client, t);
|
|
||||||
} else if t == FromClientTCPMessageType::CreateGroup as u8 { //[t:u8][list.lengthbytes:i32][clients:i32array]
|
|
||||||
read_group_message(&mut stream, &client);
|
|
||||||
} else {
|
|
||||||
//die...not correct protocol
|
|
||||||
println!("Incorrect protocol, killing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::io::stdout().flush().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn client_write_thread(mut stream: TcpStream, rx: Receiver<Vec<u8>> ) {
|
|
||||||
|
|
||||||
//wait on messages in my queue
|
|
||||||
loop {
|
|
||||||
let m = rx.recv().unwrap();
|
|
||||||
//println!("Sending a message {}",m.len());
|
|
||||||
if m.len() == 1{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
stream.write(&m).unwrap();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn handle_client(stream: TcpStream, client_id: u32, clients_mutex: Arc<RwLock<HashMap<u32,Arc<Client>>>>, rooms_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,tcp_timeout: u64,tcp_send_buffer:usize){
|
|
||||||
|
|
||||||
stream.set_nodelay(true).unwrap();
|
|
||||||
stream.set_read_timeout(Some(time::Duration::new(tcp_timeout,0))).unwrap();
|
|
||||||
stream.set_write_timeout(Some(time::Duration::new(tcp_timeout,0))).unwrap();
|
|
||||||
println!("{}: Accepted new connection, assigned client id {}",Local::now().format("%Y-%m-%d %H:%M:%S"),client_id);
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::sync_channel(tcp_send_buffer); //the server is very fast. However, if the clients throttle the sending, this buffer may overflow, and when it does, data is lost
|
|
||||||
//create a new client structure and add it to the list of clients
|
|
||||||
let client = Arc::new(Client{
|
|
||||||
id: client_id,
|
|
||||||
username: Arc::new(RwLock::new(String::from(""))),
|
|
||||||
logged_in: Arc::new(RwLock::new(false)),
|
|
||||||
room: Arc::new(RwLock::new(Option::None)),
|
|
||||||
application: Arc::new(RwLock::new(String::from(""))),
|
|
||||||
sender: tx,
|
|
||||||
rooms_mutex: rooms_mutex.clone(),
|
|
||||||
clients_mutex: clients_mutex.clone(),
|
|
||||||
groups: Arc::new(RwLock::new(HashMap::new())),
|
|
||||||
ip: Arc::new(RwLock::new(stream.peer_addr().unwrap().ip())),
|
|
||||||
port: Arc::new(RwLock::new(0))
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut clients = clients_mutex.write().unwrap();
|
|
||||||
clients.insert(client_id, client.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let read_clone = stream.try_clone().expect("clone failed");
|
|
||||||
let read_client = client.clone();
|
|
||||||
let read_handle = thread::spawn(move ||{client_read_thread(read_clone, read_client)});
|
|
||||||
let write_handle = thread::spawn(move ||{client_write_thread(stream, rx)});
|
|
||||||
|
|
||||||
//handle writing to the thread as needed
|
|
||||||
|
|
||||||
match read_handle.join(){
|
|
||||||
Ok(_)=>(),
|
|
||||||
Err(_)=>()
|
|
||||||
}
|
|
||||||
|
|
||||||
match client.sender.try_send(vec![0]){ //force send thread to exit
|
|
||||||
Ok(_)=>(),
|
|
||||||
Err(_)=>()
|
|
||||||
}
|
|
||||||
|
|
||||||
match write_handle.join() {
|
|
||||||
Ok(_)=>(),
|
|
||||||
Err(_)=>()
|
|
||||||
}
|
|
||||||
println!("{}: {}: Client {} left",Local::now().format("%Y-%m-%d %H:%M:%S"),client.application.read().unwrap().to_string(),client_id);
|
|
||||||
//now we can kill the client.
|
|
||||||
{
|
|
||||||
//make sure we remove the client from all rooms
|
|
||||||
client_leave_room(&client, false);
|
|
||||||
let mut clients = clients_mutex.write().unwrap();
|
|
||||||
clients.remove(&client_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tcp_listen(client_mutex: Arc<RwLock<HashMap<u32, Arc<Client>>>>, room_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,port:u16,tcp_timeout:u64,tcp_send_buffer:usize){
|
|
||||||
println!("{}: Started TCP Listener",Local::now().format("%Y-%m-%d %H:%M:%S"));
|
|
||||||
let listener = TcpListener::bind(format!("0.0.0.0:{}",port)).expect("could not bind port");
|
|
||||||
|
|
||||||
let mut next_client_id = 0;
|
|
||||||
// accept connections and process them serially
|
|
||||||
for stream in listener.incoming() {
|
|
||||||
let client_mutex = Arc::clone(&client_mutex);
|
|
||||||
let room_mutex = Arc::clone(&room_mutex);
|
|
||||||
thread::spawn(move || {handle_client(stream.unwrap(), next_client_id, client_mutex, room_mutex,tcp_timeout,tcp_send_buffer)});
|
|
||||||
next_client_id+=1;
|
|
||||||
}
|
|
||||||
println!("{}: Ended TCP Listener",Local::now().format("%Y-%m-%d %H:%M:%S"));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn udp_listen(client_mutex: Arc<RwLock<HashMap<u32, Arc<Client>>>>, _room_mutex: Arc<RwLock<HashMap<String,Arc<Room>>>>,port:u16){
|
|
||||||
let mut buf = [0u8;1024];
|
|
||||||
let s = UdpSocket::bind(format!("0.0.0.0:{}",port)).unwrap();
|
|
||||||
println!("{}: UDP Thread Started",Local::now().format("%Y-%m-%d %H:%M:%S"));
|
|
||||||
loop {
|
|
||||||
let res = s.recv_from(&mut buf);
|
|
||||||
match res {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(_) => continue
|
|
||||||
}
|
|
||||||
let (packet_size,addr) = res.unwrap();
|
|
||||||
let t = buf[0];
|
|
||||||
if packet_size >= 5{
|
|
||||||
//get the client id, which has to be sent with every udp message, because you don't know where udp messages are coming from
|
|
||||||
let client_id_bytes = [buf[1],buf[2],buf[3],buf[4]];
|
|
||||||
let client_id = u32::from_be_bytes(client_id_bytes);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if t == FromClientUDPMessageType::Connect as u8 { //1 byte, 0. Nothing else. This is just to establish the udp port, Echos back the same thing sent
|
|
||||||
//connect message, respond back
|
|
||||||
{
|
|
||||||
let clients = client_mutex.read().unwrap();
|
|
||||||
if clients.contains_key(&client_id){
|
|
||||||
let client = clients.get(&client_id).unwrap();
|
|
||||||
let mut port = client.port.write().unwrap();
|
|
||||||
*port = addr.port(); //set the udp port to send data to
|
|
||||||
buf[0] = ToClientUDPMessageType::Connected as u8;
|
|
||||||
match s.send_to(&buf,addr) {
|
|
||||||
Ok(_)=>(),
|
|
||||||
Err(_)=>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} else if t == FromClientUDPMessageType::SendMesssageOthersUnbuffered as u8 { //[3:u8][from:i32][contents:u8array] note that it must fit into the packet of 1024 bytes
|
|
||||||
{
|
|
||||||
let clients = client_mutex.read().unwrap();
|
|
||||||
if clients.contains_key(&client_id){
|
|
||||||
let client = clients.get(&client_id).unwrap();
|
|
||||||
let room_option = client.room.read().unwrap();
|
|
||||||
let room = room_option.as_ref().unwrap();
|
|
||||||
let room_clients = room.clients.read().unwrap(); //we finally got to the room!
|
|
||||||
buf[0] = ToClientUDPMessageType::DataMessage as u8; //technically unecessary, unless we change this number
|
|
||||||
for (_k,v) in room_clients.iter() {
|
|
||||||
if v.id != client_id{
|
|
||||||
let ip = v.ip.read().unwrap();
|
|
||||||
let port = v.port.read().unwrap();
|
|
||||||
match s.send_to(&buf,SocketAddr::new(*ip, *port)) {
|
|
||||||
Ok(_)=> (),
|
|
||||||
Err(_) => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if t == FromClientUDPMessageType::SendMessageAllUnbuffered as u8 { //see above
|
|
||||||
{
|
|
||||||
let clients = client_mutex.read().unwrap();
|
|
||||||
if clients.contains_key(&client_id){
|
|
||||||
let client = clients.get(&client_id).unwrap();
|
|
||||||
let room_option = client.room.read().unwrap();
|
|
||||||
let room = room_option.as_ref().unwrap();
|
|
||||||
let room_clients = room.clients.read().unwrap(); //we finally got to the room!
|
|
||||||
buf[0] = ToClientUDPMessageType::DataMessage as u8; //messages are always 3s, even though this came in as 4
|
|
||||||
for (_k,v) in room_clients.iter() {
|
|
||||||
|
|
||||||
let ip = v.ip.read().unwrap();
|
|
||||||
let port = v.port.read().unwrap();
|
|
||||||
match s.send_to(&buf,SocketAddr::new(*ip, *port)) {
|
|
||||||
Ok(_)=> (),
|
|
||||||
Err(_) => ()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if t == FromClientUDPMessageType::SendMessageGroupUnbuffered as u8 { //[5:byte][from:i32][group.length():u8][message:u8array]
|
|
||||||
//this one is a little different, because we don't send the group in the message, so we have to formulate another message (like a 3 message)
|
|
||||||
//send a message to a group
|
|
||||||
//read the group name
|
|
||||||
|
|
||||||
let group_name_size = buf[5];
|
|
||||||
let message_vec = buf[6..packet_size].to_vec();
|
|
||||||
let (group_name_bytes, message_bytes) = message_vec.split_at(group_name_size as usize);
|
|
||||||
let group_name = String::from_utf8(group_name_bytes.to_vec()).unwrap();
|
|
||||||
let clients = client_mutex.read().unwrap();
|
|
||||||
if clients.contains_key(&client_id){
|
|
||||||
let client = clients.get(&client_id).unwrap();
|
|
||||||
let groups = client.groups.read().unwrap();
|
|
||||||
if groups.contains_key(&group_name) {
|
|
||||||
|
|
||||||
|
|
||||||
let clients = groups.get(&group_name).unwrap();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//we need to form a new message without the group name
|
|
||||||
let mut message_to_send = vec![];
|
|
||||||
message_to_send.push(ToClientUDPMessageType::DataMessage as u8);
|
|
||||||
message_to_send.extend([buf[1],buf[2],buf[3],buf[4]]);
|
|
||||||
message_to_send.extend(message_bytes);
|
|
||||||
|
|
||||||
for v in clients.iter() {
|
|
||||||
|
|
||||||
let ip = v.ip.read().unwrap();
|
|
||||||
let port = v.port.read().unwrap();
|
|
||||||
|
|
||||||
match s.send_to(&message_to_send,SocketAddr::new(*ip, *port)) {
|
|
||||||
Ok(_)=> (),
|
|
||||||
Err(_) => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{}: UDP Thread Ended",Local::now().format("%Y-%m-%d %H:%M:%S"));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
println!("{}: VelNet Server Starting",Local::now().format("%Y-%m-%d %H:%M:%S"));
|
|
||||||
|
|
||||||
//read the config file
|
|
||||||
let foo = fs::read_to_string("config.txt").unwrap();
|
|
||||||
let config: Config = serde_json::from_str(&foo).unwrap();
|
|
||||||
println!("{}",config.port);
|
|
||||||
|
|
||||||
let clients: HashMap<u32, Arc<Client>> = HashMap::new();
|
|
||||||
let rooms: HashMap<String, Arc<Room>> = HashMap::new();
|
|
||||||
let client_mutex = Arc::new(RwLock::new(clients));
|
|
||||||
let room_mutex = Arc::new(RwLock::new(rooms));
|
|
||||||
|
|
||||||
//start the UDP thread
|
|
||||||
let udp_clients = Arc::clone(&client_mutex);
|
|
||||||
let udp_rooms = Arc::clone(&room_mutex);
|
|
||||||
let udp_handle = thread::spawn(move ||{udp_listen(udp_clients, udp_rooms, config.port);});
|
|
||||||
//start the TCP thread
|
|
||||||
tcp_listen(client_mutex, room_mutex,config.port,config.tcp_timeout,config.tcp_send_buffer);
|
|
||||||
udp_handle.join().unwrap();
|
|
||||||
println!("{}: VelNet Ended", Local::now().format("%Y-%m-%d %H:%M:%S"));
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=VelNet Server
|
||||||
|
Requires=network.target
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
WorkingDirectory=
|
||||||
|
ExecStart=
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||