REST API server for restic backup software with pluggable storage backends
Project description
pyrestserver
REST API server for restic backup software with pluggable storage backends.
This package provides a Python implementation of the restic REST API with support for multiple storage backends, including local filesystem and Drime Cloud storage.
Features
- REST API compatible with restic - Drop-in replacement for rest-server
- Upload verification - SHA-256 hash verification for data integrity (enabled by default)
- Pluggable storage backends - Support for local filesystem and cloud storage (Drime)
- Authentication - htpasswd-based authentication support
- Append-only mode - Prevent deletion of existing backups
- TLS support - Secure communication with TLS/SSL
- Prometheus metrics - Built-in metrics endpoint (basic implementation)
Installation
Basic Installation (Local filesystem only)
pip install pyrestserver
With Drime Cloud Backend
pip install pyrestserver[drime]
Development Installation
git clone <repository-url>
cd pyrestserver
pip install -e ".[dev,drime]"
Quick Start
Local Filesystem Backend
Start a REST server with local filesystem storage:
# Start server with default settings (no auth, data in /tmp/restic)
pyrestserver --path /tmp/restic --no-auth
# Start with authentication
pyrestserver --path /srv/restic --htpasswd-file /etc/restic/htpasswd
# Start with TLS
pyrestserver --path /srv/restic \
--tls \
--tls-cert /etc/restic/cert.pem \
--tls-key /etc/restic/key.pem
# Start in append-only mode (prevents deletion)
pyrestserver --path /srv/restic --append-only
Drime Cloud Backend
Start a REST server with Drime Cloud storage:
# Set environment variables for Drime authentication
export DRIME_USERNAME="your-username"
export DRIME_PASSWORD="your-password"
# Start server with Drime backend
pyrestserver --backend drime \
--workspace-id 0 \
--no-auth
Using with restic
Once the server is running, configure restic to use it:
# Initialize a new repository
restic -r rest:http://localhost:8000/myrepo init
# Create a backup
restic -r rest:http://localhost:8000/myrepo backup /path/to/data
# List snapshots
restic -r rest:http://localhost:8000/myrepo snapshots
# Restore a backup
restic -r rest:http://localhost:8000/myrepo restore latest --target /restore/path
With authentication:
# Use --username and --password or set RESTIC_REST_USERNAME and RESTIC_REST_PASSWORD
export RESTIC_REST_USERNAME=myuser
export RESTIC_REST_PASSWORD=mypassword
restic -r rest:http://localhost:8000/myrepo snapshots
Command Line Interface
The pyrestserver command provides a CLI interface matching the original rest-server:
Usage: pyrestserver [OPTIONS]
Options:
--path PATH Data directory (default: /tmp/restic)
--listen ADDRESS Listen address (default: :8000)
--no-auth Disable authentication
--htpasswd-file PATH Path to htpasswd file
--tls Enable TLS
--tls-cert PATH Path to TLS certificate
--tls-key PATH Path to TLS private key
--append-only Enable append-only mode
--private-repos Enable private repositories mode
--no-verify-upload Disable upload integrity verification (not recommended)
--debug Enable debug logging
--prometheus Enable Prometheus metrics
--prometheus-no-auth Disable auth for metrics endpoint
--backend {local,drime} Storage backend (default: local)
--workspace-id INTEGER Drime workspace ID (default: 0)
--help Show this message and exit
Authentication
Create an htpasswd file for authentication:
# Using htpasswd (from apache2-utils)
htpasswd -c /etc/restic/htpasswd myuser
# Or use Python's htpasswd library
python -c "import bcrypt; print('myuser:' + bcrypt.hashpw(b'mypassword', bcrypt.gensalt()).decode())"
Upload Verification
By default, pyrestserver verifies the integrity of uploaded data blobs by checking that the SHA-256 hash of the uploaded content matches the hash specified in the URL. This provides protection against data corruption during transmission and storage.
How it works
- When restic uploads a data blob to
/data/ab/abc123..., the filename (abc123...) is the SHA-256 hash of the content - pyrestserver calculates the SHA-256 hash of the uploaded data and compares it with the filename
- If they don't match, the upload is rejected with HTTP 400 Bad Request
- Only
datablobs are verified (keys, locks, snapshots, and index files use different naming schemes)
Security Benefits
- Data integrity: Prevents corrupted uploads from being stored
- Early detection: Catches transmission errors before data is saved
- Compatibility: Matches the behavior of the Go-based rest-server
Disabling Verification (Not Recommended)
For low-power devices where SHA-256 hashing may be too CPU-intensive, you can disable verification:
# Disable upload verification (NOT recommended for production)
pyrestserver --path /srv/restic --no-verify-upload
⚠️ Warning: Only disable verification if you're running on a very low-power device (e.g., Raspberry Pi Zero) and understand the security implications. Modern CPUs handle SHA-256 hashing with minimal overhead.
Metrics
Upload verification failures are tracked in Prometheus metrics (when enabled) as
restic_upload_verification_failures_total.
Programmatic Usage
You can also use pyrestserver as a library in your Python code:
Basic WSGI Application
from pyrestserver import ResticRESTApp
from pyrestserver.providers.local import LocalStorageProvider
# Create a storage provider
provider = LocalStorageProvider(
base_path="/srv/restic",
readonly=False
)
# Create the WSGI application
app = ResticRESTApp(
provider=provider,
append_only=False,
auth_required=False
)
# Use with any WSGI server (gunicorn, waitress, etc.)
Custom Storage Backend
from pyrestserver import StorageProvider
class MyCustomProvider(StorageProvider):
"""Custom storage provider implementation."""
def repository_exists(self, repo_path: str) -> bool:
# Implementation
pass
def config_exists(self, repo_path: str) -> tuple[bool, int]:
# Implementation
pass
# Implement other required methods...
Using Drime Backend
from pydrime import DrimeClient
from pyrestserver import ResticRESTApp
from pyrestserver.providers.drime import DrimeStorageProvider
# Create Drime client
client = DrimeClient(
username="your-username",
password="your-password"
)
# Create Drime storage provider
provider = DrimeStorageProvider(
client=client,
workspace_id=0,
readonly=False
)
# Create the WSGI application
app = ResticRESTApp(
provider=provider,
append_only=False,
auth_required=False
)
Storage Backends
Local Filesystem
The local filesystem backend stores repository data in a directory structure:
/path/to/data/
├── repo1/
│ ├── config
│ ├── data/
│ │ ├── 00/
│ │ ├── 01/
│ │ └── ...
│ ├── index/
│ ├── keys/
│ ├── locks/
│ └── snapshots/
└── repo2/
└── ...
Files are created with permissions:
- Directories: 0700 (owner read/write/execute)
- Files: 0600 (owner read/write)
Drime Cloud
The Drime backend stores repository data in Drime Cloud storage with the same structure.
It uses the pydrime library to interact with the Drime API.
API Endpoints
The REST API provides the following endpoints (compatible with restic):
GET /{repo}/config- Get repository configurationPOST /{repo}/config- Create repository configurationHEAD /{repo}/config- Check if repository existsGET /{repo}/{type}- List blobs of a typeGET /{repo}/{type}/{name}- Get blob contentHEAD /{repo}/{type}/{name}- Check if blob existsPOST /{repo}/{type}/{name}- Upload blobDELETE /{repo}/{type}/{name}- Delete blob (not allowed in append-only mode)
Valid blob types: data, index, keys, locks, snapshots
Metrics
Basic Prometheus metrics are available at /metrics (when enabled with --prometheus):
restic_repo_read_total- Total repository read operationsrestic_repo_write_total- Total repository write operations- (More metrics to be implemented)
Development
Running Tests
pytest
Code Quality
# Run ruff linter
ruff check .
# Format code
ruff format .
Differences from rest-server
This implementation aims to be compatible with the Go-based rest-server, with the following differences:
- Storage backends: Pluggable architecture allows for cloud storage backends
- Upload verification: SHA-256 hash verification is implemented and enabled by default (matching rest-server v0.14.0+)
- Metrics: Basic Prometheus metrics (more comprehensive metrics planned)
- Private repos: Not fully implemented in v1
- Quotas: Not implemented in v1
- Performance: Python implementation may have different performance characteristics
Architecture
Storage Provider Interface
All storage backends implement the StorageProvider abstract base class:
class StorageProvider(ABC):
@abstractmethod
def repository_exists(self, repo_path: str) -> bool:
"""Check if a repository exists."""
@abstractmethod
def config_exists(self, repo_path: str) -> tuple[bool, int]:
"""Check if config exists and return (exists, size)."""
@abstractmethod
def create_repository(self, repo_path: str) -> bool:
"""Create repository folder structure."""
@abstractmethod
def get_config(self, repo_path: str) -> bytes | None:
"""Get repository config content."""
@abstractmethod
def save_config(self, repo_path: str, data: bytes) -> bool:
"""Save repository config."""
@abstractmethod
def list_blobs(self, repo_path: str, blob_type: str) -> list[dict] | None:
"""List blobs of a given type."""
@abstractmethod
def blob_exists(self, repo_path: str, blob_type: str, name: str) -> tuple[bool, int]:
"""Check if blob exists and return (exists, size)."""
@abstractmethod
def get_blob(self, repo_path: str, blob_type: str, name: str) -> bytes | None:
"""Get blob content."""
@abstractmethod
def save_blob(self, repo_path: str, blob_type: str, name: str, data: bytes) -> bool:
"""Save blob content."""
@abstractmethod
def delete_blob(self, repo_path: str, blob_type: str, name: str) -> bool:
"""Delete a blob."""
@abstractmethod
def is_readonly(self) -> bool:
"""Check if provider is read-only."""
License
MIT License - See LICENSE file for details
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Credits
- Based on the restic REST server
- Uses pydrime for Drime Cloud integration
Links
- restic - Fast, secure, efficient backup program
- rest-server - Original Go-based REST server
- Documentation: restic REST API
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyrestserver-0.1.1.tar.gz.
File metadata
- Download URL: pyrestserver-0.1.1.tar.gz
- Upload date:
- Size: 68.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed24fc09d8dbaec1fd811a0f65f4e726f4155b4d3ffcbc02aa0dff82b5caf1a7
|
|
| MD5 |
993833bb033b41b4f2b888a25667ac02
|
|
| BLAKE2b-256 |
1b7dba6f2bba16e918f8a9ab34121a138b837496c2419fa8d0f9f32e61ea2c20
|
File details
Details for the file pyrestserver-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pyrestserver-0.1.1-py3-none-any.whl
- Upload date:
- Size: 28.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08283da5ce8384bea57c318ab5d985dd4553f0dcfcb7a3b63f958d55f883f29f
|
|
| MD5 |
66cdb2a05c9c75a63f295fe1b79cb1f4
|
|
| BLAKE2b-256 |
e0adbbbe3b96e8ade7dec4b86855d9712f9784c6e717774d308024424018591e
|