Compare commits

..

18 Commits

Author SHA1 Message Date
Jef Roosens d23227dd0b
fix(ci): use old platform
ci/woodpecker/tag/release Pipeline was successful Details
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/clippy Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details
2023-08-13 10:19:29 +02:00
Jef Roosens d3cb29b52e
chore: move PKGBUILD to separate repo 2023-08-13 10:19:29 +02:00
Jef Roosens 3cddea19c3
chore: revert to old platform syntax
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/clippy Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details
2023-08-12 15:47:05 +02:00
Jef Roosens 9ce2417528
chore: bump versions to 0.4.0
ci/woodpecker/tag/arch-release Pipeline was successful Details
ci/woodpecker/tag/release Pipeline was successful Details
ci/woodpecker/push/clippy Pipeline failed Details
ci/woodpecker/push/lint Pipeline failed Details
ci/woodpecker/push/build Pipeline failed Details
2023-08-12 15:06:58 +02:00
Jef Roosens f2e781dd5a
chore: bump dependency versions
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details
ci/woodpecker/push/clippy Pipeline was successful Details
2023-08-12 14:50:06 +02:00
Jef Roosens 5bdd4e21b0
chore(ci): modernize config
ci/woodpecker/push/clippy Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details
ci/woodpecker/push/lint Pipeline was successful Details
2023-08-12 14:09:22 +02:00
Jef Roosens 8f190c489b
chore: update changelog 2023-08-12 13:58:13 +02:00
Jef Roosens 5f6366078c
fix: properly parse layers env var
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/clippy Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details
2023-08-12 13:46:46 +02:00
Jef Roosens b3d1cec078
feat: granular locking for proper concurrent access to server process
ci/woodpecker/push/clippy Pipeline was successful Details
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details
2023-08-12 11:44:35 +02:00
Jef Roosens a51ff3937d
refactor: reorder imports
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/clippy Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details
2023-08-11 23:24:14 +02:00
Jef Roosens db3bba5a42
feat: also allow run args to be passed from toml file 2023-08-11 23:13:17 +02:00
Jef Roosens 34d016fd3f
feat: allow passing global configuration as TOML file 2023-08-11 21:26:17 +02:00
Jef Roosens bf83357464
feat: publish arch packages
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/clippy Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details
2023-07-08 16:11:36 +02:00
Jef Roosens bfb264e823
docs: add some more help strings
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/clippy Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details
2023-07-08 15:34:24 +02:00
Jef Roosens 241bb4d68e
feat: add extract command 2023-07-08 15:31:01 +02:00
Jef Roosens 6cdc18742e
feat: don't read non-contributing archives for export 2023-07-08 14:50:18 +02:00
Jef Roosens b924a054a6
chore: bump version to 0.3.1
ci/woodpecker/tag/lint Pipeline was successful Details
ci/woodpecker/tag/clippy Pipeline was successful Details
ci/woodpecker/tag/build Pipeline was successful Details
ci/woodpecker/tag/release Pipeline was successful Details
ci/woodpecker/push/release Pipeline was successful Details
2023-07-08 14:12:18 +02:00
Jef Roosens 32d923e64b
refactor: this is fun
ci/woodpecker/push/lint Pipeline was successful Details
ci/woodpecker/push/clippy Pipeline was successful Details
ci/woodpecker/push/build Pipeline was successful Details
2023-07-08 13:53:18 +02:00
29 changed files with 912 additions and 425 deletions

View File

@ -5,17 +5,17 @@ matrix:
platform: "linux/${ARCH}" platform: "linux/${ARCH}"
branches: when:
exclude: [main] branch:
exclude: [main]
event: push
pipeline: steps:
build: build:
image: 'rust:1.70-alpine3.18' image: 'rust:1.71-alpine3.18'
commands: commands:
- apk add --no-cache build-base - apk add --no-cache build-base
- cargo build --verbose - cargo build --verbose
- cargo test --verbose - cargo test --verbose
# Binaries, even debug ones, should be statically compiled # Binaries, even debug ones, should be statically compiled
- '[ "$(readelf -d target/debug/alex | grep NEEDED | wc -l)" = 0 ]' - '[ "$(readelf -d target/debug/alex | grep NEEDED | wc -l)" = 0 ]'
when:
event: [push]

View File

@ -1,13 +1,13 @@
platform: 'linux/amd64' platform: 'linux/amd64'
branches: when:
exclude: [main] branch:
exclude: [ main ]
event: push
pipeline: steps:
clippy: clippy:
image: 'rust:1.70' image: 'rust:1.71'
commands: commands:
- rustup component add clippy - rustup component add clippy
- cargo clippy -- --no-deps -Dwarnings - cargo clippy -- --no-deps -Dwarnings
when:
event: [push]

View File

@ -1,13 +1,13 @@
platform: 'linux/amd64' platform: 'linux/amd64'
branches: when:
exclude: [main] branch:
exclude: [ main ]
event: push
pipeline: steps:
lint: lint:
image: 'rust:1.70' image: 'rust:1.71'
commands: commands:
- rustup component add rustfmt - rustup component add rustfmt
- cargo fmt -- --check - cargo fmt -- --check
when:
event: [push]

View File

@ -4,19 +4,19 @@ matrix:
- 'linux/arm64' - 'linux/arm64'
platform: ${PLATFORM} platform: ${PLATFORM}
branches: [ main ]
pipeline: when:
event: tag
steps:
build: build:
image: 'rust:1.70-alpine3.18' image: 'rust:1.71-alpine3.18'
commands: commands:
- apk add --no-cache build-base - apk add --no-cache build-base
- cargo build --release --verbose - cargo build --release --verbose
# Ensure the release binary is also statically compiled # Ensure the release binary is also statically compiled
- '[ "$(readelf -d target/release/alex | grep NEEDED | wc -l)" = 0 ]' - '[ "$(readelf -d target/release/alex | grep NEEDED | wc -l)" = 0 ]'
- du -h target/release/alex - du -h target/release/alex
when:
event: tag
publish: publish:
image: 'curlimages/curl' image: 'curlimages/curl'
@ -28,5 +28,3 @@ pipeline:
--user "Chewing_Bever:$GITEA_PASSWORD" --user "Chewing_Bever:$GITEA_PASSWORD"
--upload-file target/release/alex --upload-file target/release/alex
https://git.rustybever.be/api/packages/Chewing_Bever/generic/alex/"${CI_COMMIT_TAG}"/alex-"$(echo '${PLATFORM}' | sed 's:/:-:g')" https://git.rustybever.be/api/packages/Chewing_Bever/generic/alex/"${CI_COMMIT_TAG}"/alex-"$(echo '${PLATFORM}' | sed 's:/:-:g')"
when:
event: tag

