Compare commits

..

13 Commits

Author SHA1 Message Date
John Lancaster
e6f5ee38e5 added crond command for reference 2024-06-15 15:11:26 -05:00
John Lancaster
cf63a16f6b readme update 2024-06-15 15:11:26 -05:00
John Lancaster
97757bff44 readme update 2024-06-15 15:11:26 -05:00
John Lancaster
a6c089a191 did away with the inotifyd nonsense 2024-06-15 15:11:26 -05:00
John Lancaster
7123ccada7 added notes for the key 2024-06-15 15:11:26 -05:00
John Lancaster
4c8b57dd16 initial move to resticprofile 2024-06-15 15:11:26 -05:00
John Lancaster
326d2c0910 added to gitignore 2024-06-15 15:08:05 -05:00
John Lancaster
08e02d12db improved logic in forget 2024-06-03 21:10:21 -05:00
John Lancaster
80842bc09c added prune before getting the size 2024-06-03 21:04:30 -05:00
John Lancaster
4e7fa8c87a improved printout 2024-06-03 20:52:52 -05:00
John Lancaster
2e2a777b11 improved regex for printout 2024-06-03 20:50:02 -05:00
John Lancaster
3957e0c869 updated comment 2024-06-03 20:27:44 -05:00
John Lancaster
cae3f2bbf4 type hint 2024-05-28 18:33:12 -05:00
8 changed files with 127 additions and 22 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
__pycache__ __pycache__
*.egg-info *.egg-info
build/ build/
.env
key

View File

@@ -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
From a parent repo, add this one as a submodule:
```shell
git submodule add https://gitea.john-stream.com/john/restic-scripts
```
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 | | Env Variable | Description |
| ------------------- | ------------------------------------------------------------------------------------------ | | ------------------- | --------------------------------------------------------------------------------------- |
| `HOSTNAME` | Network hostname of where the backup is running |
| `BACKUP_DIR` | Directory to back up | | `BACKUP_DIR` | Directory to back up |
| `RESTIC_REPOSITORY` | Directory for the restic repository. This is usually on a mount point made from Proxmox | | `RESTIC_REPOSITORY` | Directory for the restic repository. This is usually on a mount point made from Proxmox |
| `RESTIC_PASSWORD` | Password for the restic repository | | `TZ` | Set to modify the timezone shown in the scheduler |
| `LOKI_URL` | Push URL for Loki. Should include the port and end with something like `/loki/api/v1/push` |
### 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
View 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
View 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
View File

@@ -0,0 +1,9 @@
#!/bin/sh
set -e
resticprofile unschedule > /dev/null
resticprofile schedule --all
echo "Scheduled all restic profiles"

View File

@@ -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]

View File

@@ -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):
@@ -85,8 +86,12 @@ def main(loki_url: str, dry_run: bool, **kwargs):
except Exception: except Exception:
console.print_exception() console.print_exception()
else: else:
prune(loki_url, dry_run)
if not dry_run:
snapshots.snapshot(loki_url) snapshots.snapshot(loki_url)
size.get_size(loki_url) size.get_size(loki_url)
else:
logger.debug('Skipping getting size because of dry run')
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -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')