Compare commits

8 Commits

Author SHA1 Message Date
31903e8ff9 Add clippy step to code quality configuration and update test configuration
All checks were successful
ci/woodpecker/push/code-quality/1 Pipeline was successful
ci/woodpecker/push/code-quality/2 Pipeline was successful
ci/woodpecker/push/test/1 Pipeline was successful
ci/woodpecker/push/test/2 Pipeline was successful
2025-11-13 22:48:13 +00:00
9b7d07cdae Implement find_enabled_services method in DockerAdapter; update Docker struct and main function to retrieve and display enabled services.
All checks were successful
ci/woodpecker/push/test/1 Pipeline was successful
ci/woodpecker/push/test/2 Pipeline was successful
2025-11-13 22:19:42 +00:00
05a839044c looool
All checks were successful
ci/woodpecker/push/test/1 Pipeline was successful
ci/woodpecker/push/test/2 Pipeline was successful
2025-11-13 21:46:01 +00:00
5ba96e86d9 Refactor Docker integration: update BollardAdapter to use async Mutex, implement version retrieval, and clean up unused service methods.
All checks were successful
ci/woodpecker/push/test/1 Pipeline was successful
ci/woodpecker/push/test/2 Pipeline was successful
2025-11-13 21:43:26 +00:00
f3a107b2a6 Add event condition for clippy step in CI configuration
Some checks failed
ci/woodpecker/push/test/1 Pipeline failed
ci/woodpecker/push/test/2 Pipeline failed
2025-11-13 21:12:30 +00:00
aa056419c6 add missing clippy install
Some checks failed
ci/woodpecker/push/test/1 Pipeline failed
ci/woodpecker/push/test/2 Pipeline failed
2025-11-13 20:53:33 +00:00
3f14c0d572 Implement Docker integration with BollardAdapter; add Dockerfile, docker-compose, and update dependencies
Some checks failed
ci/woodpecker/push/test/1 Pipeline failed
ci/woodpecker/push/test/2 Pipeline failed
2025-11-13 20:52:25 +00:00
d0a9583d22 Add .env and .env.example files; implement development script and Docker connection logic 2025-11-12 22:54:16 +00:00
12 changed files with 252 additions and 8 deletions

13
.env.example Normal file
View File

@@ -0,0 +1,13 @@
# Backup Configuration
BACKUP_FOLDER=/data/volumes
BACKUP_CRON=*/5 * * * *
# FTP Configuration
FTP_HOST=ftp.example.com
FTP_PORT=21
FTP_USER=backup_user
FTP_PASSWORD=secret123
FTP_PATH=/backups
# Docker Configuration
DOCKER_HOST=unix:///var/run/docker.sock

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target
.env

View File

@@ -0,0 +1,13 @@
steps:
- name: clippy
image: rust:${RUST_VERSION}
commands:
- rustup component add clippy
- cargo clippy --all-targets --all-features -- -D warnings
when:
- event: [push, pull_request]
matrix:
RUST_VERSION:
- "1.91.1"
- "1.91.0"

20
.woodpecker/test.yml Normal file
View File

@@ -0,0 +1,20 @@
services:
- name: docker
image: docker:dind
privileged: true
commands:
- dockerd-entrypoint.sh &
- sleep 5
steps:
- name: test
image: rust:${RUST_VERSION}
environment:
DOCKER_HOST: tcp://docker:2375
commands:
- cargo test --verbose
matrix:
RUST_VERSION:
- "1.91.1"
- "1.91.0"

12
Cargo.lock generated
View File

@@ -30,6 +30,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "async-trait"
version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@@ -54,6 +65,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-compression",
"async-trait",
"bollard",
"tokio",
"tokio-tar",

View File

@@ -9,3 +9,4 @@ async-compression = { version = "0.4", features = ["tokio", "xz"] }
bollard = "0.19"
anyhow = "1.0"
tokio-tar = "0.3"
async-trait = "0.1"

11
Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM rust:1.91.1
WORKDIR /beekeper
COPY src/ src/
COPY Cargo.toml .
COPY Cargo.lock .
RUN cargo build --release
CMD ["./target/release/beekeper"]

View File

@@ -8,6 +8,9 @@ services:
volumes:
- caddy_data:/data
- caddy_config:/config
labels:
- "beekeper.enable"
- "beekeper.pre_command=caddy stop"
volumes:
caddy_data:

38
scripts/dev.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
set -e
# Couleurs pour les messages
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}🐝 Beekeeper Development Script${NC}"
# Vérifier si le fichier .env existe
if [ ! -f .env ]; then
echo -e "${RED}❌ Fichier .env introuvable${NC}"
echo -e "${YELLOW}Créez un fichier .env avec les variables nécessaires${NC}"
exit 1
fi
# Charger les variables d'environnement
echo -e "${GREEN}📋 Chargement des variables d'environnement depuis .env${NC}"
set -a
source .env
set +a
# Vérifier si cargo-watch est installé
if ! command -v cargo-watch &> /dev/null; then
echo -e "${YELLOW}⚠️ cargo-watch n'est pas installé${NC}"
echo -e "${YELLOW}Installation de cargo-watch...${NC}"
cargo install cargo-watch
fi
# Lancer cargo watch avec hot reload
echo -e "${GREEN}🚀 Démarrage du programme avec hot reload${NC}"
echo -e "${YELLOW}Le programme redémarrera automatiquement lors de modifications${NC}"
echo ""
cargo watch -x run

