mirror of
https://github.com/Portabase/agent.git
synced 2026-09-11 02:27:10 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25c95108f0 | |||
| f37e4d2de9 | |||
| a1aa66ff3f | |||
| d4abcbcebc |
+1
-1
@@ -27,5 +27,5 @@ keywords:
|
||||
- self-hosted
|
||||
- portabase
|
||||
license: Apache-2.0
|
||||
version: 1.7.0
|
||||
version: 1.9.0
|
||||
date-released: '2026-02-24'
|
||||
|
||||
Generated
+1
-1
@@ -3038,7 +3038,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "portabase-agent"
|
||||
version = "1.7.0"
|
||||
version = "1.9.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes-gcm",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "portabase-agent"
|
||||
version = "1.7.0"
|
||||
version = "1.9.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -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
|
||||
+29
-9
@@ -13,7 +13,7 @@
|
||||
{
|
||||
"name": "Test database 2 - MariaDB",
|
||||
"database": "mariadb",
|
||||
"type": "mysql",
|
||||
"type": "mariadb",
|
||||
"username": "mariadb",
|
||||
"password": "changeme",
|
||||
"port": 3306,
|
||||
@@ -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,20 +67,40 @@
|
||||
"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,
|
||||
"username": "default",
|
||||
"host": "db-valkey-auth",
|
||||
"generated_id": "16678561-ff7e-4c97-8c83-0adeff214681"
|
||||
},
|
||||
{
|
||||
"name": "Test database 11 - Mysql",
|
||||
"database": "mysqldb",
|
||||
"type": "mysql",
|
||||
"username": "mysqldb",
|
||||
"password": "changeme",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+67
-3
@@ -29,11 +29,75 @@ 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"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
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:
|
||||
- "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
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
networks:
|
||||
portabase:
|
||||
name: portabase_network
|
||||
external: true
|
||||
|
||||
+13
-144
@@ -7,165 +7,34 @@ 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
|
||||
#- agent-rust_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: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiOWRjZjYzNzQtZTc5ZC00MzU5LTkwMTYtNjA0OTQ2NDNhNjEwIiwibWFzdGVyS2V5QjY0IjoiQlhWM1hvbEM2NTZTVjdkTmdjV1BHUWxrKytycExJNmxHRGk3Q1BCNWllbz0ifQ=="
|
||||
#POOLING: 1
|
||||
#DATABASES_CONFIG_FILE: "config.toml"
|
||||
DATABASES_CONFIG_FILE: "config.toml"
|
||||
extra_hosts:
|
||||
- "localhost:host-gateway"
|
||||
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-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:
|
||||
# mongodb-data:
|
||||
# mongodb-data-auth:
|
||||
# sqlite-data:
|
||||
redis-data:
|
||||
redis-data-auth:
|
||||
valkey-data:
|
||||
valkey-data-auth:
|
||||
#cargo-target:
|
||||
# agent-rust_sqlite-data:
|
||||
# external: true
|
||||
|
||||
|
||||
|
||||
networks:
|
||||
portabase:
|
||||
|
||||
@@ -18,6 +18,7 @@ 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/*
|
||||
|
||||
@@ -46,6 +47,7 @@ RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
rm -rf /tmp/pg-x64 /tmp/pg-arm && \
|
||||
chmod +x /usr/lib/postgresql/*/bin/*
|
||||
|
||||
|
||||
# =========================
|
||||
# MongoDB client binaries
|
||||
# =========================
|
||||
|
||||
@@ -31,9 +31,10 @@ check_network() {
|
||||
fi
|
||||
}
|
||||
|
||||
check_docker
|
||||
#check_docker
|
||||
check_network
|
||||
|
||||
echo "Starting docker-compose..."
|
||||
docker compose -f ./docker-compose.databases.yml up -d
|
||||
docker compose -f ./docker-compose.yml up
|
||||
echo "Docker-compose started successfully."
|
||||
@@ -0,0 +1,54 @@
|
||||
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..."
|
||||
docker exec -i db-firebird isql -user alice -password fake_password /var/lib/firebird/data/mirror.fdb -sql "SHOW TABLES;"
|
||||
|
||||
seed-all:
|
||||
just seed-mongo
|
||||
just seed-mysql
|
||||
just seed-postgres
|
||||
just seed-postgres-1gb
|
||||
just seed-sqlite
|
||||
just seed-mongo
|
||||
just seed-firebird
|
||||
@@ -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,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,6 +9,8 @@ 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;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Database: Send + Sync {
|
||||
@@ -28,11 +30,12 @@ impl DatabaseFactory {
|
||||
Arc::new(PostgresDatabase::new(cfg, format))
|
||||
}
|
||||
DbType::Mysql => Arc::new(MySQLDatabase::new(cfg)),
|
||||
DbType::Mariadb => Arc::new(MySQLDatabase::new(cfg)),
|
||||
DbType::Mariadb => Arc::new(MariaDBDatabase::new(cfg)),
|
||||
DbType::MongoDB => Arc::new(MongoDatabase::new(cfg)),
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +46,12 @@ impl DatabaseFactory {
|
||||
Arc::new(PostgresDatabase::new(cfg, format))
|
||||
}
|
||||
DbType::Mysql => Arc::new(MySQLDatabase::new(cfg)),
|
||||
DbType::Mariadb => Arc::new(MySQLDatabase::new(cfg)),
|
||||
DbType::Mariadb => Arc::new(MariaDBDatabase::new(cfg)),
|
||||
DbType::MongoDB => Arc::new(MongoDatabase::new(cfg)),
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
use crate::domain::mariadb::connection::{select_mariadb_path, server_version};
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::{Context, Result};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub async fn run(
|
||||
cfg: DatabaseConfig,
|
||||
backup_dir: PathBuf,
|
||||
env: HashMap<String, String>,
|
||||
file_extension: &'static str,
|
||||
) -> Result<PathBuf> {
|
||||
tokio::task::spawn_blocking(move || -> Result<PathBuf> {
|
||||
debug!("Starting backup for database {}", cfg.name);
|
||||
|
||||
let version = match futures::executor::block_on(server_version(&cfg)) {
|
||||
Ok(v) => {
|
||||
debug!("Mariadb version detected: {}", v);
|
||||
v
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get server version for {}: {:?}", cfg.name, e);
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
info!("Mariadb version found: {}", version);
|
||||
|
||||
let file_path = backup_dir.join(format!("{}{}", cfg.generated_id, file_extension));
|
||||
|
||||
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())
|
||||
.arg("--user").arg(&cfg.username)
|
||||
.arg("--routines")
|
||||
.arg("--events")
|
||||
.arg("--triggers")
|
||||
.arg("--single-transaction")
|
||||
.arg("--quick")
|
||||
.arg("--skip-lock-tables")
|
||||
.arg("--add-drop-database")
|
||||
.arg("--databases").arg(&cfg.database)
|
||||
.arg("--compress")
|
||||
.arg("--max-allowed-packet=512M")
|
||||
.arg("--net-buffer-length=16K")
|
||||
.arg("--default-character-set=utf8mb4")
|
||||
.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);
|
||||
}
|
||||
|
||||
Ok(file_path)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::path::PathBuf;
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::Result;
|
||||
use std::process::Command;
|
||||
|
||||
pub async fn server_version(cfg: &DatabaseConfig) -> Result<String> {
|
||||
let output = Command::new("mariadb")
|
||||
.arg("--host")
|
||||
.arg(&cfg.host)
|
||||
.arg("--port")
|
||||
.arg(cfg.port.to_string())
|
||||
.arg("--user")
|
||||
.arg(&cfg.username)
|
||||
.arg("-e")
|
||||
.arg("SELECT VERSION();")
|
||||
.env("MYSQL_PWD", &cfg.password)
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
anyhow::bail!("Version query failed: {}", stderr);
|
||||
}
|
||||
|
||||
let version = String::from_utf8_lossy(&output.stdout)
|
||||
.lines()
|
||||
.nth(1)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
|
||||
pub fn select_mariadb_path(version: &str) -> PathBuf {
|
||||
let mut parts = version.split('.');
|
||||
let major = parts.next().and_then(|v| v.parse::<u32>().ok()).unwrap_or(10);
|
||||
let minor = parts.next().and_then(|v| v.parse::<u32>().ok()).unwrap_or(0);
|
||||
|
||||
if major < 10 || (major == 10 && minor <= 6) {
|
||||
"/usr/local/mariadb-10.6/bin".into()
|
||||
} else {
|
||||
"/usr/local/mariadb-12.1/bin".into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct MariaDBDatabase {
|
||||
cfg: DatabaseConfig,
|
||||
}
|
||||
|
||||
impl MariaDBDatabase {
|
||||
pub fn new(cfg: DatabaseConfig) -> Self {
|
||||
Self { cfg }
|
||||
}
|
||||
|
||||
fn build_env(&self) -> HashMap<String, String> {
|
||||
let mut envs = std::env::vars().collect::<HashMap<_, _>>();
|
||||
envs.insert("MYSQL_PWD".to_string(), self.cfg.password.to_string());
|
||||
envs
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Database for MariaDBDatabase {
|
||||
fn file_extension(&self) -> &'static str {
|
||||
".sql"
|
||||
}
|
||||
|
||||
async fn ping(&self) -> Result<bool> {
|
||||
ping::run(self.cfg.clone(), self.build_env().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.build_env().clone(),
|
||||
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 backup;
|
||||
mod connection;
|
||||
pub mod database;
|
||||
mod ping;
|
||||
mod restore;
|
||||
@@ -0,0 +1,28 @@
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use std::collections::HashMap;
|
||||
use tokio::process::Command;
|
||||
use tokio::time::{Duration, timeout};
|
||||
|
||||
pub async fn run(cfg: DatabaseConfig, env: HashMap<String, String>) -> anyhow::Result<bool> {
|
||||
let mut cmd = Command::new("mysqladmin");
|
||||
cmd.arg("--host")
|
||||
.arg(cfg.host)
|
||||
.arg("--port")
|
||||
.arg(cfg.port.to_string())
|
||||
.arg("--user")
|
||||
.arg(cfg.username)
|
||||
.arg("ping")
|
||||
.envs(env);
|
||||
|
||||
let result = timeout(Duration::from_secs(10), cmd.output()).await;
|
||||
|
||||
match result {
|
||||
Ok(output) => {
|
||||
let output = output?;
|
||||
Ok(output.status.success())
|
||||
}
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
use crate::services::config::DatabaseConfig;
|
||||
use anyhow::{Context, Result};
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub async fn run(cfg: DatabaseConfig, restore_file: PathBuf) -> Result<()> {
|
||||
let handle = tokio::task::spawn_blocking(move || -> Result<()> {
|
||||
debug!("Starting restore for database {}", cfg.name);
|
||||
|
||||
let mut sql_content = String::new();
|
||||
let mut file = File::open(&restore_file)
|
||||
.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};",
|
||||
cfg.database
|
||||
);
|
||||
|
||||
let drop_status = Command::new("mariadb")
|
||||
.arg("--host")
|
||||
.arg(&cfg.host)
|
||||
.arg("--port")
|
||||
.arg(cfg.port.to_string())
|
||||
.arg("--user")
|
||||
.arg(&cfg.username)
|
||||
.arg("-e")
|
||||
.arg(&drop_create_cmd)
|
||||
.env("MYSQL_PWD", &cfg.password)
|
||||
.status()
|
||||
.with_context(|| format!("Failed to drop/recreate database {}", cfg.name))?;
|
||||
|
||||
if !drop_status.success() {
|
||||
error!("Drop/create database failed for {}", cfg.name);
|
||||
anyhow::bail!("Failed to drop/recreate database {}", cfg.name);
|
||||
}
|
||||
info!("Database {} dropped and recreated", cfg.name);
|
||||
|
||||
let mut child = Command::new("mariadb")
|
||||
.arg("--host")
|
||||
.arg(&cfg.host)
|
||||
.arg("--port")
|
||||
.arg(cfg.port.to_string())
|
||||
.arg("--user")
|
||||
.arg(&cfg.username)
|
||||
.arg(&cfg.database)
|
||||
.env("MYSQL_PWD", &cfg.password)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.with_context(|| format!("Failed to start MariaDB restore for {}", cfg.name))?;
|
||||
|
||||
let mut stdin = child.stdin.take().context("Failed to open child stdin")?;
|
||||
stdin
|
||||
.write_all(sql_content.as_bytes())
|
||||
.context("Failed to write SQL content to MariaDB stdin")?;
|
||||
stdin.flush()?;
|
||||
drop(stdin);
|
||||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.with_context(|| format!("Failed to complete MariaDB restore for {}", cfg.name))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
error!("MariaDB restore failed for {}: {}", cfg.name, stderr);
|
||||
anyhow::bail!("MariaDB restore failed for {}", cfg.name);
|
||||
}
|
||||
|
||||
info!("Restore finished successfully for database {}", cfg.name);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
handle.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,3 +5,5 @@ pub mod postgres;
|
||||
mod redis;
|
||||
mod sqlite;
|
||||
mod valkey;
|
||||
mod mariadb;
|
||||
mod firebird;
|
||||
|
||||
@@ -30,6 +30,9 @@ 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)
|
||||
@@ -54,9 +57,12 @@ pub async fn run(
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
info!("mysqldump stderr: {}", stderr);
|
||||
anyhow::bail!("MySQL backup failed for {}: {}", cfg.name, stderr);
|
||||
}
|
||||
|
||||
info!("Output {}", String::from_utf8_lossy(&output.stdout));
|
||||
|
||||
Ok(file_path)
|
||||
})
|
||||
.await?
|
||||
|
||||
@@ -4,7 +4,7 @@ use tokio::process::Command;
|
||||
use tokio::time::{Duration, timeout};
|
||||
|
||||
pub async fn run(cfg: DatabaseConfig, env: HashMap<String, String>) -> anyhow::Result<bool> {
|
||||
let mut cmd = Command::new("mysqladmin");
|
||||
let mut cmd = Command::new("mariadb-admin");
|
||||
cmd.arg("--host")
|
||||
.arg(cfg.host)
|
||||
.arg("--port")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -21,18 +21,20 @@ pub enum DbType {
|
||||
Sqlite,
|
||||
Redis,
|
||||
Valkey,
|
||||
Firebird
|
||||
}
|
||||
|
||||
impl DbType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
DbType::Mysql => "mysql",
|
||||
DbType::Mariadb => "mysql",
|
||||
DbType::Mariadb => "mariadb",
|
||||
DbType::Postgresql => "postgresql",
|
||||
DbType::MongoDB => "mongodb",
|
||||
DbType::Sqlite => "sqlite",
|
||||
DbType::Redis => "redis",
|
||||
DbType::Valkey => "valkey",
|
||||
DbType::Firebird => "firebird",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,6 +184,7 @@ impl ConfigService {
|
||||
| DbType::Mariadb
|
||||
| DbType::MongoDB
|
||||
| DbType::Redis
|
||||
| DbType::Firebird
|
||||
| DbType::Valkey => required(&db.host, &db.name, "host")?,
|
||||
DbType::Sqlite => optional(&db.host),
|
||||
};
|
||||
@@ -192,6 +195,7 @@ impl ConfigService {
|
||||
| DbType::Mariadb
|
||||
| DbType::MongoDB
|
||||
| DbType::Redis
|
||||
| DbType::Firebird
|
||||
| DbType::Valkey => 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 {
|
||||
@@ -33,6 +34,8 @@ impl StatusService {
|
||||
|
||||
let reachable = db_engine.ping().await?;
|
||||
|
||||
info!("Ping {} => {:?}",db.name, reachable);
|
||||
|
||||
Ok::<DatabasePayload, anyhow::Error>(DatabasePayload {
|
||||
name: &db.name,
|
||||
dbms: &db.db_type.as_str(),
|
||||
|
||||
@@ -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,4 @@ mod mysql;
|
||||
mod postgres;
|
||||
mod redis;
|
||||
mod valkey;
|
||||
mod firebird;
|
||||
|
||||
Reference in New Issue
Block a user