View File

@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased](https://git.rustybever.be/Chewing_Bever/alex/src/branch/dev) ## [Unreleased](https://git.rustybever.be/Chewing_Bever/alex/src/branch/dev)
## [0.4.1](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.4.1)
### Changed
* Moved PKGBUILD to separate repo
* Properly update lock file
## [0.4.0](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.4.0)
### Added
* Extract command for working with the output of export
* Arch packages are now published to my bur repo
* Allow passing configuration variables from TOML file
### Changed
* Export command no longer reads backups that do not contribute to the final
state
* Running backups no longer block stdin input or shutdown
* Env vars `ALEX_CONFIG_DIR`, `ALEX_WORLD_DIR` and `ALEX_BACKUP_DIR` renamed to
`ALEX_CONFIG`, `ALEX_WORLD` and `ALEX_BACKUP` respectively
## [0.3.1](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.3.1)
### Added ### Added
* Export command to export any backup as a new full backup * Export command to export any backup as a new full backup

349
Cargo.lock generated
View File

@ -10,10 +10,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "alex" name = "alex"
version = "0.3.0" version = "0.4.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
"figment",
"flate2", "flate2",
"serde", "serde",
"serde_json", "serde_json",
@ -53,15 +54,15 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.0" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
@ -77,14 +78,20 @@ dependencies = [
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"windows-sys", "windows-sys",
] ]
[[package]]
name = "atomic"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -97,6 +104,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.13.0" version = "3.13.0"
@ -105,9 +118,12 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.79" version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -133,9 +149,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.3.1" version = "4.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ed2379f8603fa2b7509891660e802b88c70a79a6427a70abb5968054de2c28" checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -144,22 +160,21 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.3.1" version = "4.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"bitflags",
"clap_lex", "clap_lex",
"strsim", "strsim",
] ]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.3.1" version = "4.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59e9ef9a08ee1c0e1f2e162121665ac45ac3783b0f897db7244ae75ad9a8f65b" checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -195,10 +210,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "errno" name = "equivalent"
version = "0.3.1" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
dependencies = [ dependencies = [
"errno-dragonfly", "errno-dragonfly",
"libc", "libc",
@ -216,10 +237,24 @@ dependencies = [
] ]
[[package]] [[package]]
name = "filetime" name = "figment"
version = "0.2.21" version = "0.10.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" checksum = "4547e226f4c9ab860571e070a9034192b3175580ecea38da34fcdb53a018c9a5"
dependencies = [
"atomic",
"pear",
"serde",
"toml",
"uncased",
"version_check",
]
[[package]]
name = "filetime"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -237,6 +272,12 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.4.1" version = "0.4.1"
@ -245,15 +286,15 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.56" version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
dependencies = [ dependencies = [
"android_system_properties", "android_system_properties",
"core-foundation-sys", "core-foundation-sys",
@ -273,60 +314,70 @@ dependencies = [
] ]
[[package]] [[package]]
name = "io-lifetimes" name = "indexmap"
version = "1.0.11" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [ dependencies = [
"hermit-abi", "equivalent",
"libc", "hashbrown",
"windows-sys",
] ]
[[package]] [[package]]
name = "is-terminal" name = "inlinable_string"
version = "0.4.7" version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi",
"io-lifetimes",
"rustix", "rustix",
"windows-sys", "windows-sys",
] ]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.6" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.63" version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.144" version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.3.8" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.18" version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
@ -339,55 +390,90 @@ dependencies = [
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.15" 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 = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.17.2" version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "pear"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c"
dependencies = [
"inlinable_string",
"pear_codegen",
"yansi",
]
[[package]]
name = "pear_codegen"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f0f13dac8069c139e8300a6510e3f4143ecf5259c60b116a9b271b4ca0d54"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.59" version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "proc-macro2-diagnostics"
version = "1.0.28" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"version_check",
"yansi",
]
[[package]]
name = "quote"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.16" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
] ]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.37.19" version = "0.38.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.4.0",
"errno", "errno",
"io-lifetimes",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys", "windows-sys",
@ -395,24 +481,24 @@ dependencies = [
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.13" 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 = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.164" version = "1.0.183"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.164" version = "1.0.183"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -421,9 +507,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.96" version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -431,10 +517,19 @@ dependencies = [
] ]
[[package]] [[package]]
name = "signal-hook" name = "serde_spanned"
version = "0.3.15" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
dependencies = [
"serde",
]
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [ dependencies = [
"libc", "libc",
"signal-hook-registry", "signal-hook-registry",
@ -457,9 +552,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.18" version = "2.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -468,9 +563,9 @@ dependencies = [
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.38" version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb"
dependencies = [ dependencies = [
"filetime", "filetime",
"libc", "libc",
@ -489,10 +584,53 @@ dependencies = [
] ]
[[package]] [[package]]
name = "unicode-ident" name = "toml"
version = "1.0.9" version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "uncased"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
@ -500,6 +638,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.10.0+wasi-snapshot-preview1" version = "0.10.0+wasi-snapshot-preview1"
@ -508,9 +652,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.86" version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@ -518,9 +662,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.86" version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
@ -533,9 +677,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.86" version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -543,9 +687,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.86" version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -556,9 +700,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.86" version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
[[package]] [[package]]
name = "winapi" name = "winapi"
@ -602,9 +746,9 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.0" version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm",
"windows_aarch64_msvc", "windows_aarch64_msvc",
@ -658,10 +802,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]] [[package]]
name = "xattr" name = "winnow"
version = "0.2.3" version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d"
dependencies = [
"memchr",
]
[[package]]
name = "xattr"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985"
dependencies = [ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "yansi"
version = "1.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "alex" name = "alex"
version = "0.3.0" version = "0.4.1"
description = "Wrapper around Minecraft server processes, designed to complement Docker image installations." description = "Wrapper around Minecraft server processes, designed to complement Docker image installations."
authors = ["Jef Roosens"] authors = ["Jef Roosens"]
edition = "2021" edition = "2021"
@ -15,8 +15,9 @@ flate2 = "1.0.26"
chrono = { version = "0.4.26", features = ["serde"] } chrono = { version = "0.4.26", features = ["serde"] }
clap = { version = "4.3.1", features = ["derive", "env"] } clap = { version = "4.3.1", features = ["derive", "env"] }
signal-hook = "0.3.15" signal-hook = "0.3.15"
serde = { version = "1.0.164", features = ["derive", "rc"] } serde = { version = "1.0.164", features = ["derive"] }
serde_json = "1.0.96" serde_json = "1.0.96"
figment = { version = "0.10.10", features = ["env", "toml"] }
[profile.release] [profile.release]
lto = "fat" lto = "fat"

View File

@ -47,9 +47,9 @@ COPY --from=builder /app/target/debug/alex /bin/alex
RUN chmod +x /bin/alex RUN chmod +x /bin/alex
# Default value to keep users from eating up all ram accidentally # Default value to keep users from eating up all ram accidentally
ENV ALEX_CONFIG_DIR=/app/config \ ENV ALEX_CONFIG=/app/config \
ALEX_WORLD_DIR=/app/worlds \ ALEX_WORLD=/app/worlds \
ALEX_BACKUP_DIR=/app/backups \ ALEX_BACKUP=/app/backups \
ALEX_SERVER=paper \ ALEX_SERVER=paper \
ALEX_XMS=1024 \ ALEX_XMS=1024 \
ALEX_XMX=2048 \ ALEX_XMX=2048 \

View File

@ -11,6 +11,20 @@ Alex is distributed as statically compiled binaries for Linux amd64 and arm64.
These can be found These can be found
[here](https://git.rustybever.be/Chewing_Bever/alex/packages). [here](https://git.rustybever.be/Chewing_Bever/alex/packages).
### Arch
Arch users can install prebuilt `x86_64` & `aarch64` packages from my `bur`
repository. Add the following at the bottom of your `pacman.conf`:
```toml
[bur]
Server = https://arch.r8r.be/$repo/$arch
SigLevel = Optional
```
If you prefer building the package yourself, the PKGBUILD can be found
[here](https://git.rustybever.be/bur/alex-mc).
### Dockerfiles ### Dockerfiles
You can easily install alex in your Docker images by letting Docker download it You can easily install alex in your Docker images by letting Docker download it

16
alex-example.toml 100644
View File

@ -0,0 +1,16 @@
config = "data/config"
world = "data/worlds"
backup = "data/backups"
server = "Paper"
# [[layers]]
# name = "2min"
# frequency = 2
# chains = 4
# chain_len = 4
# [[layers]]
# name = "3min"
# frequency = 3
# chains = 2
# chain_len = 2

View File

@ -1,7 +1,8 @@
use super::State; use std::{borrow::Borrow, fmt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::fmt; use super::State;
/// Represents the changes relative to the previous backup /// Represents the changes relative to the previous backup
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -18,11 +19,18 @@ pub struct Delta {
impl Delta { impl Delta {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
added: Default::default(), added: State::new(),
removed: Default::default(), removed: State::new(),
} }
} }
/// Returns whether the delta is empty by checking whether both its added and removed state
/// return true for their `is_empty`.
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.added.is_empty() && self.removed.is_empty()
}
/// Calculate the union of this delta with another delta. /// Calculate the union of this delta with another delta.
/// ///
/// The union of two deltas is a delta that produces the same state as if you were to apply /// The union of two deltas is a delta that produces the same state as if you were to apply

View File

@ -1,8 +1,8 @@
use std::error::Error; use std::{error::Error, fmt, str::FromStr};
use std::fmt;
use std::str::FromStr;
#[derive(Clone, Debug)] use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ManagerConfig { pub struct ManagerConfig {
pub name: String, pub name: String,
pub frequency: u32, pub frequency: u32,

View File

@ -1,10 +1,13 @@
use super::{Manager, ManagerConfig}; use std::{
collections::HashMap,
io,
path::{Path, PathBuf},
};
use chrono::Utc; use chrono::Utc;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use serde::Serialize;
use std::collections::HashMap; use super::{Manager, ManagerConfig};
use std::io;
use std::path::{Path, PathBuf};
/// Manages a collection of backup layers, allowing them to be utilized as a single object. /// Manages a collection of backup layers, allowing them to be utilized as a single object.
pub struct MetaManager<T> pub struct MetaManager<T>

View File

@ -1,20 +1,20 @@
mod config; mod config;
mod meta; mod meta;
pub use config::ManagerConfig; use std::{
pub use meta::MetaManager; fs::{File, OpenOptions},
io,
path::{Path, PathBuf},
};
use chrono::{SubsecRound, Utc};
use flate2::{write::GzEncoder, Compression};
use serde::{Deserialize, Serialize};
use super::{Backup, BackupType, Delta, State}; use super::{Backup, BackupType, Delta, State};
use crate::other; use crate::other;
use chrono::SubsecRound; pub use config::ManagerConfig;
use chrono::Utc; pub use meta::MetaManager;
use flate2::write::GzEncoder;
use flate2::Compression;
use serde::Deserialize;
use serde::Serialize;
use std::fs::{File, OpenOptions};
use std::io;
use std::path::{Path, PathBuf};
/// Manages a single backup layer consisting of one or more chains of backups. /// Manages a single backup layer consisting of one or more chains of backups.
pub struct Manager<T> pub struct Manager<T>
@ -153,13 +153,11 @@ where
/// Calculate the next time a backup should be created. If no backup has been created yet, it /// Calculate the next time a backup should be created. If no backup has been created yet, it
/// will return now. /// will return now.
pub fn next_scheduled_time(&self) -> chrono::DateTime<Utc> { pub fn next_scheduled_time(&self) -> chrono::DateTime<Utc> {
if let Some(last_chain) = self.chains.last() { self.chains
if let Some(last_backup) = last_chain.last() { .last()
return last_backup.start_time + self.frequency; .and_then(|last_chain| last_chain.last())
} .map(|last_backup| last_backup.start_time + self.frequency)
} .unwrap_or_else(chrono::offset::Utc::now)
chrono::offset::Utc::now()
} }
/// Search for a chain containing a backup with the specified start time. /// Search for a chain containing a backup with the specified start time.
@ -228,9 +226,16 @@ where
let enc = GzEncoder::new(tar_gz, Compression::default()); let enc = GzEncoder::new(tar_gz, Compression::default());
let mut ar = tar::Builder::new(enc); let mut ar = tar::Builder::new(enc);
for (contribution, backup) in // We only need to consider backups that have a non-empty contribution.
contributions.iter().rev().zip(chain.iter().take(index + 1)) // This allows us to skip reading backups that have been completely
// overwritten by their successors anyways.
for (contribution, backup) in contributions
.iter()
.rev()
.zip(chain.iter().take(index + 1))
.filter(|(contribution, _)| !contribution.is_empty())
{ {
println!("{}", &backup);
backup.append(&self.backup_dir, contribution, &mut ar)?; backup.append(&self.backup_dir, contribution, &mut ar)?;
} }

View File

@ -4,23 +4,24 @@ pub mod manager;
mod path; mod path;
mod state; mod state;
use std::{
collections::HashSet,
fmt,
fs::File,
io,
path::{Path, PathBuf},
};
use chrono::Utc;
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use serde::{Deserialize, Serialize};
use delta::Delta; use delta::Delta;
pub use manager::Manager; pub use manager::Manager;
pub use manager::ManagerConfig; pub use manager::ManagerConfig;
pub use manager::MetaManager; pub use manager::MetaManager;
pub use state::State;
use chrono::Utc;
use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use path::PathExt; use path::PathExt;
use serde::{Deserialize, Serialize}; pub use state::State;
use std::collections::HashSet;
use std::fmt;
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
const BYTE_SUFFIXES: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"]; const BYTE_SUFFIXES: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"];
@ -55,6 +56,45 @@ impl Backup<()> {
let filename = format!("{}", start_time.format(Self::FILENAME_FORMAT)); let filename = format!("{}", start_time.format(Self::FILENAME_FORMAT));
backup_dir.join(filename) backup_dir.join(filename)
} }
/// Extract an archive.
///
/// # Arguments
///
/// * `backup_path` - Path to the archive to extract
/// * `dirs` - list of tuples `(path_in_tar, dst_dir)` with `dst_dir` the directory on-disk
/// where the files stored under `path_in_tar` inside the tarball should be extracted to.
pub fn extract_archive<P: AsRef<Path>>(
archive_path: P,
dirs: &Vec<(PathBuf, PathBuf)>,
) -> io::Result<()> {
let tar_gz = File::open(archive_path)?;
let enc = GzDecoder::new(tar_gz);
let mut ar = tar::Archive::new(enc);
// Unpack each file by matching it with one of the destination directories and extracting
// it to the right path
for entry in ar.entries()? {
let mut entry = entry?;
let entry_path_in_tar = entry.path()?.to_path_buf();
for (path_in_tar, dst_dir) in dirs {
if entry_path_in_tar.starts_with(path_in_tar) {
let dst_path =
dst_dir.join(entry_path_in_tar.strip_prefix(path_in_tar).unwrap());
// Ensure all parent directories are present
std::fs::create_dir_all(dst_path.parent().unwrap())?;
entry.unpack(dst_path)?;
break;
}
}
}
Ok(())
}
} }
impl<T: Clone> Backup<T> { impl<T: Clone> Backup<T> {
@ -199,31 +239,8 @@ impl<T: Clone> Backup<T> {
backup_dir: P, backup_dir: P,
dirs: &Vec<(PathBuf, PathBuf)>, dirs: &Vec<(PathBuf, PathBuf)>,
) -> io::Result<()> { ) -> io::Result<()> {
let path = Backup::path(backup_dir, self.start_time); let backup_path = Backup::path(backup_dir, self.start_time);
let tar_gz = File::open(path)?; Backup::extract_archive(backup_path, dirs)?;
let enc = GzDecoder::new(tar_gz);
let mut ar = tar::Archive::new(enc);
// Unpack each file by matching it with one of the destination directories and extracting
// it to the right path
for entry in ar.entries()? {
let mut entry = entry?;
let entry_path_in_tar = entry.path()?.to_path_buf();
for (path_in_tar, dst_dir) in dirs {
if entry_path_in_tar.starts_with(path_in_tar) {
let dst_path =
dst_dir.join(entry_path_in_tar.strip_prefix(path_in_tar).unwrap());
// Ensure all parent directories are present
std::fs::create_dir_all(dst_path.parent().unwrap())?;
entry.unpack(dst_path)?;
break;
}
}
}
// Remove any files // Remove any files
for (path_in_tar, dst_dir) in dirs { for (path_in_tar, dst_dir) in dirs {

View File

@ -1,9 +1,12 @@
use std::{
collections::HashSet,
ffi::OsString,
fs::{self, DirEntry},
io,
path::{Path, PathBuf},
};
use chrono::{Local, Utc}; use chrono::{Local, Utc};
use std::collections::HashSet;
use std::ffi::OsString;
use std::fs::DirEntry;
use std::path::{Path, PathBuf};
use std::{fs, io};
pub struct ReadDirRecursive { pub struct ReadDirRecursive {
ignored: HashSet<OsString>, ignored: HashSet<OsString>,

View File

@ -1,9 +1,13 @@
use crate::backup::Delta; use std::{
borrow::Borrow,
collections::{HashMap, HashSet},
ops::{Deref, DerefMut},
path::{Path, PathBuf},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::collections::{HashMap, HashSet}; use crate::backup::Delta;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
/// Struct that represents a current state for a backup. This struct acts as a smart pointer around /// Struct that represents a current state for a backup. This struct acts as a smart pointer around
/// a HashMap. /// a HashMap.
@ -41,6 +45,14 @@ impl State {
path.starts_with(dir) && files.contains(path.strip_prefix(dir).unwrap()) path.starts_with(dir) && files.contains(path.strip_prefix(dir).unwrap())
}) })
} }
/// Returns whether the state is empty.
///
/// Note that this does not necessarily mean that the state does not contain any sets, but
/// rather that any sets that it does contain are also empty.
pub fn is_empty(&self) -> bool {
self.0.values().all(|s| s.is_empty())
}
} }
impl<T> From<T> for State impl<T> From<T> for State

View File

@ -1,21 +1,38 @@
use crate::backup::Backup;
use crate::cli::Cli;
use crate::other;
use chrono::{TimeZone, Utc};
use clap::{Args, Subcommand};
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use chrono::{TimeZone, Utc};
use clap::{Args, Subcommand};
use crate::{backup::Backup, other};
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum BackupCommands { pub enum BackupCommands {
/// List all tracked backups /// List all tracked backups
///
/// Note that this will only list backups for the layers currently configured, and will ignore
/// any other layers also present in the backup directory.
List(BackupListArgs), List(BackupListArgs),
/// Manually create a new backup /// Manually create a new backup
///
/// Note that backups created using this command will count towards the length of a chain, and
/// can therefore shorten how far back in time your backups will be stored.
Create(BackupCreateArgs), Create(BackupCreateArgs),
/// Restore a backup /// Restore a backup
///
/// This command will restore the selected backup by extracting its entire chain up to and
/// including the requested backup in-order.
Restore(BackupRestoreArgs), Restore(BackupRestoreArgs),
/// Export a backup into a full archive /// Export a backup into a full archive
///
/// Just like the restore command, this will extract each backup from the chain up to and
/// including the requested backup, but instead of writing the files to disk, they will be
/// recompressed into a new tarball, resulting in a new tarball containing a full backup.
Export(BackupExportArgs), Export(BackupExportArgs),
/// Extract an archive file, which is assumed to be a full backup.
///
/// This command mostly exists as a convenience method for working with the output of `export`.
Extract(BackupExtractArgs),
} }
#[derive(Args)] #[derive(Args)]
@ -45,6 +62,9 @@ pub struct BackupRestoreArgs {
/// Directory to store worlds in /// Directory to store worlds in
output_worlds: PathBuf, output_worlds: PathBuf,
/// Whether to overwrite the contents of the output directories /// Whether to overwrite the contents of the output directories
///
/// If set, the output directories will be completely cleared before trying to restore the
/// backup.
#[arg(short, long, default_value_t = false)] #[arg(short, long, default_value_t = false)]
force: bool, force: bool,
/// Create output directories if they don't exist /// Create output directories if they don't exist
@ -63,19 +83,39 @@ pub struct BackupExportArgs {
make: bool, make: bool,
} }
#[derive(Args)]
pub struct BackupExtractArgs {
/// Path to the backup to extract
path: PathBuf,
/// Directory to store config in
output_config: PathBuf,
/// Directory to store worlds in
output_worlds: PathBuf,
/// Whether to overwrite the contents of the output directories
///
/// If set, the output directories will be completely cleared before trying to restore the
/// backup.
#[arg(short, long, default_value_t = false)]
force: bool,
/// Create output directories if they don't exist
#[arg(short, long, default_value_t = false)]
make: bool,
}
impl BackupArgs { impl BackupArgs {
pub fn run(&self, cli: &Cli) -> io::Result<()> { pub fn run(&self, cli: &super::Config) -> io::Result<()> {
match &self.command { match &self.command {
BackupCommands::Create(args) => args.run(cli), BackupCommands::Create(args) => args.run(cli),
BackupCommands::List(args) => args.run(cli), BackupCommands::List(args) => args.run(cli),
BackupCommands::Restore(args) => args.run(cli), BackupCommands::Restore(args) => args.run(cli),
BackupCommands::Export(args) => args.run(cli), BackupCommands::Export(args) => args.run(cli),
BackupCommands::Extract(args) => args.run(cli),
} }
} }
} }
impl BackupCreateArgs { impl BackupCreateArgs {
pub fn run(&self, cli: &Cli) -> io::Result<()> { pub fn run(&self, cli: &super::Config) -> io::Result<()> {
let mut meta = cli.meta()?; let mut meta = cli.meta()?;
if let Some(res) = meta.create_backup(&self.layer) { if let Some(res) = meta.create_backup(&self.layer) {
@ -87,7 +127,7 @@ impl BackupCreateArgs {
} }
impl BackupListArgs { impl BackupListArgs {
pub fn run(&self, cli: &Cli) -> io::Result<()> { pub fn run(&self, cli: &super::Config) -> io::Result<()> {
let meta = cli.meta()?; let meta = cli.meta()?;
// A bit scuffed? Sure // A bit scuffed? Sure
@ -144,7 +184,7 @@ fn parse_backup_path(
} }
impl BackupRestoreArgs { impl BackupRestoreArgs {
pub fn run(&self, cli: &Cli) -> io::Result<()> { pub fn run(&self, cli: &super::Config) -> io::Result<()> {
let backup_dir = cli.backup.canonicalize()?; let backup_dir = cli.backup.canonicalize()?;
// Create directories if needed // Create directories if needed
@ -197,7 +237,7 @@ impl BackupRestoreArgs {
} }
impl BackupExportArgs { impl BackupExportArgs {
pub fn run(&self, cli: &Cli) -> io::Result<()> { pub fn run(&self, cli: &super::Config) -> io::Result<()> {
let backup_dir = cli.backup.canonicalize()?; let backup_dir = cli.backup.canonicalize()?;
if self.make { if self.make {
@ -219,3 +259,44 @@ impl BackupExportArgs {
} }
} }
} }
impl BackupExtractArgs {
pub fn run(&self, _cli: &super::Config) -> io::Result<()> {
// Create directories if needed
if self.make {
std::fs::create_dir_all(&self.output_config)?;
std::fs::create_dir_all(&self.output_worlds)?;
}
let output_config = self.output_config.canonicalize()?;
let output_worlds = self.output_worlds.canonicalize()?;
let backup_path = self.path.canonicalize()?;
// Clear previous contents of directories
let mut entries = output_config
.read_dir()?
.chain(output_worlds.read_dir()?)
.peekable();
if entries.peek().is_some() && !self.force {
return Err(other("Output directories are not empty. If you wish to overwrite these contents, use the force flag."));
}
for entry in entries {
let path = entry?.path();
if path.is_dir() {
std::fs::remove_dir_all(path)?;
} else {
std::fs::remove_file(path)?;
}
}
let dirs = vec![
(PathBuf::from("config"), output_config),
(PathBuf::from("worlds"), output_worlds),
];
Backup::extract_archive(backup_path, &dirs)
}
}

49
src/cli/config.rs 100644
View File

@ -0,0 +1,49 @@
use std::{io, path::PathBuf};
use serde::{Deserialize, Serialize};
use crate::{
backup::{ManagerConfig, MetaManager},
server::{Metadata, ServerType},
};
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub config: PathBuf,
pub world: PathBuf,
pub backup: PathBuf,
pub layers: Vec<ManagerConfig>,
pub server: ServerType,
pub server_version: String,
}
impl Default for Config {
fn default() -> Self {
Self {
config: PathBuf::from("."),
world: PathBuf::from("../worlds"),
backup: PathBuf::from("../backups"),
layers: Vec::new(),
server: ServerType::Unknown,
server_version: String::from(""),
}
}
}
impl Config {
/// Convenience method to initialize backup manager from the cli arguments
pub fn meta(&self) -> io::Result<MetaManager<Metadata>> {
let metadata = Metadata {
server_type: self.server,
server_version: self.server_version.clone(),
};
let dirs = vec![
(PathBuf::from("config"), self.config.canonicalize()?),
(PathBuf::from("worlds"), self.world.canonicalize()?),
];
let mut meta = MetaManager::new(self.backup.canonicalize()?, dirs, metadata);
meta.add_all(&self.layers)?;
Ok(meta)
}
}

View File

@ -1,92 +1,119 @@
mod backup; mod backup;
mod config;
mod run; mod run;
pub use crate::backup::MetaManager; use std::{path::PathBuf, str::FromStr};
pub use crate::server::Metadata;
pub use backup::{BackupArgs, BackupCommands};
pub use run::RunArgs;
use crate::backup::ManagerConfig; use clap::{Args, Parser, Subcommand};
use crate::server::ServerType; use figment::{
use clap::{Parser, Subcommand}; providers::{Env, Format, Serialized, Toml},
use std::io; Figment,
use std::path::PathBuf; };
use serde::{Deserialize, Serialize};
use crate::{backup::ManagerConfig, server::ServerType};
use backup::BackupArgs;
use config::Config;
use run::RunCli;
#[derive(Parser, Serialize)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
#[serde(skip)]
pub command: Commands,
/// Path to a TOML configuration file
#[arg(long = "config-file", global = true)]
pub config_file: Option<PathBuf>,
#[command(flatten)]
pub args: CliArgs,
}
#[derive(Args, Serialize, Deserialize, Clone)]
pub struct CliArgs {
/// Directory where configs are stored, and where the server will run
#[arg(long, value_name = "CONFIG_DIR", global = true)]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub config: Option<PathBuf>,
/// Directory where world files will be saved
#[arg(long, value_name = "WORLD_DIR", global = true)]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub world: Option<PathBuf>,
/// Directory where backups will be stored
#[arg(long, value_name = "BACKUP_DIR", global = true)]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub backup: Option<PathBuf>,
/// What backup layers to employ, provided as a list of tuples name,frequency,chains,chain_len
/// delimited by semicolons (;).
#[arg(long, global = true, value_delimiter = ';')]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub layers: Option<Vec<ManagerConfig>>,
/// Type of server
#[arg(long, global = true)]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub server: Option<ServerType>,
/// Version string for the server, e.g. 1.19.4-545
#[arg(long, global = true)]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub server_version: Option<String>,
}
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum Commands { pub enum Commands {
/// Run the server /// Run the server
Run(RunArgs), Run(RunCli),
/// Interact with the backup system without starting a server /// Interact with the backup system without starting a server
Backup(BackupArgs), Backup(BackupArgs),
} }
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
/// Directory where configs are stored, and where the server will run
#[arg(
long,
value_name = "CONFIG_DIR",
default_value = ".",
env = "ALEX_CONFIG_DIR",
global = true
)]
pub config: PathBuf,
/// Directory where world files will be saved
#[arg(
long,
value_name = "WORLD_DIR",
default_value = "../worlds",
env = "ALEX_WORLD_DIR",
global = true
)]
pub world: PathBuf,
/// Directory where backups will be stored
#[arg(
long,
value_name = "BACKUP_DIR",
default_value = "../backups",
env = "ALEX_BACKUP_DIR",
global = true
)]
pub backup: PathBuf,
/// What backup layers to employ, provided as a list of tuples name,frequency,chains,chain_len
/// delimited by semicolons (;).
#[arg(long, env = "ALEX_LAYERS", global = true, value_delimiter = ';')]
pub layers: Vec<ManagerConfig>,
/// Type of server
#[arg(long, default_value = "unknown", env = "ALEX_SERVER", global = true)]
pub server: ServerType,
/// Version string for the server, e.g. 1.19.4-545
#[arg(long, default_value = "", env = "ALEX_SERVER_VERSION", global = true)]
pub server_version: String,
}
impl Cli { impl Cli {
pub fn run(&self) -> io::Result<()> { pub fn run(&self) -> crate::Result<()> {
let config = self.config(&self.args)?;
match &self.command { match &self.command {
Commands::Run(args) => args.run(self), Commands::Run(args) => args.run(self, &config),
Commands::Backup(args) => args.run(self), Commands::Backup(args) => Ok(args.run(&config)?),
} }
} }
/// Convenience method to initialize backup manager from the cli arguments pub fn config<T, U>(&self, args: &U) -> crate::Result<T>
pub fn meta(&self) -> io::Result<MetaManager<Metadata>> { where
let metadata = Metadata { T: Default + Serialize + for<'de> Deserialize<'de>,
server_type: self.server, U: Serialize,
server_version: self.server_version.clone(), {
}; let toml_file = self
let dirs = vec![ .config_file
(PathBuf::from("config"), self.config.canonicalize()?), .clone()
(PathBuf::from("worlds"), self.world.canonicalize()?), .unwrap_or(PathBuf::from(Env::var_or("ALEX_CONFIG_FILE", "")));
];
let mut meta = MetaManager::new(self.backup.canonicalize()?, dirs, metadata);
meta.add_all(&self.layers)?;
Ok(meta) let mut figment = Figment::new()
.merge(Serialized::defaults(T::default()))
.merge(Toml::file(toml_file))
.merge(Env::prefixed("ALEX_").ignore(&["ALEX_LAYERS"]));
// Layers need to be parsed separately, as the env var format is different than the one
// serde expects
if let Some(layers_env) = Env::var("ALEX_LAYERS") {
let res = layers_env
.split(';')
.map(ManagerConfig::from_str)
.collect::<Vec<_>>();
if res.iter().any(|e| e.is_err()) {
return Err(crate::other("Invalid layer configuration").into());
}
let layers: Vec<_> = res.iter().flatten().collect();
figment = figment.merge(Serialized::default("layers", layers));
}
Ok(figment.merge(Serialized::defaults(args)).extract()?)
} }
} }

View File

@ -1,94 +0,0 @@
use crate::cli::Cli;
use crate::server;
use crate::signals;
use crate::stdin;
use clap::Args;
use std::io;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
#[derive(Args)]
pub struct RunArgs {
/// Server jar to execute
#[arg(
long,
value_name = "JAR_PATH",
default_value = "server.jar",
env = "ALEX_JAR"
)]
pub jar: PathBuf,
/// Java command to run the server jar with
#[arg(long, value_name = "JAVA_CMD", default_value_t = String::from("java"), env = "ALEX_JAVA")]
pub java: String,
/// XMS value in megabytes for the server instance
#[arg(long, default_value_t = 1024, env = "ALEX_XMS")]
pub xms: u64,
/// XMX value in megabytes for the server instance
#[arg(long, default_value_t = 2048, env = "ALEX_XMX")]
pub xmx: u64,
/// Don't actually run the server, but simply output the server configuration that would have
/// been ran
#[arg(short, long, default_value_t = false)]
pub dry: bool,
}
fn backups_thread(counter: Arc<Mutex<server::ServerProcess>>) {
loop {
let next_scheduled_time = {
let server = counter.lock().unwrap();
server.backups.next_scheduled_time().unwrap()
};
let now = chrono::offset::Utc::now();
if next_scheduled_time > now {
std::thread::sleep((next_scheduled_time - now).to_std().unwrap());
}
{
let mut server = counter.lock().unwrap();
// We explicitely ignore the error here, as we don't want the thread to fail
let _ = server.backup();
}
}
}
impl RunArgs {
pub fn run(&self, cli: &Cli) -> io::Result<()> {
let (_, mut signals) = signals::install_signal_handlers()?;
let mut cmd = server::ServerCommand::new(cli.server, &cli.server_version)
.java(&self.java)
.jar(self.jar.clone())
.config(cli.config.clone())
.world(cli.world.clone())
.backup(cli.backup.clone())
.managers(cli.layers.clone())
.xms(self.xms)
.xmx(self.xmx);
cmd.canonicalize()?;
if self.dry {
print!("{}", cmd);
return Ok(());
}
let counter = Arc::new(Mutex::new(cmd.spawn()?));
if !cli.layers.is_empty() {
let clone = Arc::clone(&counter);
std::thread::spawn(move || backups_thread(clone));
}
// Spawn thread that handles the main stdin loop
let clone = Arc::clone(&counter);
std::thread::spawn(move || stdin::handle_stdin(clone));
// Signal handler loop exits the process when necessary
signals::handle_signals(&mut signals, counter)
}
}

View File

@ -0,0 +1,22 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
pub jar: PathBuf,
pub java: String,
pub xms: u64,
pub xmx: u64,
}
impl Default for Config {
fn default() -> Self {
Self {
jar: PathBuf::from("server.jar"),
java: String::from("java"),
xms: 1024,
xmx: 2048,
}
}
}

103
src/cli/run/mod.rs 100644
View File

@ -0,0 +1,103 @@
mod config;
use std::{path::PathBuf, sync::Arc};
use clap::Args;
use serde::{Deserialize, Serialize};
use crate::{server, signals, stdin};
use config::Config;
#[derive(Args)]
pub struct RunCli {
#[command(flatten)]
pub args: RunArgs,
/// Don't actually run the server, but simply output the server configuration that would have
/// been ran
#[arg(short, long, default_value_t = false)]
pub dry: bool,
}
#[derive(Args, Serialize, Deserialize, Clone)]
pub struct RunArgs {
/// Server jar to execute
#[arg(long, value_name = "JAR_PATH")]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub jar: Option<PathBuf>,
/// Java command to run the server jar with
#[arg(long, value_name = "JAVA_CMD")]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub java: Option<String>,
/// XMS value in megabytes for the server instance
#[arg(long)]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub xms: Option<u64>,
/// XMX value in megabytes for the server instance
#[arg(long)]
#[serde(skip_serializing_if = "::std::option::Option::is_none")]
pub xmx: Option<u64>,
}
fn backups_thread(server: Arc<server::ServerProcess>) {
loop {
let next_scheduled_time = {
server
.backups
.read()
.unwrap()
.next_scheduled_time()
.unwrap()
};
let now = chrono::offset::Utc::now();
if next_scheduled_time > now {
std::thread::sleep((next_scheduled_time - now).to_std().unwrap());
}
// We explicitely ignore the error here, as we don't want the thread to fail
let _ = server.backup();
}
}
impl RunCli {
pub fn run(&self, cli: &super::Cli, global: &super::Config) -> crate::Result<()> {
let config: Config = cli.config(&self.args)?;
let (_, mut signals) = signals::install_signal_handlers()?;
let mut cmd = server::ServerCommand::new(global.server, &global.server_version)
.java(&config.java)
.jar(config.jar.clone())
.config(global.config.clone())
.world(global.world.clone())
.backup(global.backup.clone())
.managers(global.layers.clone())
.xms(config.xms)
.xmx(config.xmx);
cmd.canonicalize()?;
if self.dry {
print!("{}", cmd);
return Ok(());
}
let counter = Arc::new(cmd.spawn()?);
if !global.layers.is_empty() {
let clone = Arc::clone(&counter);
std::thread::spawn(move || backups_thread(clone));
}
// Spawn thread that handles the main stdin loop
let clone = Arc::clone(&counter);
std::thread::spawn(move || stdin::handle_stdin(clone));
// Signal handler loop exits the process when necessary
Ok(signals::handle_signals(&mut signals, counter)?)
}
}

32
src/error.rs 100644
View File

@ -0,0 +1,32 @@
use std::{fmt, io};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
IO(io::Error),
Figment(figment::Error),
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::IO(err) => write!(fmt, "{}", err),
Error::Figment(err) => write!(fmt, "{}", err),
}
}
}
impl std::error::Error for Error {}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::IO(err)
}
}
impl From<figment::Error> for Error {
fn from(err: figment::Error) -> Self {
Error::Figment(err)
}
}

