Compare commits

..

12 Commits

Author SHA1 Message Date
root
0086878d57 added more .env stuff 2024-06-28 20:15:14 -05:00
root
2ed9f3345d added some dotenv stuff 2024-06-28 20:10:47 -05:00
root
50dd0df84f some error tolerance 2024-06-28 20:10:04 -05:00
John Lancaster
e17894e49c added docker sdk to dependencies 2024-06-15 15:49:34 -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
John Lancaster
5b8221ef77 fixes 2024-05-27 19:22:10 -05:00
9 changed files with 43 additions and 16 deletions

5
.gitignore vendored
View File

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

View File

@@ -7,11 +7,11 @@ Purpose:
## Environment Variables ## Environment Variables
Recommended to put these in the relevant `~/.bashrc` file Put these in a `.env` file in the directory that the backup will be run from.
| Env Variable | Description | | Env Variable | Description |
| ------------------- | ------------------------------------------------------------------------------------------ | |---------------------|--------------------------------------------------------------------------------------------|
| `HOSTNAME` | Network hostname of where the backup is running | | `HOSTNAME` | Network hostname of where the backup is running. Used to tag the backups in restic |
| `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 | | `RESTIC_PASSWORD` | Password for the restic repository |

View File

@@ -12,4 +12,4 @@ authors = [
license = { file = "LICENSE" } license = { file = "LICENSE" }
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = ["rich", "requests", "click"] dependencies = ["rich", "requests", "click", "docker", "python-dotenv"]

View File

@@ -2,4 +2,6 @@ uv
ruff ruff
rich rich
requests requests
click click
docker
python-dotenv

View File

@@ -81,6 +81,7 @@ def main(
logging.getLogger('urllib3.connectionpool').setLevel('WARNING') logging.getLogger('urllib3.connectionpool').setLevel('WARNING')
if project is not None and services is not None: if project is not None and services is not None:
logger.debug(f'Using project {project} and stopping services: {services}')
decorator = manage_containers(project=project, services=services.split(',')) decorator = manage_containers(project=project, services=services.split(','))
func = decorator(run) func = decorator(run)
else: else:
@@ -96,4 +97,10 @@ def main(
if __name__ == '__main__': if __name__ == '__main__':
from dotenv import load_dotenv
from pathlib import Path
dotenv_file = Path.cwd() / '.env'
print(dotenv_file)
load_dotenv(dotenv_path=dotenv_file)
main() main()

View File

@@ -15,11 +15,12 @@ def manage_containers(project: str, services: list[str]):
try: try:
project_containers = ( project_containers = (
c c
for c in client.containers.list() for c in client.containers.list(all=True)
if c.labels['com.docker.compose.project'] == project if c.labels.get('com.docker.compose.project', False)
) )
service_dict = { service_dict: dict[str, Container] = {
c.labels['com.docker.compose.service']: c for c in project_containers service: c for c in project_containers
if (service := c.labels.get('com.docker.compose.service', False))
} }
containers: list[Container] = [service_dict[s] for s in services] containers: list[Container] = [service_dict[s] for s in services]
except Exception as e: except Exception as e:

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):
@@ -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,9 +86,16 @@ 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__':
from dotenv import load_dotenv
load_dotenv()
main() 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')

View File

@@ -35,4 +35,7 @@ def main(loki_url: str = None):
if __name__ == '__main__': if __name__ == '__main__':
from dotenv import load_dotenv
load_dotenv()
main() main()