first commit
This commit is contained in:
20
.devcontainer/Dockerfile
Normal file
20
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM docker:dind
|
||||||
|
|
||||||
|
ARG USERNAME=vscode
|
||||||
|
ARG USER_UID=1000
|
||||||
|
ARG USER_GID=$USER_UID
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
curl \
|
||||||
|
bash \
|
||||||
|
build-base \
|
||||||
|
openssl-dev \
|
||||||
|
&& addgroup -g ${USER_GID} ${USERNAME} \
|
||||||
|
&& adduser -D -u ${USER_UID} -G ${USERNAME} -s /bin/bash ${USERNAME} \
|
||||||
|
&& addgroup ${USERNAME} docker
|
||||||
|
|
||||||
|
USER ${USERNAME}
|
||||||
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
|
|
||||||
|
USER root
|
||||||
|
CMD ["/usr/local/bin/dockerd-entrypoint.sh"]
|
||||||
21
.devcontainer/devcontainer.json
Normal file
21
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "Docker DinD + Rust",
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile"
|
||||||
|
},
|
||||||
|
"runArgs": [
|
||||||
|
"--privileged"
|
||||||
|
],
|
||||||
|
"overrideCommand": false,
|
||||||
|
"remoteUser": "vscode",
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"rust-lang.rust-analyzer",
|
||||||
|
"fill-labs.dependi",
|
||||||
|
"tamasfe.even-better-toml"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer.check.command": "clippy"
|
||||||
|
}
|
||||||
1578
Cargo.lock
generated
Normal file
1578
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "beekeper"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
async-compression = { version = "0.4", features = ["tokio", "xz"] }
|
||||||
|
bollard = "0.19"
|
||||||
|
anyhow = "1.0"
|
||||||
|
tokio-tar = "0.3"
|
||||||
170
README.md
Normal file
170
README.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Beekeeper
|
||||||
|
|
||||||
|
Automated Docker volume backup service with compression and FTP upload.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Beekeeper is a Rust-based service that automatically backs up Docker volumes on a scheduled basis. It safely stops containers, compresses their volumes, restarts the containers, and uploads the backups to an FTP server.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Automated Backups**: Schedule backups using cron expressions
|
||||||
|
- **Safe Container Management**: Automatically stops and restarts containers during backup
|
||||||
|
- **Efficient Compression**: Creates tar.xz archives for optimal storage
|
||||||
|
- **FTP Upload**: Automatically uploads backups to a remote FTP server
|
||||||
|
- **Label-Based Selection**: Uses Docker labels to identify containers to backup
|
||||||
|
- **Volume Mounting**: Shares the same volumes as the target containers
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Discovery**: Beekeeper scans for Docker containers with the backup label
|
||||||
|
2. **Preparation**: Stops the identified containers gracefully
|
||||||
|
3. **Compression**: Creates a tar.xz archive of the volume contents
|
||||||
|
4. **Restoration**: Restarts the stopped containers
|
||||||
|
5. **Upload**: Transfers the backup archive to the configured FTP server
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description | Required | Example |
|
||||||
|
|----------|-------------|----------|---------|
|
||||||
|
| `BACKUP_FOLDER` | Path to the folder containing volumes to backup | Yes | `/data/volumes` |
|
||||||
|
| `BACKUP_CRON` | Cron expression for backup schedule | Yes | `0 2 * * *` |
|
||||||
|
| `FTP_HOST` | FTP server hostname | Yes | `ftp.example.com` |
|
||||||
|
| `FTP_PORT` | FTP server port | No | `21` (default) |
|
||||||
|
| `FTP_USER` | FTP username | Yes | `backup_user` |
|
||||||
|
| `FTP_PASSWORD` | FTP password | Yes | `secret123` |
|
||||||
|
| `FTP_PATH` | Remote path on FTP server | No | `/backups` |
|
||||||
|
|
||||||
|
### Docker Labels
|
||||||
|
|
||||||
|
Add the following label to containers you want to backup:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
labels:
|
||||||
|
- "beekeeper.backup=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Docker Compose Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
beekeeper:
|
||||||
|
image: beekeeper:latest
|
||||||
|
container_name: beekeeper
|
||||||
|
privileged: true
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- data-volume:/data/volumes
|
||||||
|
environment:
|
||||||
|
- BACKUP_FOLDER=/data/volumes
|
||||||
|
- BACKUP_CRON=0 2 * * *
|
||||||
|
- FTP_HOST=ftp.example.com
|
||||||
|
- FTP_USER=backup_user
|
||||||
|
- FTP_PASSWORD=secret123
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Example service to backup
|
||||||
|
myapp:
|
||||||
|
image: myapp:latest
|
||||||
|
volumes:
|
||||||
|
- data-volume:/app/data
|
||||||
|
labels:
|
||||||
|
- "beekeeper.backup=true"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
data-volume:
|
||||||
|
```
|
||||||
|
|
||||||
|
### Standalone Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name beekeeper \
|
||||||
|
--privileged \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-v data-volume:/data/volumes \
|
||||||
|
-e BACKUP_FOLDER=/data/volumes \
|
||||||
|
-e BACKUP_CRON="0 2 * * *" \
|
||||||
|
-e FTP_HOST=ftp.example.com \
|
||||||
|
-e FTP_USER=backup_user \
|
||||||
|
-e FTP_PASSWORD=secret123 \
|
||||||
|
beekeeper:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cron Expression Examples
|
||||||
|
|
||||||
|
| Expression | Description |
|
||||||
|
|------------|-------------|
|
||||||
|
| `0 2 * * *` | Daily at 2:00 AM |
|
||||||
|
| `0 */6 * * *` | Every 6 hours |
|
||||||
|
| `0 0 * * 0` | Weekly on Sunday at midnight |
|
||||||
|
| `0 3 1 * *` | Monthly on the 1st at 3:00 AM |
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Rust 1.70+
|
||||||
|
- Docker 20.10+
|
||||||
|
- Docker Compose (optional)
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export BACKUP_FOLDER=/tmp/backups
|
||||||
|
export BACKUP_CRON="*/5 * * * *"
|
||||||
|
export FTP_HOST=localhost
|
||||||
|
export FTP_USER=test
|
||||||
|
export FTP_PASSWORD=test
|
||||||
|
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- **Privileged Mode**: Beekeeper requires access to the Docker socket and privileged mode to manage containers
|
||||||
|
- **Credentials**: Store FTP credentials securely (use Docker secrets in production)
|
||||||
|
- **Network**: Ensure FTP traffic is encrypted (use FTPS if possible)
|
||||||
|
- **Permissions**: The backup folder should have appropriate permissions
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- Currently supports FTP only (SFTP/S3 support planned)
|
||||||
|
- Containers are stopped during backup (downtime expected)
|
||||||
|
- No incremental backup support yet
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
- [ ] SFTP and S3 backend support
|
||||||
|
- [ ] Incremental backups
|
||||||
|
- [ ] Backup retention policies
|
||||||
|
- [ ] Backup verification
|
||||||
|
- [ ] Prometheus metrics
|
||||||
|
- [ ] Web UI for monitoring
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please open an issue or submit a pull request.
|
||||||
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
caddy:
|
||||||
|
image: caddy:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- caddy_data:/data
|
||||||
|
- caddy_config:/config
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
caddy_data:
|
||||||
|
caddy_config:
|
||||||
28
src/main.rs
Normal file
28
src/main.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use bollard::Docker;
|
||||||
|
|
||||||
|
#[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 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);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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?;
|
||||||
|
while let Some(entry) = dir_entries.next_entry().await? {
|
||||||
|
let file_type = entry.file_type().await?;
|
||||||
|
if file_type.is_dir() {
|
||||||
|
folders.push(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(folders)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user