View File

@ -1,13 +1,17 @@
mod backup; mod backup;
mod cli; mod cli;
mod error;
mod server; mod server;
mod signals; mod signals;
mod stdin; mod stdin;
use crate::cli::Cli;
use clap::Parser;
use std::io; use std::io;
use clap::Parser;
use crate::cli::Cli;
pub use error::{Error, Result};
pub fn other(msg: &str) -> io::Error { pub fn other(msg: &str) -> io::Error {
io::Error::new(io::ErrorKind::Other, msg) io::Error::new(io::ErrorKind::Other, msg)
} }
@ -32,7 +36,8 @@ pub fn other(msg: &str) -> io::Error {
// // manager.remove_old_backups() // // manager.remove_old_backups()
// } // }
fn main() -> io::Result<()> { fn main() -> crate::Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
cli.run() cli.run()
} }

View File

@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum ServerType { pub enum ServerType {
Unknown, Unknown,
Paper, Paper,

View File

@ -1,22 +1,21 @@
use crate::backup::MetaManager; use std::{io::Write, process::Child, sync::RwLock};
use crate::server::Metadata;
use std::io::Write; use crate::{backup::MetaManager, server::Metadata};
use std::process::Child;
pub struct ServerProcess { pub struct ServerProcess {
child: Child, child: RwLock<Child>,
pub backups: MetaManager<Metadata>, pub backups: RwLock<MetaManager<Metadata>>,
} }
impl ServerProcess { impl ServerProcess {
pub fn new(manager: MetaManager<Metadata>, child: Child) -> ServerProcess { pub fn new(manager: MetaManager<Metadata>, child: Child) -> ServerProcess {
ServerProcess { ServerProcess {
child, child: RwLock::new(child),
backups: manager, backups: RwLock::new(manager),
} }
} }
pub fn send_command(&mut self, cmd: &str) -> std::io::Result<()> { pub fn send_command(&self, cmd: &str) -> std::io::Result<()> {
match cmd.trim() { match cmd.trim() {
"stop" | "exit" => self.stop()?, "stop" | "exit" => self.stop()?,
"backup" => self.backup()?, "backup" => self.backup()?,
@ -26,29 +25,34 @@ impl ServerProcess {
Ok(()) Ok(())
} }
fn custom(&mut self, cmd: &str) -> std::io::Result<()> { fn custom(&self, cmd: &str) -> std::io::Result<()> {
let mut stdin = self.child.stdin.as_ref().unwrap(); let child = self.child.write().unwrap();
let mut stdin = child.stdin.as_ref().unwrap();
stdin.write_all(format!("{}\n", cmd.trim()).as_bytes())?; stdin.write_all(format!("{}\n", cmd.trim()).as_bytes())?;
stdin.flush()?; stdin.flush()?;
Ok(()) Ok(())
} }
pub fn stop(&mut self) -> std::io::Result<()> { pub fn stop(&self) -> std::io::Result<()> {
self.custom("stop")?; self.custom("stop")?;
self.child.wait()?;
self.child.write().unwrap().wait()?;
Ok(()) Ok(())
} }
pub fn kill(&mut self) -> std::io::Result<()> { pub fn kill(&self) -> std::io::Result<()> {
self.child.kill() self.child.write().unwrap().kill()
} }
/// Perform a backup by disabling the server's save feature and flushing its data, before /// Perform a backup by disabling the server's save feature and flushing its data, before
/// creating an archive file. /// creating an archive file.
pub fn backup(&mut self) -> std::io::Result<()> { pub fn backup(&self) -> std::io::Result<()> {
let layer_name = String::from(self.backups.next_scheduled_layer().unwrap()); // We explicitely lock this entire function to prevent parallel backups
let mut backups = self.backups.write().unwrap();
let layer_name = String::from(backups.next_scheduled_layer().unwrap());
self.custom(&format!("say starting backup for layer '{}'", layer_name))?; self.custom(&format!("say starting backup for layer '{}'", layer_name))?;
// Make sure the server isn't modifying the files during the backup // Make sure the server isn't modifying the files during the backup
@ -60,7 +64,7 @@ impl ServerProcess {
std::thread::sleep(std::time::Duration::from_secs(10)); std::thread::sleep(std::time::Duration::from_secs(10));
let start_time = chrono::offset::Utc::now(); let start_time = chrono::offset::Utc::now();
let res = self.backups.perform_backup_cycle(); let res = backups.perform_backup_cycle();
// The server's save feature needs to be enabled again even if the archive failed to create // The server's save feature needs to be enabled again even if the archive failed to create
self.custom("save-on")?; self.custom("save-on")?;

View File

@ -1,10 +1,13 @@
use std::io; use std::{
use std::sync::atomic::AtomicBool; io,
use std::sync::{Arc, Mutex}; sync::{atomic::AtomicBool, Arc},
};
use signal_hook::consts::TERM_SIGNALS; use signal_hook::{
use signal_hook::flag; consts::TERM_SIGNALS,
use signal_hook::iterator::{Signals, SignalsInfo}; flag,
iterator::{Signals, SignalsInfo},
};
use crate::server; use crate::server;
@ -34,7 +37,7 @@ pub fn install_signal_handlers() -> io::Result<(Arc<AtomicBool>, SignalsInfo)> {
/// Loop that handles terminating signals as they come in. /// Loop that handles terminating signals as they come in.
pub fn handle_signals( pub fn handle_signals(
signals: &mut SignalsInfo, signals: &mut SignalsInfo,
counter: Arc<Mutex<server::ServerProcess>>, server: Arc<server::ServerProcess>,
) -> io::Result<()> { ) -> io::Result<()> {
let mut force = false; let mut force = false;
@ -46,17 +49,15 @@ pub fn handle_signals(
// This will currently not work, as the initial stop command will block the kill from // This will currently not work, as the initial stop command will block the kill from
// happening. // happening.
if force { if force {
let mut server = counter.lock().unwrap();
return server.kill(); return server.kill();
} }
// The stop command runs in a separate thread to avoid blocking the signal handling loop. // The stop command runs in a separate thread to avoid blocking the signal handling loop.
// After stopping the server, the thread terminates the process. // After stopping the server, the thread terminates the process.
else { else {
let clone = Arc::clone(&counter); let clone = Arc::clone(&server);
std::thread::spawn(move || { std::thread::spawn(move || {
let mut server = clone.lock().unwrap(); let _ = clone.stop();
let _ = server.stop();
std::process::exit(0); std::process::exit(0);
}); });
} }

View File

@ -1,9 +1,8 @@
use std::io; use std::{io, sync::Arc};
use std::sync::{Arc, Mutex};
use crate::server; use crate::server;
pub fn handle_stdin(counter: Arc<Mutex<server::ServerProcess>>) { pub fn handle_stdin(server: Arc<server::ServerProcess>) {
let stdin = io::stdin(); let stdin = io::stdin();
let input = &mut String::new(); let input = &mut String::new();
@ -14,13 +13,9 @@ pub fn handle_stdin(counter: Arc<Mutex<server::ServerProcess>>) {
continue; continue;
}; };
{ if let Err(e) = server.send_command(input) {
let mut server = counter.lock().unwrap(); println!("{}", e);
};
if let Err(e) = server.send_command(input) {
println!("{}", e);
};
}
if input.trim() == "stop" { if input.trim() == "stop" {
std::process::exit(0); std::process::exit(0);