72
src/docker/bollard.rs Normal file
View File

@@ -0,0 +1,72 @@
use bollard::{Docker, query_parameters::ListServicesOptions};
use std::sync::Arc;
use tokio::sync::Mutex;
pub struct BollardAdapter {
client: Arc<Mutex<Option<Docker>>>,
}
impl BollardAdapter {
pub fn new() -> Self {
Self {
client: Arc::new(Mutex::new(None)),
}
}
}
impl crate::docker::DockerAdapter for BollardAdapter {
async fn connect_docker(&self, docker_host: Option<String>) -> anyhow::Result<()> {
let docker = match docker_host {
Some(host) => Docker::connect_with_http(&host, 60, bollard::API_DEFAULT_VERSION)?,
None => Docker::connect_with_socket_defaults()?,
};
docker.ping().await?;
let mut client = self.client.lock().await;
*client = Some(docker);
Ok(())
}
async fn version(&self) -> anyhow::Result<String> {
let client = self.client.lock().await;
if let Some(docker) = &*client {
let version_info = docker.version().await?;
let version = version_info.version;
version.ok_or_else(|| anyhow::anyhow!("Version not available"))
} else {
Err(anyhow::anyhow!("Docker client is not connected"))
}
}
async fn find_enabled_services(&self) -> anyhow::Result<Vec<String>> {
let client = self.client.lock().await;
let docker = client.as_ref().ok_or_else(|| anyhow::anyhow!("Docker client is not connected"))?;
let services = docker.list_services(None::<ListServicesOptions>).await?;
let mut enabled_services = Vec::new();
for service in services {
if let Some(spec) = service.spec {
let enabled = spec.labels
.map(|hash| hash.contains_key("beekeper.enable"))
.is_some();
if enabled && let Some(name) = spec.name {
enabled_services.push(name);
}
}
}
Ok(enabled_services)
}
}
impl Default for BollardAdapter {
fn default() -> Self {
Self::new()
}
}

31
src/docker/mod.rs Normal file
View File

@@ -0,0 +1,31 @@
mod bollard;
pub use bollard::BollardAdapter;
pub struct Docker<T> where T: DockerAdapter {
adapter: T,
}
impl<T> Docker<T> where T: DockerAdapter {
pub fn new(adapter: T) -> Self {
Self { adapter }
}
pub async fn connect_docker(&self, docker_host: Option<String>) -> anyhow::Result<()> {
self.adapter.connect_docker(docker_host).await
}
pub async fn version(&self) -> anyhow::Result<String> {
self.adapter.version().await
}
pub async fn find_enabled_services(&self) -> anyhow::Result<Vec<String>> {
self.adapter.find_enabled_services().await
}
}
pub trait DockerAdapter {
async fn connect_docker(&self, docker_host: Option<String>) -> anyhow::Result<()>;
async fn version(&self) -> anyhow::Result<String>;
async fn find_enabled_services(&self) -> anyhow::Result<Vec<String>>;
}

View File

@@ -1,19 +1,36 @@
use bollard::Docker;
mod docker;
use docker::{BollardAdapter, Docker};
struct AppEnvVars {
backup_folder: String,
docker_host: Option<String>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let backup_root_folder = std::env::var("BACKUP_FOLDER")?;
let backup_root_path = std::path::Path::new(&backup_root_folder);
let envs = load_envs()?;
let folders = list_folders(backup_root_path).await?;
let backup_root_path = std::path::Path::new(&envs.backup_folder);
let _folders = list_folders(backup_root_path).await?;
let docker_conn = Docker::connect_with_socket_defaults()?;
let docker_version = docker_conn.version().await?;
println!("Docker version: {:?}", docker_version);
let docker_conn = connect_docker(envs.docker_host).await?;
println!("Docker version: {}", docker_conn.version().await?);
let enabled_services = docker_conn.find_enabled_services().await?;
println!("Enabled services: {:?}", enabled_services);
Ok(())
}
async fn connect_docker(docker_host: Option<String>) -> anyhow::Result<Docker<BollardAdapter>> {
let adapter = BollardAdapter::new();
let docker = Docker::new(adapter);
docker.connect_docker(docker_host).await?;
Ok(docker)
}
async fn list_folders(path: &std::path::Path) -> anyhow::Result<Vec<std::path::PathBuf>> {
let mut folders = Vec::new();
let mut dir_entries = tokio::fs::read_dir(path).await?;
@@ -26,3 +43,15 @@ async fn list_folders(path: &std::path::Path) -> anyhow::Result<Vec<std::path::P
Ok(folders)
}
fn load_envs() -> anyhow::Result<AppEnvVars> {
let backup_folder = std::env::var("BACKUP_FOLDER")?;
let docker_host = std::env::var("DOCKER_HOST").ok();
Ok(
AppEnvVars {
backup_folder,
docker_host,
}
)
}