A Rust application designed to fetch Git repositories on a schedule and keep each repository in a separate tarball archive.
Keep your Git repositories safe and backed up with ease!
You can also install it as an add-on for Home Assistant:
- Scheduled Git Repository Syncing: Automatically fetch and archive Git repositories based on a cron schedule
- Dual Storage Modes:
- Compact Mode: Repositories stored as compressed
.tar.gzarchives (space-efficient) - Non-Compact Mode: Repositories stored as regular folders (faster syncs, incremental updates)
- Compact Mode: Repositories stored as compressed
- Incremental Updates: Pull changes instead of re-cloning on subsequent syncs
- Repository Size Tracking: Track and display repository sizes (archive or cumulative folder size)
- REST API: Actix-web based REST API for managing repositories and credentials
- JWT Authentication: Secure API endpoints with JWT token-based authentication
- Credential Management: Store and manage Git credentials (username/password, SSH keys with encryption)
- Error Webhooks: Configure webhook URLs to receive notifications when sync errors occur
- YAML Configuration: Simple YAML-based configuration without a database
- Manual Sync: Trigger repository synchronization manually via API
- Rust 1.70 or higher
- Git 2.0 or higher
git clone https://github.com/j0rsa/gitsafe.git
cd gitsafe
cargo build --releaseThe binary will be available at target/release/gitsafe.
This project includes pre-commit hooks that automatically run formatting, checks, and linting before each commit. To install them:
./scripts/setup-hooks.shThe pre-commit hooks will automatically run:
cargo test --all- Run all testscargo check --all- Check compilationcargo clippy --all -- -D warnings- Lint code with clippycargo fmt --all -- --check- Check code formatting
The hooks are stored in .githooks/ and configured via git's core.hooksPath setting. To uninstall, run:
git config --unset core.hooksPathCreate a config.yaml file in the same directory as the binary. You can use config.yaml.example as a template:
cp config.yaml.example config.yamlserver:
host: "127.0.0.1"
port: 8080
jwt_secret: "your-secret-key"
encryption_key: "your-encryption-key-for-ssh-keys"
# Optional: List of webhook URLs to notify when sync errors occur
error_webhooks:
- "https://example.com/webhook"
- "https://another-service.com/notify"
storage:
archive_dir: "./archives"
# If true, repositories are stored as compressed tarballs (.tar.gz)
# If false, repositories are stored as regular folders
compact: true
scheduler:
# Cron format: "sec min hour day_of_month month day_of_week"
cron_expression: "0 0 * * * *" # Every hour
repositories: []
credentials: {}
users:
- username: "admin"
password_hash: "$2b$12$..." # bcrypt hashImportant: Change the default admin password before running in production!
./target/release/gitsafeOr with cargo:
cargo run --releaseThe server will start on the configured host and port (default: http://127.0.0.1:8080).
make runThe server will start on the configured host and port (default: http://127.0.0.1:8080).
brew install caddy
caddy run --config - --adapter caddyfile <<EOF
:8081 {
handle_path /example/one/* {
reverse_proxy localhost:8080
}
}
EOFThe server will be available at http://localhost:8081/example/one.
Login
curl -X POST http://127.0.0.1:8080/api/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin"}'Response:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}List Repositories
curl -X GET http://127.0.0.1:8080/api/repositories \
-H "Authorization: Bearer YOUR_TOKEN"Add Repository
curl -X POST http://127.0.0.1:8080/api/repositories \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://github.com/user/repo.git",
"credential_id": null
}'Delete Repository
curl -X DELETE http://127.0.0.1:8080/api/repositories/{id} \
-H "Authorization: Bearer YOUR_TOKEN"Manually Sync Repository
curl -X POST http://127.0.0.1:8080/api/sync \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"repository_id": "REPO_ID"}'List Credentials
curl -X GET http://127.0.0.1:8080/api/credentials \
-H "Authorization: Bearer YOUR_TOKEN"Add Credential
curl -X POST http://127.0.0.1:8080/api/credentials \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "git_user",
"password": "git_password",
"ssh_key": null
}'For SSH key authentication:
curl -X POST http://127.0.0.1:8080/api/credentials \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "git",
"password": "",
"ssh_key": "/path/to/private/key"
}'Delete Credential
curl -X DELETE http://127.0.0.1:8080/api/credentials/{id} \
-H "Authorization: Bearer YOUR_TOKEN"curl http://127.0.0.1:8080/healthThe scheduler runs based on the cron expression defined in config.yaml. The default configuration syncs all enabled repositories every hour.
The cron expression follows the format: sec min hour day_of_month month day_of_week
Examples:
0 0 * * * *- Every hour at minute 00 */30 * * * *- Every 30 minutes0 0 */6 * * *- Every 6 hours0 0 2 * * *- Every day at 2:00 AM
GitSafe supports two storage modes configured via storage.compact:
Repositories are stored as compressed .tar.gz archives. On each sync:
- Existing archive is unpacked (if present)
- Changes are pulled from remote
- New archive is created
- Temporary files are cleaned up
Archive naming: {domain}_{user}_{repo}.tar.gz
- Example:
github_com-example-repo1.tar.gz
Repositories are stored as regular folders. On each sync:
- Repository is cloned (if new) or updated via pull (if exists)
- Cumulative folder size is calculated
Folder naming: {domain}_{user}_{repo}
- Example:
github_com-example-repo1
Repository names are automatically generated from URLs to prevent collisions:
- Domain dots are replaced with underscores:
github.com→github_com - Path segments are joined with dashes:
example/repo1→example-repo1 .gitsuffix is automatically removed- Example:
https://github.com/example/repo1.git→github_com-example-repo1
Configure webhook URLs in server.error_webhooks to receive notifications when repository sync errors occur. Each webhook receives a POST request with the following JSON payload:
{
"time": "2024-01-01T12:00:00Z",
"repo": {
"id": "repo-123",
"url": "https://github.com/user/repo.git",
"enabled": true
},
"operation": "sync",
"credential_id": "cred-456",
"error_message": "Failed to clone repository: ..."
}Webhook calls are:
- Non-blocking: Sent asynchronously without affecting sync operations
- Fault-tolerant: Failures are logged but don't interrupt the main flow
- Timeout-protected: 10-second timeout per webhook
- Change Default Credentials: The default admin password is
admin. Change it immediately in production. - JWT Secret: Use a strong, random secret for JWT token generation.
- Encryption Key: Use a strong, random key for SSH key encryption (different from JWT secret).
- HTTPS: Use a reverse proxy (nginx, caddy) to enable HTTPS in production.
- SSH Key Encryption: SSH keys are encrypted using AES-256-GCM before storage.
- File Permissions: Set restrictive permissions on
config.yaml:chmod 600 config.yaml
- actix-web: Web framework
- git2: Git operations
- auth-git2: Git authentication helpers
- tokio-cron-scheduler: Scheduled task execution
- serde_yaml_ng: YAML configuration parsing (maintained fork of serde_yaml)
- jsonwebtoken: JWT authentication
- bcrypt: Password hashing
- tar & flate2: Archive creation and compression
- reqwest: HTTP client for webhook notifications
- aes-gcm: AES-256-GCM encryption for SSH keys
- chrono: Date and time handling
This project uses GitHub Actions for continuous integration and deployment:
- Lint: Runs
cargo fmtandcargo clippyon every PR (formatting is checked first for fast feedback) - Build: Compiles the project in release mode
- Test: Runs all unit and integration tests
- Security Audit: Checks dependencies for known vulnerabilities using
cargo audit - Docker: Uses an optimized multi-stage build process:
- Binary Build: Compiles binaries for amd64 and arm64 in parallel using matrix builds
- Docker Build: Creates architecture-specific images using pre-built binaries (fast, no QEMU emulation)
- Manifest Creation: Combines images into a multi-arch manifest on main branch
- Images are built on PRs for verification but only pushed to GitHub Container Registry (
ghcr.io) on main branch
Docker images are:
- Built on PRs: Verified for correctness but not pushed to the registry
- Built and pushed on main branch: Tagged with branch name and commit SHA
- Multi-architecture: Supports both
linux/amd64andlinux/arm64(aarch64) platforms - Optimized build: Binaries are built natively for each architecture in parallel for faster CI times
Images pushed to GitHub Container Registry are tagged with:
mainormaster: Branch reference tagsha-<commit>: Commit SHA tag
To run the Docker image from main:
docker run -d -p 8080:8080 \
-v $(pwd)/config.yaml:/app/config.yaml \
-v $(pwd)/archives:/app/archives \
ghcr.io/j0rsa/gitsafe:mainDocker will automatically pull the correct image for your platform (amd64 or arm64).
The CI pipeline uses the built-in GITHUB_TOKEN for pushing Docker images to GitHub Container Registry. No additional secrets need to be configured.
See LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.

