mirror of
https://github.com/Portabase/agent.git
synced 2026-09-11 22:11:52 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f149363b12 | |||
| aa3ff39bb3 | |||
| db26910679 | |||
| eac2853b83 | |||
| a4bbb37a4e | |||
| 14068846b9 | |||
| 0a422448b4 | |||
| 14a6c9ab0a | |||
| 86027fd6bd | |||
| 8eab97a3d6 | |||
| 25c95108f0 | |||
| f37e4d2de9 | |||
| a1aa66ff3f | |||
| d4abcbcebc |
@@ -60,7 +60,7 @@ representative at an online or offline event.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
contact@soluce-technologies.com.
|
||||
contact@portabase.io.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ We take security seriously and aim to support the following versions of the proj
|
||||
If you discover a security vulnerability in this project, we appreciate your help in disclosing it responsibly.
|
||||
|
||||
1. **Contact Us**
|
||||
Please report the vulnerability by emailing **[contact@soluce-technologies.com](mailto:contact@soluce-technologies.com)**. Include the following details:
|
||||
Please report the vulnerability by emailing **[contact@portabase.io](mailto:contact@portabase.io)**. Include the following details:
|
||||
- A detailed description of the issue.
|
||||
- Steps to reproduce the vulnerability (if applicable).
|
||||
- Any potential impacts or risks.
|
||||
|
||||
+4
-1
@@ -3,4 +3,7 @@
|
||||
/src/data/
|
||||
|
||||
.DS_Store
|
||||
.env
|
||||
.env
|
||||
|
||||
.claude
|
||||
/docs
|
||||
+1
-1
@@ -27,5 +27,5 @@ keywords:
|
||||
- self-hosted
|
||||
- portabase
|
||||
license: Apache-2.0
|
||||
version: 1.7.0
|
||||
version: 1.11.1
|
||||
date-released: '2026-02-24'
|
||||
|
||||
Generated
+123
-8
@@ -169,6 +169,19 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asynchronous-codec"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@@ -496,7 +509,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"rustls 0.21.12",
|
||||
"rustls 0.23.37",
|
||||
"rustls-native-certs",
|
||||
"rustls-native-certs 0.8.3",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.4",
|
||||
@@ -770,7 +783,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"rand 0.9.2",
|
||||
"rustls 0.23.37",
|
||||
"rustls-native-certs",
|
||||
"rustls-native-certs 0.8.3",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -968,6 +981,12 @@ version = "0.4.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
|
||||
|
||||
[[package]]
|
||||
name = "connection-string"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "510ca239cf13b7f8d16a2b48f263de7b4f8c566f0af58d901031473c76afb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
@@ -1436,6 +1455,26 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -2050,7 +2089,7 @@ dependencies = [
|
||||
"hyper 1.8.1",
|
||||
"hyper-util",
|
||||
"rustls 0.23.37",
|
||||
"rustls-native-certs",
|
||||
"rustls-native-certs 0.8.3",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.4",
|
||||
@@ -2854,6 +2893,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
@@ -3038,7 +3083,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "portabase-agent"
|
||||
version = "1.7.0"
|
||||
version = "1.11.1"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
@@ -3076,6 +3121,7 @@ dependencies = [
|
||||
"testcontainers",
|
||||
"testcontainers-modules",
|
||||
"thiserror 2.0.18",
|
||||
"tiberius",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-postgres",
|
||||
@@ -3191,6 +3237,12 @@ dependencies = [
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty-hex"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
@@ -3677,16 +3729,37 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
|
||||
dependencies = [
|
||||
"openssl-probe 0.1.6",
|
||||
"rustls-pemfile",
|
||||
"schannel",
|
||||
"security-framework 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"openssl-probe 0.2.1",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework 3.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3711,10 +3784,10 @@ dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls 0.23.37",
|
||||
"rustls-native-certs",
|
||||
"rustls-native-certs 0.8.3",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki 0.103.9",
|
||||
"security-framework",
|
||||
"security-framework 3.7.0",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -3832,6 +3905,19 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
@@ -4353,6 +4439,34 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiberius"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1446cb4198848d1562301a3340424b4f425ef79f35ef9ee034769a9dd92c10d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"asynchronous-codec",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"connection-string",
|
||||
"encoding_rs",
|
||||
"enumflags2",
|
||||
"futures-util",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"pretty-hex",
|
||||
"rustls-native-certs 0.6.3",
|
||||
"rustls-pemfile",
|
||||
"thiserror 1.0.69",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
@@ -4668,6 +4782,7 @@ version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "portabase-agent"
|
||||
version = "1.7.0"
|
||||
version = "1.11.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
@@ -35,7 +35,8 @@ rand = "0.9.2"
|
||||
bytes = "1.11.0"
|
||||
async-stream = "0.3.6"
|
||||
uuid = { version = "1.20.0", features = ["v4"] }
|
||||
tokio-util = "0.7.18"
|
||||
tokio-util = { version = "0.7.18", features = ["compat"] }
|
||||
tiberius = { version = "0.12", default-features = false, features = ["rustls", "chrono"] }
|
||||
aws-config = "1.8.13"
|
||||
aws-sdk-s3 = { version = "1.122.0", features = ["behavior-version-latest"] }
|
||||
async-compression = { version = "0.4.37", features = ["tokio", "gzip"] }
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
include .env
|
||||
export $(shell sed 's/=.*//' .env)
|
||||
CLUSTER_SCRIPT=docker/entrypoints/app-dev-entrypoint.sh
|
||||
|
||||
.PHONY: seed-mongo seed-mysql seed-postgres
|
||||
|
||||
up:
|
||||
@bash $(CLUSTER_SCRIPT)
|
||||
|
||||
seed-mongo:
|
||||
@echo "Seeding MongoDB..."
|
||||
bash ./scripts/mongo/seed-mongo.sh
|
||||
|
||||
seed-mongo-auth:
|
||||
@echo "Seeding MongoDB with auth..."
|
||||
bash ./scripts/mongo/seed-mongo.sh auth
|
||||
|
||||
seed-mysql:
|
||||
@echo "Seeding MySQL..."
|
||||
mysql -h 127.0.0.1 -P "$$MYSQL_PORT" -u "$$MYSQL_USER" -p"$$MYSQL_PASSWORD" "$$MYSQL_DB" < ./scripts/mysql/seed-mysql.sql
|
||||
|
||||
seed-mysql-1gb:
|
||||
@echo "Seeding MySQL..."
|
||||
mysql -h 127.0.0.1 -P "$$MYSQL_PORT" -u "$$MYSQL_USER" -p"$$MYSQL_PASSWORD" "$$MYSQL_DB" < ./scripts/mysql/seed-1gb.sql
|
||||
|
||||
|
||||
seed-postgres:
|
||||
@echo "Seeding Postgres..."
|
||||
docker exec -i -e PGPASSWORD=$$PG_PASSWORD $$PG_CONTAINER \
|
||||
psql -U $$PG_USER -d $$PG_DB < ./scripts/postgres/seed.sql
|
||||
|
||||
seed-postgres-1gb:
|
||||
@echo "Seeding Postgres..."
|
||||
docker exec -i -e PGPASSWORD=$$PG_PASSWORD $$PG_CONTAINER \
|
||||
psql -U $$PG_USER -d $$PG_DB < ./scripts/postgres/seed-1gb.sql
|
||||
|
||||
|
||||
SQLITE_SEED_FILE := $(if $(filter big,$(SEED)),./scripts/sqlite/seed-big.sql,./scripts/sqlite/seed.sql)
|
||||
|
||||
seed-sqlite:
|
||||
@echo "Seeding Sqlite..."
|
||||
@echo "Run as root to fix permissions inside the volume"
|
||||
docker exec -u 0 -it db-sqlite sh -c "chmod -R 777 /workspace/data"
|
||||
@echo "Create the database file (if it doesn’t exist)"
|
||||
docker exec -u 0 -it db-sqlite sh -c "touch /workspace/data/app.db"
|
||||
@echo "Seed the database"
|
||||
docker exec -i db-sqlite sh -c "sqlite3 /workspace/data/app.db" < $(SQLITE_SEED_FILE)
|
||||
@echo "Verify"
|
||||
docker exec -it db-sqlite sqlite3 /workspace/data/app.db "SELECT name FROM sqlite_master WHERE type='table';"
|
||||
@echo "Done"
|
||||
|
||||
|
||||
|
||||
seed-all: seed-mongo seed-mysql seed-postgres seed-postgres-1gb
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
[](LICENSE)
|
||||
[](https://hub.docker.com/r/portabase/agent)
|
||||
[](https://github.com/Portabase/agent/pkgs/container/charts%2Fportabase-agent)
|
||||
[](https://github.com/Portabase/portabase)
|
||||
[](https://www.buymeacoffee.com/portabase)
|
||||
|
||||
@@ -20,9 +21,12 @@
|
||||
[](https://www.mysql.com/)
|
||||
[](https://sqlite.org/)
|
||||
[](https://redis.io/)
|
||||
[](https://valkey.io/)
|
||||
[](https://mariadb.org/)
|
||||
[](https://www.mongodb.com/)
|
||||
[](https://valkey.io/)
|
||||
[](https://firebirdsql.org/)
|
||||
|
||||
|
||||
[](https://github.com/Portabase/portabase)
|
||||
[](https://github.com/Portabase/portabase)
|
||||
|
||||
@@ -50,7 +54,7 @@ You have 4 ways to install Portabase Agent:
|
||||
|
||||
- Automated CLI (recommended) - [details](https://portabase.io/docs/agent/setup#cli)
|
||||
- Docker Compose setup - [details](https://portabase.io/docs/agent/setup#docker)
|
||||
- Kubernetes with Helm (soon)
|
||||
- Kubernetes with Helm [details](https://portabase.io/docs/agent/setup#helm)
|
||||
- Development setup - [details](https://portabase.io/docs/agent/setup#development)
|
||||
|
||||
**Ensure Docker is installed on your machine before getting started.**
|
||||
|
||||
+29
-9
@@ -21,7 +21,7 @@
|
||||
"generated_id": "16678124-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 4 - MongoDB Auth",
|
||||
"name": "Test database 3 - MongoDB Auth",
|
||||
"database": "testdbauth",
|
||||
"type": "mongodb",
|
||||
"username": "root",
|
||||
@@ -31,7 +31,7 @@
|
||||
"generated_id": "16678145-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 5 - MongoDB",
|
||||
"name": "Test database 4 - MongoDB",
|
||||
"database": "testdb",
|
||||
"type": "mongodb",
|
||||
"port": 27017,
|
||||
@@ -39,26 +39,26 @@
|
||||
"generated_id": "16678147-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 6 - SQLite DB",
|
||||
"name": "Test database 5 - SQLite DB",
|
||||
"type": "sqlite",
|
||||
"path": "/sqlite-data/workspace/data/app.db",
|
||||
"generated_id": "16678178-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 7 - SQLite DB",
|
||||
"name": "Test database 6 - SQLite DB",
|
||||
"type": "sqlite",
|
||||
"path": "/sqlite-data-2/workspace/data/app.db",
|
||||
"generated_id": "16678179-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 8 - Redis",
|
||||
"name": "Test database 7 - Redis",
|
||||
"type": "redis",
|
||||
"port": 6379,
|
||||
"host": "db-redis",
|
||||
"generated_id": "16678166-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 9 - Redis Auth",
|
||||
"name": "Test database 8 - Redis Auth",
|
||||
"type": "redis",
|
||||
"password": "supersecurepassword",
|
||||
"port": 6379,
|
||||
@@ -67,14 +67,14 @@
|
||||
"generated_id": "16678160-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 10 - Valkey",
|
||||
"name": "Test database 9 - Valkey",
|
||||
"type": "valkey",
|
||||
"port": 6379,
|
||||
"host": "db-valkey",
|
||||
"generated_id": "16678560-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 11 - Valkey Auth",
|
||||
"name": "Test database 10 - Valkey Auth",
|
||||
"type": "valkey",
|
||||
"password": "supersecurepassword",
|
||||
"port": 6379,
|
||||
@@ -83,7 +83,7 @@
|
||||
"generated_id": "16678561-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 12 - Mysql",
|
||||
"name": "Test database 11 - Mysql",
|
||||
"database": "mysqldb",
|
||||
"type": "mysql",
|
||||
"username": "mysqldb",
|
||||
@@ -91,6 +91,26 @@
|
||||
"port": 3306,
|
||||
"host": "db-mysql",
|
||||
"generated_id": "16656124-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 12 - Firebird",
|
||||
"database": "/var/lib/firebird/data/mirror.fdb",
|
||||
"type": "firebird",
|
||||
"username": "alice",
|
||||
"password": "fake_password",
|
||||
"port": 3050,
|
||||
"host": "db-firebird",
|
||||
"generated_id": "16706124-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 13 - MsSQL",
|
||||
"database": "myappdb",
|
||||
"type": "mssql",
|
||||
"username": "sa",
|
||||
"password": "Portabase!Strong1",
|
||||
"port": 1433,
|
||||
"host": "db-mssql",
|
||||
"generated_id": "16706125-ff7e-4c97-8c83-0adeff214682"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+77
-3
@@ -29,11 +29,85 @@ host = "db-mongodb-auth"
|
||||
generated_id = "16678145-ff7e-4c97-8c83-0adeff214681"
|
||||
|
||||
[[databases]]
|
||||
name = "Test database 3 - MongoDB"
|
||||
name = "Test database 4 - MongoDB"
|
||||
database = "testdb"
|
||||
type = "mongodb"
|
||||
username = ""
|
||||
password = ""
|
||||
port = 27017
|
||||
host = "db-mongodb"
|
||||
generated_id = "16678147-ff7e-4c97-8c83-0adeff214681"
|
||||
|
||||
[[databases]]
|
||||
name = "Test database 5 - SQLite DB"
|
||||
type = "sqlite"
|
||||
path = "/sqlite-data/workspace/data/app.db"
|
||||
generated_id = "16678178-ff7e-4c97-8c83-0adeff214681"
|
||||
|
||||
[[databases]]
|
||||
name = "Test database 6 - SQLite DB 2"
|
||||
type = "sqlite"
|
||||
path = "/sqlite-data-2/workspace/data/app.db"
|
||||
generated_id = "16678179-ff7e-4c97-8c83-0adeff214681"
|
||||
|
||||
[[databases]]
|
||||
name = "Test database 7 - Redis"
|
||||
type = "redis"
|
||||
port = 6379
|
||||
host = "db-redis"
|
||||
generated_id = "16678166-ff7e-4c97-8c83-0adeff214681"
|
||||
|
||||
[[databases]]
|
||||
name = "Test database 8 - Redis Auth"
|
||||
type = "redis"
|
||||
password = "supersecurepassword"
|
||||
username = "default"
|
||||
port = 6379
|
||||
host = "db-redis-auth"
|
||||
generated_id = "16678160-ff7e-4c97-8c83-0adeff214681"
|
||||
|
||||
[[databases]]
|
||||
name = "Test database 9 - Valkey"
|
||||
type = "valkey"
|
||||
port = 6379
|
||||
host = "db-valkey"
|
||||
generated_id = "16678560-ff7e-4c97-8c83-0adeff214681"
|
||||
|
||||
[[databases]]
|
||||
name = "Test database 10 - Valkey Auth"
|
||||
type = "valkey"
|
||||
password = "supersecurepassword"
|
||||
username = "default"
|
||||
port = 6379
|
||||
host = "db-valkey-auth"
|
||||
generated_id = "16678561-ff7e-4c97-8c83-0adeff214681"
|
||||
|
||||
[[databases]]
|
||||
name = "Test database 11 - Mysql"
|
||||
database = "mysqldb"
|
||||
type = "mysql"
|
||||
password = "changeme"
|
||||
username = "mysqldb"
|
||||
port = 3306
|
||||
host = "db-mysql"
|
||||
generated_id = "16656124-ff7e-4c97-8c83-0adeff214681"
|
||||
|
||||
[[databases]]
|
||||
name = "Test database 12 - Firebird"
|
||||
database = "/var/lib/firebird/data/mirror.fdb"
|
||||
type = "firebird"
|
||||
password = "fake_password"
|
||||
username = "alice"
|
||||
port = 3050
|
||||
host = "db-firebird"
|
||||
generated_id = "16706124-ff7e-4c97-8c83-0adeff214681"
|
||||
|
||||
[[databases]]
|
||||
name = "Test database 13 - MSSQL"
|
||||
database = "myappdb"
|
||||
type = "mssql"
|
||||
username = "sa"
|
||||
password = "Portabase!Strong1"
|
||||
port = 1433
|
||||
host = "db-mssql"
|
||||
generated_id = "16706125-ff7e-4c97-8c83-0adeff214682"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
name: databases
|
||||
|
||||
services:
|
||||
db-postgres:
|
||||
container_name: db-postgres
|
||||
image: postgres:17-alpine
|
||||
ports:
|
||||
- "5436:5432"
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_DB=devdb
|
||||
- POSTGRES_USER=devuser
|
||||
- POSTGRES_PASSWORD=changeme
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
db-mariadb:
|
||||
container_name: db-mariadb
|
||||
image: mariadb:latest
|
||||
ports:
|
||||
- "3311:3306"
|
||||
environment:
|
||||
- MYSQL_DATABASE=mariadb
|
||||
- MYSQL_USER=mariadb
|
||||
- MYSQL_PASSWORD=changeme
|
||||
- MYSQL_RANDOM_ROOT_PASSWORD=yes
|
||||
volumes:
|
||||
- mariadb-data:/var/lib/mysql
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
db-mysql:
|
||||
container_name: db-mysql
|
||||
image: mysql:9.5
|
||||
ports:
|
||||
- "3312:3306"
|
||||
environment:
|
||||
- MYSQL_DATABASE=mysqldb
|
||||
- MYSQL_USER=mysqldb
|
||||
- MYSQL_PASSWORD=changeme
|
||||
- MYSQL_RANDOM_ROOT_PASSWORD=yes
|
||||
volumes:
|
||||
- mysql-data:/var/lib/mysql
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
|
||||
db-mongodb-auth:
|
||||
container_name: db-mongodb-auth
|
||||
image: mongo:latest
|
||||
ports:
|
||||
- "27082:27017"
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: root
|
||||
MONGO_INITDB_ROOT_PASSWORD: rootpassword
|
||||
MONGO_INITDB_DATABASE: testdbauth
|
||||
command: mongod --auth
|
||||
networks:
|
||||
- portabase
|
||||
volumes:
|
||||
- mongodb-data-auth:/data/db
|
||||
healthcheck:
|
||||
test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
db-mongodb:
|
||||
container_name: db-mongodb
|
||||
image: mongo:latest
|
||||
ports:
|
||||
- "27083:27017"
|
||||
volumes:
|
||||
- mongodb-data:/data/db
|
||||
healthcheck:
|
||||
test: [ "CMD", "mongosh", "--eval", "db.adminCommand('ping')" ]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: testdb
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
sqlite:
|
||||
container_name: db-sqlite
|
||||
image: keinos/sqlite3
|
||||
volumes:
|
||||
- sqlite-data:/workspace/data
|
||||
working_dir: /workspace
|
||||
command: tail -f /dev/null
|
||||
stdin_open: true
|
||||
tty: true
|
||||
|
||||
db-redis:
|
||||
image: redis:latest
|
||||
container_name: db-redis
|
||||
ports:
|
||||
- "6378:6379"
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
command: [ "redis-server", "--appendonly", "yes" ]
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
db-redis-auth:
|
||||
image: redis:latest
|
||||
container_name: db-redis-auth
|
||||
ports:
|
||||
- "6380:6379"
|
||||
volumes:
|
||||
- redis-data-auth:/data
|
||||
environment:
|
||||
- REDIS_PASSWORD=supersecurepassword
|
||||
command: [ "redis-server", "--requirepass", "supersecurepassword", "--appendonly", "yes" ]
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
db-valkey:
|
||||
image: valkey/valkey
|
||||
container_name: db-valkey
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
ports:
|
||||
- '6381:6379'
|
||||
volumes:
|
||||
- valkey-data:/data
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
db-valkey-auth:
|
||||
image: valkey/valkey
|
||||
container_name: db-valkey-auth
|
||||
command: >
|
||||
--requirepass "supersecurepassword"
|
||||
ports:
|
||||
- '6382:6379'
|
||||
volumes:
|
||||
- valkey-data-auth:/data
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
db-firebird:
|
||||
image: firebirdsql/firebird
|
||||
container_name: db-firebird
|
||||
restart: always
|
||||
environment:
|
||||
- FIREBIRD_ROOT_PASSWORD=fake_root_password
|
||||
- FIREBIRD_USER=alice
|
||||
- FIREBIRD_PASSWORD=fake_password
|
||||
- FIREBIRD_DATABASE=mirror.fdb
|
||||
- FIREBIRD_DATABASE_DEFAULT_CHARSET=UTF8
|
||||
volumes:
|
||||
- firebird-data:/var/lib/firebird/data
|
||||
ports:
|
||||
- "3060:3050"
|
||||
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
db-mssql:
|
||||
container_name: db-mssql
|
||||
image: mcr.microsoft.com/azure-sql-edge:latest
|
||||
ports:
|
||||
- "1433:1433"
|
||||
environment:
|
||||
ACCEPT_EULA: "Y"
|
||||
MSSQL_SA_PASSWORD: "Portabase!Strong1"
|
||||
volumes:
|
||||
- mssql-data:/var/opt/mssql
|
||||
networks:
|
||||
- portabase
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "cat /proc/net/tcp6 | grep -q '059901' || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
mariadb-data:
|
||||
mysql-data:
|
||||
mongodb-data:
|
||||
mongodb-data-auth:
|
||||
sqlite-data:
|
||||
redis-data:
|
||||
redis-data-auth:
|
||||
valkey-data:
|
||||
valkey-data-auth:
|
||||
firebird-data:
|
||||
mssql-data:
|
||||
|
||||
networks:
|
||||
portabase:
|
||||
name: portabase_network
|
||||
external: true
|
||||
|
||||
+11
-158
@@ -7,19 +7,20 @@ services:
|
||||
container_name: rust-dev
|
||||
volumes:
|
||||
- .:/app
|
||||
- ./databases.json:/config/config.json
|
||||
# - ./databases.toml:/config/config.toml
|
||||
- cargo-registry:/usr/local/cargo/registry
|
||||
- cargo-git:/usr/local/cargo/git
|
||||
# - /var/run/docker.sock:/var/run/docker.sock
|
||||
# - cargo-target:/app/target
|
||||
# - sqlite-data:/sqlite-data/workspace/data
|
||||
# - ./scripts/sqlite/test-db:/sqlite-data-2/workspace/data
|
||||
- ./databases.json:/config/config.json
|
||||
#- ./databases.toml:/config/config.toml
|
||||
#- /var/run/docker.sock:/var/run/docker.sock
|
||||
#- cargo-target:/app/target
|
||||
- databases_sqlite-data:/sqlite-data/workspace/data
|
||||
- ./scripts/sqlite/test-db:/sqlite-data-2/workspace/data
|
||||
environment:
|
||||
APP_ENV: development
|
||||
LOG: debug
|
||||
TZ: "Europe/Paris"
|
||||
EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiNGI1OTM2MGItNTNkMi00ZTZmLWE1ODctODcyMmQ1NDc1MTNmIiwibWFzdGVyS2V5QjY0IjoiQlhWM1hvbEM2NTZTVjdkTmdjV1BHUWxrKytycExJNmxHRGk3Q1BCNWllbz0ifQ=="
|
||||
EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiNzNlZmJhNjMtNTkzMy00Mzk3LWI0ZmMtMjlmNTViNmI5YzA4IiwibWFzdGVyS2V5QjY0IjoiQlhWM1hvbEM2NTZTVjdkTmdjV1BHUWxrKytycExJNmxHRGk3Q1BCNWllbz0ifQ=="
|
||||
#CHUNK_SIZE_MB: "1"
|
||||
#POOLING: 1
|
||||
#DATABASES_CONFIG_FILE: "config.toml"
|
||||
extra_hosts:
|
||||
@@ -27,160 +28,12 @@ services:
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
# db-postgres:
|
||||
# container_name: db-postgres
|
||||
# image: postgres:17-alpine
|
||||
# ports:
|
||||
# - "5436:5432"
|
||||
# volumes:
|
||||
# - postgres-data:/var/lib/postgresql/data
|
||||
# environment:
|
||||
# - POSTGRES_DB=devdb
|
||||
# - POSTGRES_USER=devuser
|
||||
# - POSTGRES_PASSWORD=changeme
|
||||
# networks:
|
||||
# - portabase
|
||||
|
||||
db-mariadb:
|
||||
container_name: db-mariadb
|
||||
image: mariadb:latest
|
||||
ports:
|
||||
- "3311:3306"
|
||||
environment:
|
||||
- MYSQL_DATABASE=mariadb
|
||||
- MYSQL_USER=mariadb
|
||||
- MYSQL_PASSWORD=changeme
|
||||
- MYSQL_RANDOM_ROOT_PASSWORD=yes
|
||||
volumes:
|
||||
- mariadb-data:/var/lib/mysql
|
||||
networks:
|
||||
- portabase
|
||||
|
||||
db-mysql:
|
||||
container_name: db-mysql
|
||||
image: mysql:9.5
|
||||
ports:
|
||||
- "3312:3306"
|
||||
environment:
|
||||
- MYSQL_DATABASE=mysqldb
|
||||
- MYSQL_USER=mysqldb
|
||||
- MYSQL_PASSWORD=changeme
|
||||
- MYSQL_RANDOM_ROOT_PASSWORD=yes
|
||||
volumes:
|
||||
- mysql-data:/var/lib/mysql
|
||||
networks:
|
||||
- portabase
|
||||
#
|
||||
#
|
||||
# db-mongodb-auth:
|
||||
# container_name: db-mongodb-auth
|
||||
# image: mongo:latest
|
||||
# ports:
|
||||
# - "27082:27017"
|
||||
# environment:
|
||||
# MONGO_INITDB_ROOT_USERNAME: root
|
||||
# MONGO_INITDB_ROOT_PASSWORD: rootpassword
|
||||
# MONGO_INITDB_DATABASE: testdbauth
|
||||
# command: mongod --auth
|
||||
# networks:
|
||||
# - portabase
|
||||
# volumes:
|
||||
# - mongodb-data-auth:/data/db
|
||||
# healthcheck:
|
||||
# test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
|
||||
# interval: 5s
|
||||
# timeout: 5s
|
||||
# retries: 10
|
||||
#
|
||||
# db-mongodb:
|
||||
# container_name: db-mongodb
|
||||
# image: mongo:latest
|
||||
# ports:
|
||||
# - "27083:27017"
|
||||
# volumes:
|
||||
# - mongodb-data:/data/db
|
||||
# healthcheck:
|
||||
# test: [ "CMD", "mongosh", "--eval", "db.adminCommand('ping')" ]
|
||||
# interval: 5s
|
||||
# timeout: 5s
|
||||
# retries: 10
|
||||
# environment:
|
||||
# MONGO_INITDB_DATABASE: testdb
|
||||
# networks:
|
||||
# - portabase
|
||||
|
||||
# sqlite:
|
||||
# container_name: db-sqlite
|
||||
# image: keinos/sqlite3
|
||||
# volumes:
|
||||
# - sqlite-data:/workspace/data
|
||||
# working_dir: /workspace
|
||||
# command: tail -f /dev/null
|
||||
# stdin_open: true
|
||||
# tty: true
|
||||
|
||||
# db-redis:
|
||||
# image: redis:latest
|
||||
# container_name: db-redis
|
||||
# ports:
|
||||
# - "6379:6379"
|
||||
# volumes:
|
||||
# - redis-data:/data
|
||||
# command: [ "redis-server", "--appendonly", "yes" ]
|
||||
# networks:
|
||||
# - portabase
|
||||
#
|
||||
# db-redis-auth:
|
||||
# image: redis:latest
|
||||
# container_name: db-redis-auth
|
||||
# ports:
|
||||
# - "6380:6379"
|
||||
# volumes:
|
||||
# - redis-data-auth:/data
|
||||
# environment:
|
||||
# - REDIS_PASSWORD=supersecurepassword
|
||||
# command: [ "redis-server", "--requirepass", "supersecurepassword", "--appendonly", "yes" ]
|
||||
# networks:
|
||||
# - portabase
|
||||
#
|
||||
# db-valkey:
|
||||
# image: valkey/valkey
|
||||
# container_name: db-valkey
|
||||
# environment:
|
||||
# - ALLOW_EMPTY_PASSWORD=yes
|
||||
# ports:
|
||||
# - '6381:6379'
|
||||
# volumes:
|
||||
# - valkey-data:/data
|
||||
# networks:
|
||||
# - portabase
|
||||
#
|
||||
# db-valkey-auth:
|
||||
# image: valkey/valkey
|
||||
# container_name: db-valkey-auth
|
||||
# command: >
|
||||
# --requirepass "supersecurepassword"
|
||||
# ports:
|
||||
# - '6382:6379'
|
||||
# volumes:
|
||||
# - valkey-data-auth:/data
|
||||
# networks:
|
||||
# - portabase
|
||||
|
||||
volumes:
|
||||
cargo-registry:
|
||||
cargo-git:
|
||||
# cargo-target:
|
||||
# postgres-data:
|
||||
mariadb-data:
|
||||
mysql-data:
|
||||
# mongodb-data:
|
||||
# mongodb-data-auth:
|
||||
# sqlite-data:
|
||||
# redis-data:
|
||||
# redis-data-auth:
|
||||
# valkey-data:
|
||||
# valkey-data-auth:
|
||||
#cargo-target:
|
||||
databases_sqlite-data:
|
||||
external: true
|
||||
|
||||
networks:
|
||||
portabase:
|
||||
|
||||
+24
-1
@@ -18,9 +18,24 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
sqlite3 \
|
||||
redis-tools \
|
||||
valkey \
|
||||
firebird3.0-utils \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV DOTNET_ROOT=/usr/local/dotnet
|
||||
RUN curl -sSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh \
|
||||
&& chmod +x /tmp/dotnet-install.sh \
|
||||
&& /tmp/dotnet-install.sh --channel 8.0 --install-dir /usr/local/dotnet \
|
||||
&& rm /tmp/dotnet-install.sh \
|
||||
&& /usr/local/dotnet/dotnet tool install --global microsoft.sqlpackage
|
||||
|
||||
ENV PATH="$PATH:/usr/local/dotnet:/root/.dotnet/tools"
|
||||
|
||||
RUN ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') \
|
||||
&& curl -sSL "https://github.com/microsoft/go-sqlcmd/releases/latest/download/sqlcmd-linux-${ARCH}.tar.bz2" \
|
||||
| tar -xjf - -C /usr/local/bin sqlcmd \
|
||||
&& chmod +x /usr/local/bin/sqlcmd
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
# =========================
|
||||
@@ -96,12 +111,19 @@ RUN apt-get update && apt-get install -y \
|
||||
libreadline8 \
|
||||
libncurses6 \
|
||||
zlib1g \
|
||||
curl \
|
||||
mariadb-client \
|
||||
sqlite3 \
|
||||
redis-tools \
|
||||
valkey \
|
||||
firebird3.0-utils \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV DOTNET_ROOT=/usr/local/dotnet
|
||||
RUN curl -sSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh \
|
||||
&& chmod +x /tmp/dotnet-install.sh \
|
||||
&& /tmp/dotnet-install.sh --channel 8.0 --runtime dotnet --install-dir /usr/local/dotnet \
|
||||
&& rm /tmp/dotnet-install.sh
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -112,8 +134,9 @@ RUN chmod +x /entrypoint.sh
|
||||
|
||||
COPY --from=base /usr/lib/postgresql/ /usr/lib/postgresql/
|
||||
COPY --from=base /usr/local/mongodb/bin/ /usr/local/mongodb/bin/
|
||||
COPY --from=base /root/.dotnet/tools/ /root/.dotnet/tools/
|
||||
|
||||
|
||||
ENV PATH="$PATH:/usr/local/dotnet:/root/.dotnet/tools"
|
||||
ENV APP_ENV=production
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
|
||||
@@ -34,6 +34,11 @@ check_network() {
|
||||
check_docker
|
||||
check_network
|
||||
|
||||
echo "Starting docker-compose..."
|
||||
docker compose -f ./docker-compose.yml up
|
||||
echo "Docker-compose started successfully."
|
||||
echo "Stopping old database containers..."
|
||||
docker compose -f ./docker-compose.databases.yml down
|
||||
|
||||
echo "Starting database containers..."
|
||||
docker compose -f ./docker-compose.databases.yml up -d
|
||||
|
||||
echo "Starting main services..."
|
||||
docker compose -f ./docker-compose.yml up
|
||||
@@ -0,0 +1,61 @@
|
||||
set dotenv-load := true
|
||||
set shell := ["bash", "-cu"]
|
||||
|
||||
CLUSTER_SCRIPT := "docker/entrypoints/app-dev-entrypoint.sh"
|
||||
|
||||
up:
|
||||
bash {{CLUSTER_SCRIPT}}
|
||||
|
||||
seed-mongo:
|
||||
echo "Seeding MongoDB..."
|
||||
bash ./scripts/mongo/seed-mongo.sh
|
||||
|
||||
seed-mongo-auth:
|
||||
echo "Seeding MongoDB with auth..."
|
||||
bash ./scripts/mongo/seed-mongo.sh auth
|
||||
|
||||
seed-mysql:
|
||||
echo "Seeding MySQL..."
|
||||
mysql -h 127.0.0.1 -P "$MYSQL_PORT" -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DB" < ./scripts/mysql/seed-mysql.sql
|
||||
|
||||
seed-mysql-1gb:
|
||||
echo "Seeding MySQL (1GB)..."
|
||||
mysql -h 127.0.0.1 -P "$MYSQL_PORT" -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DB" < ./scripts/mysql/seed-1gb.sql
|
||||
|
||||
seed-postgres:
|
||||
echo "Seeding Postgres..."
|
||||
docker exec -i -e PGPASSWORD="$PG_PASSWORD" "$PG_CONTAINER" \
|
||||
psql -U "$PG_USER" -d "$PG_DB" < ./scripts/postgres/seed.sql
|
||||
|
||||
seed-postgres-1gb:
|
||||
echo "Seeding Postgres (1GB)..."
|
||||
docker exec -i -e PGPASSWORD="$PG_PASSWORD" "$PG_CONTAINER" \
|
||||
psql -U "$PG_USER" -d "$PG_DB" < ./scripts/postgres/seed-1gb.sql
|
||||
|
||||
seed-sqlite:
|
||||
echo "Seeding SQLite..."
|
||||
bash ./scripts/sqlite/seed.sh
|
||||
echo "Done"
|
||||
|
||||
seed-firebird:
|
||||
echo "Seeding Firebird..."
|
||||
docker exec -i db-firebird isql -user alice -password fake_password /var/lib/firebird/data/mirror.fdb < ./scripts/firebird/seed.sql
|
||||
|
||||
echo "Verifying Firebird tables..."
|
||||
echo "SELECT RDB\$RELATION_NAME FROM RDB\$RELATIONS WHERE RDB\$SYSTEM_FLAG = 0 AND RDB\$VIEW_BLR IS NULL;" \
|
||||
| docker exec -i db-firebird isql -user alice -password fake_password /var/lib/firebird/data/mirror.fdb
|
||||
|
||||
seed-mssql:
|
||||
echo "Seeding MSSQL..."
|
||||
docker exec -i rust-dev sqlcmd -S "db-mssql,1433" -U sa -P "$MSSQL_SA_PASSWORD" -N disable -i /app/scripts/mssql/seed.sql
|
||||
echo "Done"
|
||||
|
||||
seed-all:
|
||||
just seed-mongo
|
||||
just seed-mysql
|
||||
just seed-postgres
|
||||
just seed-postgres-1gb
|
||||
just seed-sqlite
|
||||
just seed-mongo
|
||||
just seed-firebird
|
||||
just seed-mssql
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE users (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
name VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice');
|
||||
INSERT INTO users (email, name) VALUES ('bob@example.com', 'Bob');
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,20 @@
|
||||
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = N'myappdb')
|
||||
CREATE DATABASE [myappdb];
|
||||
GO
|
||||
|
||||
USE [myappdb];
|
||||
GO
|
||||
|
||||
IF OBJECT_ID('users', 'U') IS NULL
|
||||
CREATE TABLE users (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
email NVARCHAR(255) NOT NULL UNIQUE,
|
||||
name NVARCHAR(255),
|
||||
created_at DATETIME DEFAULT GETDATE()
|
||||
);
|
||||
GO
|
||||
|
||||
INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice');
|
||||
INSERT INTO users (email, name) VALUES ('bob@example.com', 'Bob');
|
||||
INSERT INTO users (email, name) VALUES ('charlie@example.com', 'Charlie');
|
||||
GO
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SEED_VALUE="${SEED:-}"
|
||||
|
||||
if [ "$SEED_VALUE" = "big" ]; then
|
||||
SQLITE_SEED_FILE="./scripts/sqlite/seed-big.sql"
|
||||
else
|
||||
SQLITE_SEED_FILE="./scripts/sqlite/seed.sql"
|
||||
fi
|
||||
|
||||
docker exec -u 0 db-sqlite sh -c "chmod -R 777 /workspace/data"
|
||||
docker exec -u 0 db-sqlite sh -c "touch /workspace/data/app.db"
|
||||
docker exec -i db-sqlite sh -c "sqlite3 /workspace/data/app.db" < "$SQLITE_SEED_FILE"
|
||||
@@ -9,7 +9,9 @@ use crate::services::config::{DatabaseConfig, DbType};
|
||||
use anyhow::Result;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use crate::domain::firebird::database::FirebirdDatabase;
|
||||
use crate::domain::mariadb::database::MariaDBDatabase;
|
||||
use crate::domain::mssql::database::MssqlDatabase;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Database: Send + Sync {
|
||||
@@ -34,6 +36,8 @@ impl DatabaseFactory {
|
||||
DbType::Sqlite => Arc::new(SqliteDatabase::new(cfg)),
|
||||
DbType::Redis => Arc::new(RedisDatabase::new(cfg)),
|
||||
DbType::Valkey => Arc::new(ValkeyDatabase::new(cfg)),
|
||||
DbType::Firebird => Arc::new(FirebirdDatabase::new(cfg)),
|
||||
DbType::Mssql => Arc::new(MssqlDatabase::new(cfg)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +53,8 @@ impl DatabaseFactory {
|
||||
DbType::Sqlite => Arc::new(SqliteDatabase::new(cfg)),
|
||||
DbType::Redis => Arc::new(RedisDatabase::new(cfg)),
|
||||
DbType::Valkey => Arc::new(ValkeyDatabase::new(cfg)),
|
||||
DbType::Firebird => Arc::new(FirebirdDatabase::new(cfg)),
|
||||
DbType::Mssql => Arc::new(MssqlDatabase::new(cfg)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub async fn run(
|
||||
cfg: DatabaseConfig,
|
||||
backup_dir: PathBuf,
|
||||
file_extension: &'static str,
|
||||
) -> Result<PathBuf> {
|
||||
tokio::task::spawn_blocking(move || -> Result<PathBuf> {
|
||||
debug!("Starting backup for database {}", cfg.name);
|
||||
|
||||
let file_path = backup_dir.join(format!("{}{}", cfg.generated_id, file_extension));
|
||||
|
||||
let db_path = format!(
|
||||
"{}/{}:{}",
|
||||
cfg.host,
|
||||
cfg.port,
|
||||
cfg.database
|
||||
);
|
||||
|
||||
info!("Firebird database target: {}", db_path);
|
||||
info!("Backup file: {}", file_path.display());
|
||||
|
||||
let output = Command::new("gbak")
|
||||
.arg("-b")
|
||||
.arg("-v")
|
||||
.arg("-user").arg(&cfg.username)
|
||||
.arg("-password").arg(&cfg.password)
|
||||
.arg(db_path)
|
||||
.arg(&file_path)
|
||||
.output()
|
||||
.with_context(|| format!("Failed to run gbak for {}", cfg.name))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
error!("Firebird backup failed: {}", stderr);
|
||||
anyhow::bail!("Firebird backup failed for {}: {}", cfg.name, stderr);
|
||||
}
|
||||
|
||||
info!("Firebird backup completed: {}", file_path.display());
|
||||
|
||||
Ok(file_path)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
use super::{backup, ping, restore};
|
||||
use crate::domain::factory::Database;
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use crate::utils::locks::{DbOpLock, FileLock};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct FirebirdDatabase {
|
||||
cfg: DatabaseConfig,
|
||||
}
|
||||
|
||||
impl FirebirdDatabase {
|
||||
pub fn new(cfg: DatabaseConfig) -> Self {
|
||||
Self { cfg }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Database for FirebirdDatabase {
|
||||
fn file_extension(&self) -> &'static str {
|
||||
".fbk"
|
||||
}
|
||||
|
||||
async fn ping(&self) -> Result<bool> {
|
||||
ping::run(self.cfg.clone()).await
|
||||
}
|
||||
|
||||
async fn backup(&self, dir: &Path) -> Result<PathBuf> {
|
||||
FileLock::acquire(&self.cfg.generated_id, DbOpLock::Backup.as_str()).await?;
|
||||
let res = backup::run(
|
||||
self.cfg.clone(),
|
||||
dir.to_path_buf(),
|
||||
self.file_extension(),
|
||||
)
|
||||
.await;
|
||||
FileLock::release(&self.cfg.generated_id).await?;
|
||||
res
|
||||
}
|
||||
|
||||
async fn restore(&self, file: &Path) -> Result<()> {
|
||||
FileLock::acquire(&self.cfg.generated_id, DbOpLock::Restore.as_str()).await?;
|
||||
let res = restore::run(self.cfg.clone(), file.to_path_buf()).await;
|
||||
FileLock::release(&self.cfg.generated_id).await?;
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod database;
|
||||
mod ping;
|
||||
mod backup;
|
||||
mod restore;
|
||||
@@ -0,0 +1,57 @@
|
||||
use std::process::Stdio;
|
||||
use tracing::{error, info};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use tokio::process::Command;
|
||||
use tokio::time::{Duration, timeout};
|
||||
|
||||
pub async fn run(cfg: DatabaseConfig) -> anyhow::Result<bool> {
|
||||
let db_path = format!("{}/{}:{}", cfg.host, cfg.port, cfg.database);
|
||||
|
||||
info!("Running Ping database from {}", db_path);
|
||||
|
||||
let mut child = Command::new("isql-fb")
|
||||
.arg("-q")
|
||||
.arg("-user")
|
||||
.arg(&cfg.username)
|
||||
.arg("-password")
|
||||
.arg(&cfg.password)
|
||||
.arg(&db_path)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let query = b"SELECT 1 FROM RDB$DATABASE;\nQUIT;\n";
|
||||
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
stdin.write_all(query).await?;
|
||||
}
|
||||
|
||||
let output = match timeout(Duration::from_secs(5), child.wait_with_output()).await {
|
||||
Ok(res) => res?,
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
|
||||
if !output.status.success() {
|
||||
error!("Error output for firebird: {:?}", output);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
if stderr.to_lowercase().contains("error") {
|
||||
error!("Error output for firebird: {:?}", output);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if stdout.contains("1") {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
info!("stdout {}", stdout);
|
||||
error!("stderr {}", stderr);
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub async fn run(cfg: DatabaseConfig, restore_file: PathBuf) -> Result<()> {
|
||||
tokio::task::spawn_blocking(move || -> Result<()> {
|
||||
debug!("Starting Firebird restore for database {}", cfg.name);
|
||||
|
||||
let db_path = format!("{}/{}:{}", cfg.host, cfg.port, cfg.database);
|
||||
|
||||
info!("Restore source: {}", restore_file.display());
|
||||
info!("Restore target: {}", db_path);
|
||||
|
||||
let output = Command::new("gbak")
|
||||
.arg("-c")
|
||||
.arg("-v")
|
||||
.arg("-replace_database")
|
||||
.arg("-user")
|
||||
.arg(&cfg.username)
|
||||
.arg("-password")
|
||||
.arg(&cfg.password)
|
||||
.arg(&restore_file)
|
||||
.arg(&db_path)
|
||||
.output()
|
||||
.with_context(|| format!("Failed to run gbak restore for {}", cfg.name))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
error!("Firebird restore failed for {}: {}", cfg.name, stderr);
|
||||
anyhow::bail!("Firebird restore failed for {}: {}", cfg.name, stderr);
|
||||
}
|
||||
|
||||
info!("Firebird restore completed for {}", cfg.name);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await?
|
||||
}
|
||||
@@ -33,6 +33,7 @@ pub async fn run(
|
||||
let mariadb_dump = select_mariadb_path(&version).join("mariadb-dump");
|
||||
info!("Mariadb dump found: {}", mariadb_dump.display());
|
||||
|
||||
|
||||
let output = Command::new("mariadb-dump")
|
||||
.arg("--host").arg(&cfg.host)
|
||||
.arg("--port").arg(cfg.port.to_string())
|
||||
@@ -43,18 +44,18 @@ pub async fn run(
|
||||
.arg("--single-transaction")
|
||||
.arg("--quick")
|
||||
.arg("--skip-lock-tables")
|
||||
.arg("--add-drop-database")
|
||||
.arg("--databases").arg(&cfg.database)
|
||||
.arg("--no-create-db")
|
||||
.arg("--skip-add-drop-table")
|
||||
.arg("--compress")
|
||||
.arg("--max-allowed-packet=512M")
|
||||
.arg("--net-buffer-length=16K")
|
||||
.arg("--default-character-set=utf8mb4")
|
||||
.arg(&cfg.database)
|
||||
.arg("-r").arg(&file_path)
|
||||
.envs(env)
|
||||
.output()
|
||||
.with_context(|| format!("Failed to run mariadb-dump for {}", cfg.name))?;
|
||||
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
anyhow::bail!("Mariadb backup failed for {}: {}", cfg.name, stderr);
|
||||
|
||||
@@ -15,9 +15,9 @@ pub async fn run(cfg: DatabaseConfig, restore_file: PathBuf) -> Result<()> {
|
||||
.with_context(|| format!("Failed to open restore file {}", restore_file.display()))?;
|
||||
file.read_to_string(&mut sql_content)
|
||||
.with_context(|| format!("Failed to read restore file {}", restore_file.display()))?;
|
||||
|
||||
|
||||
let drop_create_cmd = format!(
|
||||
"DROP DATABASE IF EXISTS {0}; CREATE DATABASE {0};",
|
||||
"DROP DATABASE IF EXISTS `{0}`; CREATE DATABASE `{0}`;",
|
||||
cfg.database
|
||||
);
|
||||
|
||||
@@ -47,6 +47,7 @@ pub async fn run(cfg: DatabaseConfig, restore_file: PathBuf) -> Result<()> {
|
||||
.arg(cfg.port.to_string())
|
||||
.arg("--user")
|
||||
.arg(&cfg.username)
|
||||
.arg("--database")
|
||||
.arg(&cfg.database)
|
||||
.env("MYSQL_PWD", &cfg.password)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
@@ -77,4 +78,4 @@ pub async fn run(cfg: DatabaseConfig, restore_file: PathBuf) -> Result<()> {
|
||||
handle.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,3 +6,5 @@ mod redis;
|
||||
mod sqlite;
|
||||
mod valkey;
|
||||
mod mariadb;
|
||||
mod firebird;
|
||||
pub mod mssql;
|
||||
|
||||
@@ -15,7 +15,6 @@ pub async fn run(
|
||||
|
||||
let file_path = backup_dir.join(format!("{}{}", cfg.generated_id, file_extension));
|
||||
let mongodump = select_mongo_path().join("mongodump");
|
||||
info!("{:?}", mongodump);
|
||||
let uri = get_mongo_uri(cfg.clone())?;
|
||||
|
||||
let output = Command::new(mongodump)
|
||||
|
||||
@@ -28,3 +28,18 @@ pub fn get_mongo_uri(cfg: DatabaseConfig) -> Result<String> {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn extract_db_name(dry_output: &str) -> Option<String> {
|
||||
let mut dbs = std::collections::HashSet::new();
|
||||
for line in dry_output.lines() {
|
||||
if let Some(pos) = line.find("archive prelude ") {
|
||||
let rest = &line[pos + "archive prelude ".len()..];
|
||||
if let Some(dot) = rest.find('.') {
|
||||
let db = &rest[..dot];
|
||||
dbs.insert(db.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
dbs.into_iter().next()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::domain::mongodb::connection::{get_mongo_uri, select_mongo_path};
|
||||
use crate::domain::mongodb::connection::{extract_db_name, get_mongo_uri, select_mongo_path};
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
@@ -12,18 +12,41 @@ pub async fn run(cfg: DatabaseConfig, restore_file: PathBuf) -> Result<()> {
|
||||
let mongorestore = select_mongo_path().join("mongorestore");
|
||||
let uri = get_mongo_uri(cfg.clone())?;
|
||||
|
||||
let output = Command::new(mongorestore)
|
||||
let dry_run = Command::new(&mongorestore)
|
||||
.arg(format!(
|
||||
"--uri={}",
|
||||
format!(
|
||||
"mongodb://{}:{}@{}:{}/?authSource=admin",
|
||||
cfg.username, cfg.password, cfg.host, cfg.port
|
||||
)
|
||||
))
|
||||
.arg(format!("--archive={}", restore_file.display()))
|
||||
.arg("--gzip")
|
||||
.arg("--dryRun")
|
||||
.arg("--verbose")
|
||||
.output()?;
|
||||
|
||||
let dry_output = String::from_utf8_lossy(&dry_run.stderr);
|
||||
let source_db = extract_db_name(&dry_output)
|
||||
.context("Could not detect source database name from archive")?;
|
||||
|
||||
info!("Detected source database in archive: {}", source_db);
|
||||
|
||||
let output = Command::new(&mongorestore)
|
||||
.arg(format!("--uri={}", uri))
|
||||
.arg(format!("--archive={}", restore_file.display()))
|
||||
.arg("--gzip")
|
||||
.arg("--drop")
|
||||
.arg(format!("--nsInclude={}.*", source_db))
|
||||
.arg(format!("--nsFrom={}.*", source_db))
|
||||
.arg(format!("--nsTo={}.*", cfg.database))
|
||||
.output()
|
||||
.with_context(|| format!("Failed to run mongorestore for {}", cfg.name))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
error!("MongoDB restore failed for {}: {}", cfg.name, stderr);
|
||||
anyhow::bail!("MongoDB restore failed for : {}", cfg.name);
|
||||
anyhow::bail!("MongoDB restore failed for: {}", cfg.name);
|
||||
}
|
||||
|
||||
info!("MongoDB restore completed for {}", cfg.name);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub async fn run(
|
||||
cfg: DatabaseConfig,
|
||||
backup_dir: PathBuf,
|
||||
file_extension: &'static str,
|
||||
) -> Result<PathBuf> {
|
||||
tokio::task::spawn_blocking(move || -> Result<PathBuf> {
|
||||
debug!("Starting MSSQL backup for database {}", cfg.name);
|
||||
|
||||
let file_path = backup_dir.join(format!("{}{}", cfg.generated_id, file_extension));
|
||||
let connection_string = format!(
|
||||
"Server=tcp:{},{};Database={};User Id={};Password={};TrustServerCertificate=True;Encrypt=False",
|
||||
cfg.host, cfg.port, cfg.database, cfg.username, cfg.password
|
||||
);
|
||||
|
||||
info!(
|
||||
"MSSQL backup: {}:{}/{} → {}",
|
||||
cfg.host,
|
||||
cfg.port,
|
||||
cfg.database,
|
||||
file_path.display()
|
||||
);
|
||||
|
||||
let output = Command::new("sqlpackage")
|
||||
.arg("/a:Export")
|
||||
.arg(format!("/scs:{}", connection_string))
|
||||
.arg(format!("/tf:{}", file_path.display()))
|
||||
.output()
|
||||
.with_context(|| format!("Failed to run sqlpackage for {}", cfg.name))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
error!("MSSQL backup failed — stderr: {} stdout: {}", stderr, stdout);
|
||||
anyhow::bail!("MSSQL backup failed for {}: {}", cfg.name, stderr);
|
||||
}
|
||||
|
||||
info!("MSSQL backup completed: {}", file_path.display());
|
||||
Ok(file_path)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::Result;
|
||||
use tiberius::{AuthMethod, Client, Config};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::compat::{Compat, TokioAsyncWriteCompatExt};
|
||||
|
||||
pub async fn build_client(cfg: &DatabaseConfig) -> Result<Client<Compat<TcpStream>>> {
|
||||
let mut config = Config::new();
|
||||
config.host(&cfg.host);
|
||||
config.port(cfg.port);
|
||||
config.authentication(AuthMethod::sql_server(&cfg.username, &cfg.password));
|
||||
config.trust_cert();
|
||||
|
||||
let tcp = TcpStream::connect(config.get_addr()).await?;
|
||||
tcp.set_nodelay(true)?;
|
||||
let client = Client::connect(config, tcp.compat_write()).await?;
|
||||
Ok(client)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use super::{backup, ping, restore};
|
||||
use crate::domain::factory::Database;
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use crate::utils::locks::{DbOpLock, FileLock};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct MssqlDatabase {
|
||||
cfg: DatabaseConfig,
|
||||
}
|
||||
|
||||
impl MssqlDatabase {
|
||||
pub fn new(cfg: DatabaseConfig) -> Self {
|
||||
Self { cfg }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Database for MssqlDatabase {
|
||||
fn file_extension(&self) -> &'static str {
|
||||
".bacpac"
|
||||
}
|
||||
|
||||
async fn ping(&self) -> Result<bool> {
|
||||
ping::run(self.cfg.clone()).await
|
||||
}
|
||||
|
||||
async fn backup(&self, dir: &Path) -> Result<PathBuf> {
|
||||
FileLock::acquire(&self.cfg.generated_id, DbOpLock::Backup.as_str()).await?;
|
||||
let res = backup::run(
|
||||
self.cfg.clone(),
|
||||
dir.to_path_buf(),
|
||||
self.file_extension(),
|
||||
)
|
||||
.await;
|
||||
FileLock::release(&self.cfg.generated_id).await?;
|
||||
res
|
||||
}
|
||||
|
||||
async fn restore(&self, file: &Path) -> Result<()> {
|
||||
FileLock::acquire(&self.cfg.generated_id, DbOpLock::Restore.as_str()).await?;
|
||||
let res = restore::run(self.cfg.clone(), file.to_path_buf()).await;
|
||||
FileLock::release(&self.cfg.generated_id).await?;
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod database;
|
||||
mod connection;
|
||||
mod ping;
|
||||
mod backup;
|
||||
mod restore;
|
||||
@@ -0,0 +1,24 @@
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::Result;
|
||||
use tracing::{error, info};
|
||||
|
||||
pub async fn run(cfg: DatabaseConfig) -> Result<bool> {
|
||||
info!("Running ping for MSSQL database {}", cfg.name);
|
||||
|
||||
match super::connection::build_client(&cfg).await {
|
||||
Ok(mut client) => match client.simple_query("SELECT 1").await {
|
||||
Ok(_) => {
|
||||
info!("MSSQL ping succeeded for {}", cfg.name);
|
||||
Ok(true)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("MSSQL ping query failed for {}: {:?}", cfg.name, e);
|
||||
Ok(false)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("MSSQL connection failed for {}: {:?}", cfg.name, e);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub async fn run(cfg: DatabaseConfig, restore_file: PathBuf) -> Result<()> {
|
||||
tokio::task::spawn_blocking(move || -> Result<()> {
|
||||
debug!("Starting MSSQL restore for database {}", cfg.name);
|
||||
|
||||
let connection_string = format!(
|
||||
"Server=tcp:{},{};Database={};User Id={};Password={};TrustServerCertificate=True;Encrypt=False",
|
||||
cfg.host, cfg.port, cfg.database, cfg.username, cfg.password
|
||||
);
|
||||
|
||||
info!(
|
||||
"MSSQL restore: {} → {}:{}/{}",
|
||||
restore_file.display(),
|
||||
cfg.host,
|
||||
cfg.port,
|
||||
cfg.database
|
||||
);
|
||||
|
||||
let output = Command::new("sqlpackage")
|
||||
.arg("/a:Import")
|
||||
.arg(format!("/tcs:{}", connection_string))
|
||||
.arg(format!("/sf:{}", restore_file.display()))
|
||||
.output()
|
||||
.with_context(|| format!("Failed to run sqlpackage restore for {}", cfg.name))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
error!(
|
||||
"MSSQL restore failed for {} — stderr: {} stdout: {}",
|
||||
cfg.name, stderr, stdout
|
||||
);
|
||||
anyhow::bail!("MSSQL restore failed for {}: {}", cfg.name, stderr);
|
||||
}
|
||||
|
||||
info!("MSSQL restore completed for {}", cfg.name);
|
||||
Ok(())
|
||||
})
|
||||
.await?
|
||||
}
|
||||
@@ -30,25 +30,23 @@ pub async fn run(
|
||||
|
||||
let file_path = backup_dir.join(format!("{}{}", cfg.generated_id, file_extension));
|
||||
|
||||
// let mysql_dump = select_mysql_path(&version).join("mysqldump");
|
||||
// info!("MySQL dump found: {}", mysql_dump.display());
|
||||
|
||||
let output = Command::new("mysqldump")
|
||||
.arg("--host")
|
||||
.arg(cfg.host)
|
||||
.arg(&cfg.host)
|
||||
.arg("--port")
|
||||
.arg(cfg.port.to_string())
|
||||
.arg("--user")
|
||||
.arg(cfg.username)
|
||||
.arg(&cfg.username)
|
||||
.arg("--routines")
|
||||
.arg("--events")
|
||||
.arg("--triggers")
|
||||
.arg("--verbose")
|
||||
.arg("--single-transaction")
|
||||
.arg("--quick")
|
||||
.arg("--add-drop-database")
|
||||
.arg("--databases")
|
||||
.arg(cfg.database)
|
||||
.arg("--skip-lock-tables")
|
||||
.arg("--skip-add-drop-table")
|
||||
.arg("--no-create-db") // IMPORTANT
|
||||
.arg("--default-character-set=utf8mb4")
|
||||
.arg(&cfg.database) // IMPORTANT: NOT --databases
|
||||
.arg("-r")
|
||||
.arg(&file_path)
|
||||
.envs(env)
|
||||
|
||||
@@ -17,7 +17,7 @@ pub async fn run(cfg: DatabaseConfig, restore_file: PathBuf) -> Result<()> {
|
||||
.with_context(|| format!("Failed to read restore file {}", restore_file.display()))?;
|
||||
|
||||
let drop_create_cmd = format!(
|
||||
"DROP DATABASE IF EXISTS {0}; CREATE DATABASE {0};",
|
||||
"DROP DATABASE IF EXISTS `{0}`; CREATE DATABASE `{0}`;",
|
||||
cfg.database
|
||||
);
|
||||
|
||||
@@ -47,6 +47,7 @@ pub async fn run(cfg: DatabaseConfig, restore_file: PathBuf) -> Result<()> {
|
||||
.arg(cfg.port.to_string())
|
||||
.arg("--user")
|
||||
.arg(&cfg.username)
|
||||
.arg("--database")
|
||||
.arg(&cfg.database)
|
||||
.env("MYSQL_PWD", &cfg.password)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
|
||||
@@ -37,8 +37,8 @@ pub async fn run(
|
||||
info!("Connections terminated for database {}", cfg.name);
|
||||
|
||||
let url = format!(
|
||||
"postgresql://{}:{}@{}:{}/postgres",
|
||||
cfg.username, cfg.password, cfg.host, cfg.port
|
||||
"postgresql://{}:{}@{}:{}/{}",
|
||||
cfg.username, cfg.password, cfg.host, cfg.port, cfg.database
|
||||
);
|
||||
|
||||
debug!("Restore URL: {}", url);
|
||||
@@ -51,7 +51,7 @@ pub async fn run(
|
||||
.arg("--no-privileges")
|
||||
.arg("--clean")
|
||||
.arg("--if-exists")
|
||||
.arg("--create")
|
||||
// .arg("--create")
|
||||
.arg("--dbname")
|
||||
.arg(&url)
|
||||
.arg("-v")
|
||||
@@ -139,7 +139,7 @@ pub async fn run(
|
||||
.arg("--no-privileges")
|
||||
.arg("--clean")
|
||||
.arg("--if-exists")
|
||||
.arg("--create")
|
||||
// .arg("--create")
|
||||
.arg("--dbname")
|
||||
.arg(&url)
|
||||
.arg("-v")
|
||||
|
||||
@@ -30,10 +30,7 @@ pub async fn run(cfg: DatabaseConfig) -> Result<bool> {
|
||||
let output = output.context("Failed to execute redis-cli")?;
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
if !stdout.is_empty() {
|
||||
error!("Redis stderr: {}", stderr);
|
||||
}
|
||||
|
||||
|
||||
if stderr.contains("NOAUTH") {
|
||||
error!("Redis authentication failed (NOAUTH required)");
|
||||
|
||||
@@ -19,6 +19,7 @@ pub async fn run(
|
||||
};
|
||||
|
||||
let db_path = PathBuf::from(db_path_str);
|
||||
info!("database path: {}", db_path.display());
|
||||
|
||||
if !db_path.exists() {
|
||||
anyhow::bail!("SQLite database file not found: {}", db_path.display());
|
||||
@@ -31,7 +32,7 @@ pub async fn run(
|
||||
.arg(format!(".backup '{}'", file_path.display()))
|
||||
.output()
|
||||
.context("SQLite backup command failed to start")?;
|
||||
|
||||
info!("Backup successful: {:?}", output);
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
error!("SQLite backup failed for {}: {}", cfg.name, stderr);
|
||||
|
||||
@@ -30,11 +30,7 @@ pub async fn run(cfg: DatabaseConfig) -> Result<bool> {
|
||||
let output = output.context("Failed to execute valkey-cli")?;
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
if !stdout.is_empty() {
|
||||
error!("Valkey stderr: {}", stderr);
|
||||
}
|
||||
|
||||
|
||||
if stderr.contains("NOAUTH") {
|
||||
error!("Valkey authentication failed (NOAUTH required)");
|
||||
return Ok(false);
|
||||
|
||||
@@ -52,13 +52,16 @@ impl BackupService {
|
||||
code: Some("backup_already_in_progress".into()),
|
||||
}),
|
||||
|
||||
Err(_) => Ok(BackupResult {
|
||||
generated_id,
|
||||
db_type,
|
||||
status: "failed".into(),
|
||||
backup_file: None,
|
||||
code: None,
|
||||
}),
|
||||
Err(e) => {
|
||||
error!("Backup failed for {}: {:?}", generated_id, e);
|
||||
Ok(BackupResult {
|
||||
generated_id,
|
||||
db_type,
|
||||
status: "failed".into(),
|
||||
backup_file: None,
|
||||
code: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-4
@@ -21,6 +21,8 @@ pub enum DbType {
|
||||
Sqlite,
|
||||
Redis,
|
||||
Valkey,
|
||||
Firebird,
|
||||
Mssql,
|
||||
}
|
||||
|
||||
impl DbType {
|
||||
@@ -33,6 +35,8 @@ impl DbType {
|
||||
DbType::Sqlite => "sqlite",
|
||||
DbType::Redis => "redis",
|
||||
DbType::Valkey => "valkey",
|
||||
DbType::Firebird => "firebird",
|
||||
DbType::Mssql => "mssql",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,14 +167,14 @@ impl ConfigService {
|
||||
}
|
||||
|
||||
let username = match db.db_type {
|
||||
DbType::Postgresql | DbType::Mysql | DbType::Mariadb => {
|
||||
DbType::Postgresql | DbType::Mysql | DbType::Mariadb | DbType::Mssql => {
|
||||
required(&db.username, &db.name, "username")?
|
||||
}
|
||||
_ => optional(&db.username),
|
||||
};
|
||||
|
||||
let password = match db.db_type {
|
||||
DbType::Postgresql | DbType::Mysql | DbType::Mariadb => {
|
||||
DbType::Postgresql | DbType::Mysql | DbType::Mariadb | DbType::Mssql => {
|
||||
required(&db.password, &db.name, "password")?
|
||||
}
|
||||
_ => optional(&db.password),
|
||||
@@ -182,7 +186,9 @@ impl ConfigService {
|
||||
| DbType::Mariadb
|
||||
| DbType::MongoDB
|
||||
| DbType::Redis
|
||||
| DbType::Valkey => required(&db.host, &db.name, "host")?,
|
||||
| DbType::Firebird
|
||||
| DbType::Valkey
|
||||
| DbType::Mssql => required(&db.host, &db.name, "host")?,
|
||||
DbType::Sqlite => optional(&db.host),
|
||||
};
|
||||
|
||||
@@ -192,7 +198,9 @@ impl ConfigService {
|
||||
| DbType::Mariadb
|
||||
| DbType::MongoDB
|
||||
| DbType::Redis
|
||||
| DbType::Valkey => required(&db.port, &db.name, "port")?,
|
||||
| DbType::Firebird
|
||||
| DbType::Valkey
|
||||
| DbType::Mssql => required(&db.port, &db.name, "port")?,
|
||||
DbType::Sqlite => db.port.unwrap_or(0),
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use reqwest::Client;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use futures_util::future::try_join_all;
|
||||
use tracing::info;
|
||||
use crate::domain::factory::DatabaseFactory;
|
||||
|
||||
pub struct StatusService {
|
||||
@@ -32,6 +33,7 @@ impl StatusService {
|
||||
let db_engine = DatabaseFactory::create_for_backup(db.clone()).await;
|
||||
|
||||
let reachable = db_engine.ping().await?;
|
||||
info!("Ping {} => {:?}",db.name, reachable);
|
||||
|
||||
Ok::<DatabasePayload, anyhow::Error>(DatabasePayload {
|
||||
name: &db.name,
|
||||
|
||||
@@ -14,6 +14,7 @@ pub struct Settings {
|
||||
pub pooling: usize,
|
||||
pub timezone: String,
|
||||
pub log: String,
|
||||
pub chunk_size: usize, // bytes
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
@@ -35,6 +36,18 @@ impl Settings {
|
||||
pooling_seconds
|
||||
);
|
||||
}
|
||||
|
||||
let chunk_size_mb = env::var("CHUNK_SIZE_MB")
|
||||
.unwrap_or_else(|_| "1".to_string())
|
||||
.parse::<usize>()
|
||||
.expect("CHUNK_SIZE_MB must be a valid positive integer");
|
||||
|
||||
if chunk_size_mb == 0 || chunk_size_mb > 10 {
|
||||
panic!("CHUNK_SIZE_MB must be between 1 and 10 MB");
|
||||
}
|
||||
|
||||
let chunk_size = chunk_size_mb * 1024 * 1024;
|
||||
|
||||
let tz = env::var("TZ").unwrap_or_else(|_| "UTC".to_string());
|
||||
|
||||
Self {
|
||||
@@ -49,6 +62,7 @@ impl Settings {
|
||||
pooling: pooling_seconds,
|
||||
timezone: tz,
|
||||
log: env::var("LOG").unwrap_or_else(|_| "info".into()),
|
||||
chunk_size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
use crate::domain::factory::DatabaseFactory;
|
||||
use crate::services::config::{DatabaseConfig, DbType};
|
||||
use crate::tests::init_tracing_for_test;
|
||||
use crate::utils::compress::{compress_to_tar_gz_large, decompress_large_tar_gz};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use tempfile::TempDir;
|
||||
use testcontainers::runners::AsyncRunner;
|
||||
use testcontainers::{ContainerAsync, GenericImage, ImageExt};
|
||||
use testcontainers::core::{AccessMode, IntoContainerPort};
|
||||
use tracing::{error, info};
|
||||
use testcontainers::core::{Mount};
|
||||
|
||||
async fn create_config() -> (ContainerAsync<GenericImage>, DatabaseConfig) {
|
||||
|
||||
let mount = Mount::volume_mount("firebird-test-data", "/var/lib/firebird/data")
|
||||
.with_access_mode(AccessMode::ReadWrite);
|
||||
|
||||
let container = GenericImage::new("firebirdsql/firebird", "latest")
|
||||
.with_exposed_port(3050.tcp())
|
||||
.with_mount(mount)
|
||||
.with_env_var("FIREBIRD_ROOT_PASSWORD", "fake_root_password")
|
||||
.with_env_var("FIREBIRD_USER", "alice")
|
||||
.with_env_var("FIREBIRD_PASSWORD", "fake_password")
|
||||
.with_env_var("FIREBIRD_DATABASE", "mirror.fdb")
|
||||
.with_env_var("FIREBIRD_DATABASE_DEFAULT_CHARSET", "UTF8")
|
||||
.start()
|
||||
.await
|
||||
.expect("Firebird started");
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(15)).await;
|
||||
|
||||
let host = container.get_host().await.unwrap().to_string();
|
||||
let port = container.get_host_port_ipv4(3050).await.unwrap();
|
||||
|
||||
let database = "/var/lib/firebird/data/mirror.fdb";
|
||||
|
||||
let config = DatabaseConfig {
|
||||
name: "Test Firebird".to_string(),
|
||||
database: database.to_string(),
|
||||
db_type: DbType::Firebird,
|
||||
username: "alice".to_string(),
|
||||
password: "fake_password".to_string(),
|
||||
port,
|
||||
host,
|
||||
generated_id: "3c445eb4-c2c6-4bde-a423-ee1385dcf6d2".to_string(),
|
||||
path: "".to_string(),
|
||||
};
|
||||
|
||||
(container, config)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn firebird_ping_test() {
|
||||
init_tracing_for_test();
|
||||
|
||||
let (_container, config) = create_config().await;
|
||||
|
||||
let db = DatabaseFactory::create_for_backup(config.clone()).await;
|
||||
let reachable = db.ping().await.unwrap_or(false);
|
||||
|
||||
assert!(reachable);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn firebird_backup_restore_test() {
|
||||
init_tracing_for_test();
|
||||
|
||||
let (_container, config) = create_config().await;
|
||||
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let backup_path = temp_dir.path();
|
||||
|
||||
let db = DatabaseFactory::create_for_backup(config.clone()).await;
|
||||
|
||||
let file_path = db.backup(backup_path).await.unwrap();
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
|
||||
assert!(file_path.is_file());
|
||||
|
||||
let compression = compress_to_tar_gz_large(&file_path).await.unwrap();
|
||||
assert!(compression.compressed_path.is_file());
|
||||
|
||||
let files = decompress_large_tar_gz(
|
||||
compression.compressed_path.as_path(),
|
||||
temp_dir.path(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let backup_file: PathBuf = if files.len() == 1 {
|
||||
files[0].clone()
|
||||
} else {
|
||||
panic!("Unexpected number of files after decompression");
|
||||
};
|
||||
|
||||
let db = DatabaseFactory::create_for_restore(config.clone(), &backup_file).await;
|
||||
|
||||
let reachable = db.ping().await.unwrap_or(false);
|
||||
info!("Reachable: {}", reachable);
|
||||
assert!(reachable);
|
||||
|
||||
match db.restore(&backup_file).await {
|
||||
Ok(_) => {
|
||||
info!("Restore succeeded for {}", config.generated_id);
|
||||
assert!(true)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Restore failed for {}: {:?}", config.generated_id, e);
|
||||
assert!(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,3 +4,5 @@ mod mysql;
|
||||
mod postgres;
|
||||
mod redis;
|
||||
mod valkey;
|
||||
mod firebird;
|
||||
mod mssql;
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
use crate::domain::factory::DatabaseFactory;
|
||||
use crate::services::config::{DatabaseConfig, DbType};
|
||||
use crate::tests::init_tracing_for_test;
|
||||
use crate::utils::compress::{compress_to_tar_gz_large, decompress_large_tar_gz};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use tempfile::TempDir;
|
||||
use tiberius::{AuthMethod, Client, Config};
|
||||
use testcontainers::runners::AsyncRunner;
|
||||
use testcontainers::{ContainerAsync, GenericImage, ImageExt};
|
||||
use testcontainers::core::IntoContainerPort;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::compat::TokioAsyncWriteCompatExt;
|
||||
use tracing::{error, info};
|
||||
|
||||
const SA_PASSWORD: &str = "Test!Str0ng1";
|
||||
|
||||
async fn start_container() -> ContainerAsync<GenericImage> {
|
||||
GenericImage::new("mcr.microsoft.com/azure-sql-edge", "latest")
|
||||
.with_exposed_port(1433.tcp())
|
||||
.with_env_var("ACCEPT_EULA", "Y")
|
||||
.with_env_var("MSSQL_SA_PASSWORD", SA_PASSWORD)
|
||||
.start()
|
||||
.await
|
||||
.expect("azure-sql-edge container started")
|
||||
}
|
||||
|
||||
async fn create_user_database(host: &str, port: u16, db_name: &str) {
|
||||
let mut config = Config::new();
|
||||
config.host(host);
|
||||
config.port(port);
|
||||
config.authentication(AuthMethod::sql_server("sa", SA_PASSWORD));
|
||||
config.trust_cert();
|
||||
|
||||
let tcp = TcpStream::connect(config.get_addr()).await.unwrap();
|
||||
tcp.set_nodelay(true).unwrap();
|
||||
let mut client = Client::connect(config, tcp.compat_write()).await.unwrap();
|
||||
|
||||
let sql = format!(
|
||||
"IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = N'{}') CREATE DATABASE [{}]",
|
||||
db_name, db_name
|
||||
);
|
||||
client.simple_query(sql.as_str()).await.unwrap();
|
||||
}
|
||||
|
||||
fn make_config(host: String, port: u16, database: &str) -> DatabaseConfig {
|
||||
DatabaseConfig {
|
||||
name: "Test MSSQL".to_string(),
|
||||
database: database.to_string(),
|
||||
db_type: DbType::Mssql,
|
||||
username: "sa".to_string(),
|
||||
password: SA_PASSWORD.to_string(),
|
||||
port,
|
||||
host,
|
||||
generated_id: "5a445eb4-c2c6-4bde-a423-ee1385dcf6d3".to_string(),
|
||||
path: "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mssql_ping_test() {
|
||||
init_tracing_for_test();
|
||||
|
||||
let container = start_container().await;
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
|
||||
let host = container.get_host().await.unwrap().to_string();
|
||||
let port = container.get_host_port_ipv4(1433).await.unwrap();
|
||||
let config = make_config(host, port, "master");
|
||||
|
||||
let db = DatabaseFactory::create_for_backup(config).await;
|
||||
let reachable = db.ping().await.unwrap_or(false);
|
||||
|
||||
assert!(reachable, "MSSQL ping should succeed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mssql_backup_test() {
|
||||
init_tracing_for_test();
|
||||
|
||||
let container = start_container().await;
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
|
||||
let host = container.get_host().await.unwrap().to_string();
|
||||
let port = container.get_host_port_ipv4(1433).await.unwrap();
|
||||
|
||||
create_user_database(&host, port, "backupdb").await;
|
||||
|
||||
let config = make_config(host, port, "backupdb");
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
let db = DatabaseFactory::create_for_backup(config).await;
|
||||
let file_path = db.backup(temp_dir.path()).await.unwrap();
|
||||
|
||||
assert!(file_path.is_file(), "backup file should exist");
|
||||
assert!(
|
||||
file_path.metadata().unwrap().len() > 0,
|
||||
"backup file should be non-empty"
|
||||
);
|
||||
assert!(
|
||||
file_path.extension().and_then(|e| e.to_str()) == Some("bacpac"),
|
||||
"backup file should have .bacpac extension"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mssql_backup_restore_test() {
|
||||
init_tracing_for_test();
|
||||
|
||||
let container = start_container().await;
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
|
||||
let host = container.get_host().await.unwrap().to_string();
|
||||
let port = container.get_host_port_ipv4(1433).await.unwrap();
|
||||
|
||||
create_user_database(&host, port, "sourcedb").await;
|
||||
|
||||
let backup_config = make_config(host.clone(), port, "sourcedb");
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
let db = DatabaseFactory::create_for_backup(backup_config).await;
|
||||
let file_path = db.backup(temp_dir.path()).await.unwrap();
|
||||
assert!(file_path.is_file());
|
||||
|
||||
let compression = compress_to_tar_gz_large(&file_path).await.unwrap();
|
||||
assert!(compression.compressed_path.is_file());
|
||||
|
||||
let files = decompress_large_tar_gz(
|
||||
compression.compressed_path.as_path(),
|
||||
temp_dir.path(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let backup_file: PathBuf = if files.len() == 1 {
|
||||
files[0].clone()
|
||||
} else {
|
||||
panic!("Unexpected number of files after decompression: {}", files.len());
|
||||
};
|
||||
|
||||
let restore_config = make_config(host, port, "restoreddb");
|
||||
let db_restore = DatabaseFactory::create_for_restore(restore_config, &backup_file).await;
|
||||
|
||||
match db_restore.restore(&backup_file).await {
|
||||
Ok(_) => info!("MSSQL restore succeeded"),
|
||||
Err(e) => {
|
||||
error!("MSSQL restore failed: {:?}", e);
|
||||
panic!("Restore failed: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-7
@@ -3,12 +3,15 @@ use anyhow::Result;
|
||||
use bytes::Bytes;
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::pin::Pin;
|
||||
use tokio_util::io::ReaderStream;
|
||||
use tokio::io::{AsyncReadExt};
|
||||
use crate::settings::CONFIG;
|
||||
|
||||
pub struct UploadStream {
|
||||
pub stream: Pin<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub async fn build_stream(
|
||||
file_path: &std::path::Path,
|
||||
encrypt: bool,
|
||||
@@ -25,13 +28,27 @@ pub async fn build_stream(
|
||||
|
||||
Ok(UploadStream { stream })
|
||||
} else {
|
||||
let file = tokio::fs::File::open(file_path).await?;
|
||||
let reader = ReaderStream::new(file);
|
||||
let mut file = tokio::fs::File::open(file_path).await?;
|
||||
|
||||
let stream = Box::pin(
|
||||
reader.map(|r| r.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))),
|
||||
);
|
||||
let stream = async_stream::stream! {
|
||||
let mut buffer = vec![0u8; CONFIG.chunk_size];
|
||||
|
||||
Ok(UploadStream { stream })
|
||||
loop {
|
||||
let n = match file.read(&mut buffer).await {
|
||||
Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
yield Ok(Bytes::copy_from_slice(&buffer[..n]));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(UploadStream {
|
||||
stream: Box::pin(stream),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -3,8 +3,7 @@ use bytes::Bytes;
|
||||
use futures::{Stream, StreamExt};
|
||||
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
|
||||
use tracing::{error, info};
|
||||
|
||||
const PATCH_CHUNK_SIZE: usize = 1 * 1024 * 1024;
|
||||
use crate::settings::CONFIG;
|
||||
|
||||
pub async fn upload_to_tus_stream_with_headers<S>(
|
||||
encrypted_stream: S,
|
||||
@@ -66,7 +65,9 @@ where
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk.context("Stream produced IO error")?;
|
||||
|
||||
for sub_chunk in chunk.chunks(PATCH_CHUNK_SIZE) {
|
||||
info!("Chunk: {:?}", CONFIG.chunk_size);
|
||||
|
||||
for sub_chunk in chunk.chunks(CONFIG.chunk_size) {
|
||||
let mut patch_headers = extra_headers.clone();
|
||||
patch_headers.insert("Tus-Resumable", HeaderValue::from_static("1.0.0"));
|
||||
patch_headers.insert(
|
||||
|
||||
Reference in New Issue
Block a user