Compare commits
14 Commits
9127b34739
...
resticprof
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6f5ee38e5 | ||
|
|
cf63a16f6b | ||
|
|
97757bff44 | ||
|
|
a6c089a191 | ||
|
|
7123ccada7 | ||
|
|
4c8b57dd16 | ||
|
|
326d2c0910 | ||
|
|
08e02d12db | ||
|
|
80842bc09c | ||
|
|
4e7fa8c87a | ||
|
|
2e2a777b11 | ||
|
|
3957e0c869 | ||
|
|
cae3f2bbf4 | ||
|
|
5b8221ef77 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
*.egg-info
|
*.egg-info
|
||||||
build/
|
build/
|
||||||
|
|
||||||
|
.env
|
||||||
|
key
|
||||||
73
README.md
73
README.md
@@ -2,23 +2,52 @@
|
|||||||
|
|
||||||
Purpose:
|
Purpose:
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This behvior does not work on the `resticprofile` branch
|
||||||
|
|
||||||
- Wrap `restic` with the ability to start/stop Docker containers
|
- Wrap `restic` with the ability to start/stop Docker containers
|
||||||
- Send updates to Loki server
|
- Send updates to Loki server
|
||||||
|
|
||||||
## Environment Variables
|
## Installation
|
||||||
|
|
||||||
Recommended to put these in the relevant `~/.bashrc` file
|
### Include in Repo
|
||||||
|
|
||||||
| Env Variable | Description |
|
From a parent repo, add this one as a submodule:
|
||||||
| ------------------- | ------------------------------------------------------------------------------------------ |
|
|
||||||
| `HOSTNAME` | Network hostname of where the backup is running |
|
```shell
|
||||||
| `BACKUP_DIR` | Directory to back up |
|
git submodule add https://gitea.john-stream.com/john/restic-scripts
|
||||||
| `RESTIC_REPOSITORY` | Directory for the restic repository. This is usually on a mount point made from Proxmox |
|
```
|
||||||
| `RESTIC_PASSWORD` | Password for the restic repository |
|
|
||||||
| `LOKI_URL` | Push URL for Loki. Should include the port and end with something like `/loki/api/v1/push` |
|
Then add this to the `docker-compose.yml` file of the parent.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
include:
|
||||||
|
- restic-scripts/docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Put these in `./restic-scripts/.env`, which is on the `.gitignore` list.
|
||||||
|
|
||||||
|
| Env Variable | Description |
|
||||||
|
| ------------------- | --------------------------------------------------------------------------------------- |
|
||||||
|
| `BACKUP_DIR` | Directory to back up |
|
||||||
|
| `RESTIC_REPOSITORY` | Directory for the restic repository. This is usually on a mount point made from Proxmox |
|
||||||
|
| `TZ` | Set to modify the timezone shown in the scheduler |
|
||||||
|
|
||||||
|
### Key file
|
||||||
|
|
||||||
|
The password needs to be stored in `./restic-scripts/key`. Make sure it has the right (secure) permissions.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo chown root:root ./restic-scripts/key && sudo chmod 600 ./restic-scripts/key
|
||||||
|
```
|
||||||
|
|
||||||
## Loki Updates
|
## Loki Updates
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This doesn't currently work on the `resticprofile` branch
|
||||||
|
|
||||||
Updates sent to Loki will have the following labels:
|
Updates sent to Loki will have the following labels:
|
||||||
|
|
||||||
| Label | Description |
|
| Label | Description |
|
||||||
@@ -28,16 +57,28 @@ Updates sent to Loki will have the following labels:
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```shell
|
Check snapshots
|
||||||
python -m restic.snapshots
|
|
||||||
```
|
|
||||||
|
|
||||||
To stop docker containers before the backup and start them again afterwards, use the `--project` and `--services` flags.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python -m restic.backup --tag manual --project joplin --services app,db
|
docker compose exec backup resticprofile snapshots
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Dry-run a backup
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python -m restic.prune
|
docker compose exec backup resticprofile --dry-run backup
|
||||||
|
```
|
||||||
|
|
||||||
|
Check crontab entry in container
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker compose exec backup cat /var/spool/cron/crontabs/root
|
||||||
|
```
|
||||||
|
|
||||||
|
### Crond
|
||||||
|
|
||||||
|
Crond command when scheduled using `resticprofile schedule --all`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /resticprofile && /usr/bin/resticprofile --no-ansi --config /etc/resticprofile/profiles.yaml --name default backup
|
||||||
```
|
```
|
||||||
|
|||||||
16
docker-compose.yml
Normal file
16
docker-compose.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
services:
|
||||||
|
backup:
|
||||||
|
image: creativeprojects/resticprofile
|
||||||
|
entrypoint: '/bin/sh'
|
||||||
|
command:
|
||||||
|
- -c
|
||||||
|
- 'resticprofile-schedule.sh && crond -f'
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
hostname: ${HOSTNAME}
|
||||||
|
volumes:
|
||||||
|
- ./restic-profile.yaml:/etc/resticprofile/profiles.yaml:ro
|
||||||
|
- ./resticprofile-schedule.sh:/usr/local/bin/resticprofile-schedule.sh:ro
|
||||||
|
- ./key:/etc/resticprofile/key:ro
|
||||||
|
- ${BACKUP_DIR}:${BACKUP_DIR}:ro
|
||||||
|
- ${RESTIC_REPOSITORY}:${RESTIC_REPOSITORY}:rw
|
||||||
29
restic-profile.yaml
Normal file
29
restic-profile.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
global:
|
||||||
|
scheduler: crond
|
||||||
|
|
||||||
|
default:
|
||||||
|
base-dir: ${BACKUP_DIR}
|
||||||
|
repository: local:${RESTIC_REPOSITORY}
|
||||||
|
password-file: key
|
||||||
|
initialize: true
|
||||||
|
backup:
|
||||||
|
source: ./
|
||||||
|
exclude-caches: true
|
||||||
|
one-file-system: true
|
||||||
|
schedule: "*:00"
|
||||||
|
schedule-permission: system
|
||||||
|
check-after: true
|
||||||
|
tag:
|
||||||
|
- resticprofile
|
||||||
|
|
||||||
|
retention:
|
||||||
|
after-backup: true
|
||||||
|
before-backup: false
|
||||||
|
prune: true
|
||||||
|
tag:
|
||||||
|
- resticprofile
|
||||||
|
keep-within: 3h
|
||||||
|
keep-hourly: 72
|
||||||
|
keep-daily: 14
|
||||||
|
keep-weekly: 8
|
||||||
|
keep-monthly: 6
|
||||||
9
resticprofile-schedule.sh
Executable file
9
resticprofile-schedule.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
resticprofile unschedule > /dev/null
|
||||||
|
|
||||||
|
resticprofile schedule --all
|
||||||
|
|
||||||
|
echo "Scheduled all restic profiles"
|
||||||
@@ -18,7 +18,7 @@ def manage_containers(project: str, services: list[str]):
|
|||||||
for c in client.containers.list()
|
for c in client.containers.list()
|
||||||
if c.labels['com.docker.compose.project'] == project
|
if c.labels['com.docker.compose.project'] == project
|
||||||
)
|
)
|
||||||
service_dict = {
|
service_dict: dict[str, Container] = {
|
||||||
c.labels['com.docker.compose.service']: c for c in project_containers
|
c.labels['com.docker.compose.service']: c for c in project_containers
|
||||||
}
|
}
|
||||||
containers: list[Container] = [service_dict[s] for s in services]
|
containers: list[Container] = [service_dict[s] for s in services]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from rich.logging import RichHandler
|
|||||||
from restic import size, snapshots
|
from restic import size, snapshots
|
||||||
from restic.console import console, logger
|
from restic.console import console, logger
|
||||||
from restic.loki import send_to_loki
|
from restic.loki import send_to_loki
|
||||||
|
from restic.prune import prune
|
||||||
|
|
||||||
|
|
||||||
class KeepStrategy(BaseModel):
|
class KeepStrategy(BaseModel):
|
||||||
@@ -43,7 +44,8 @@ def forget(loki_url: str = None, dry_run: bool = False, **kwargs):
|
|||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
try:
|
try:
|
||||||
line = result.stdout
|
line = result.stdout
|
||||||
data = json.loads(line)
|
# data = json.loads(line)
|
||||||
|
json.loads(line)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
logger.error('Error decoding JSON')
|
logger.error('Error decoding JSON')
|
||||||
logger.error(line)
|
logger.error(line)
|
||||||
@@ -53,7 +55,7 @@ def forget(loki_url: str = None, dry_run: bool = False, **kwargs):
|
|||||||
finally:
|
finally:
|
||||||
logger.debug('Done')
|
logger.debug('Done')
|
||||||
else:
|
else:
|
||||||
logger.error(f'Error running command')
|
logger.error('Error running command')
|
||||||
logger.error(result.stderr)
|
logger.error(result.stderr)
|
||||||
|
|
||||||
|
|
||||||
@@ -84,8 +86,12 @@ def main(loki_url: str, dry_run: bool, **kwargs):
|
|||||||
except Exception:
|
except Exception:
|
||||||
console.print_exception()
|
console.print_exception()
|
||||||
else:
|
else:
|
||||||
snapshots.snapshot(loki_url)
|
prune(loki_url, dry_run)
|
||||||
size.get_size(loki_url)
|
if not dry_run:
|
||||||
|
snapshots.snapshot(loki_url)
|
||||||
|
size.get_size(loki_url)
|
||||||
|
else:
|
||||||
|
logger.debug('Skipping getting size because of dry run')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from restic.console import console, logger
|
|||||||
from restic.loki import send_to_loki
|
from restic.loki import send_to_loki
|
||||||
|
|
||||||
field_regex = re.compile(
|
field_regex = re.compile(
|
||||||
r'^(?P<name>[\w ]+):\s+(?P<blobs>\d+) blobs \/ (?P<size>\d+(\.\d+)? (M|G)iB)', re.MULTILINE
|
r'^(?P<name>[\w ]+):\s+(?P<blobs>\d+) blobs \/ (?P<size>\d+(\.\d+)? (?:Mi|Gi)?B)', re.MULTILINE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -23,6 +23,8 @@ def convert_size(size_str: str) -> int:
|
|||||||
scale = 8589934592
|
scale = 8589934592
|
||||||
elif size_str.endswith('MiB'):
|
elif size_str.endswith('MiB'):
|
||||||
scale = 8388608
|
scale = 8388608
|
||||||
|
else:
|
||||||
|
scale = 1
|
||||||
return int(round(base_size * scale))
|
return int(round(base_size * scale))
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +50,7 @@ def prune(loki_url: str = None, dry_run: bool = False):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
d = {f['name']: {'blobs': f['blobs'], 'size': f['size']} for f in field_gen(result.stdout)}
|
d = {f['name']: {'blobs': f['blobs'], 'size': f['size']} for f in field_gen(result.stdout)}
|
||||||
console.print(d)
|
logger.debug(json.dumps(d, indent=4))
|
||||||
|
|
||||||
if loki_url is not None and not dry_run:
|
if loki_url is not None and not dry_run:
|
||||||
send_to_loki(loki_url, line=json.dumps(d), backup='prune')
|
send_to_loki(loki_url, line=json.dumps(d), backup='prune')
|
||||||
|
|||||||
Reference in New Issue
Block a user