skilleter-thingy 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of skilleter-thingy might be problematic. Click here for more details.
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/PKG-INFO +46 -1
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/README.md +45 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/pyproject.toml +10 -1
- skilleter_thingy-0.2.1/skilleter_thingy/borger.py +273 -0
- skilleter_thingy-0.2.1/skilleter_thingy/diskspacecheck.py +67 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/ggit.py +1 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/ggrep.py +1 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_br.py +7 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_ca.py +8 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_cleanup.py +11 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_co.py +8 -3
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_common.py +12 -4
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_hold.py +9 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_mr.py +11 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_parent.py +23 -18
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_retag.py +10 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_review.py +1 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_update.py +1 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/git_wt.py +2 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/gitprompt.py +1 -0
- skilleter_thingy-0.2.1/skilleter_thingy/localphotosync.py +201 -0
- skilleter_thingy-0.2.1/skilleter_thingy/moviemover.py +133 -0
- skilleter_thingy-0.2.1/skilleter_thingy/photodupe.py +135 -0
- skilleter_thingy-0.2.1/skilleter_thingy/phototidier.py +248 -0
- skilleter_thingy-0.2.1/skilleter_thingy/splitpics.py +99 -0
- skilleter_thingy-0.2.1/skilleter_thingy/sysmon.py +435 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/git.py +18 -5
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/git2.py +20 -7
- skilleter_thingy-0.2.1/skilleter_thingy/window_rename.py +92 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy.egg-info/PKG-INFO +46 -1
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy.egg-info/SOURCES.txt +9 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy.egg-info/entry_points.txt +9 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/LICENSE +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/setup.cfg +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/__init__.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/addpath.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/console_colours.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/docker_purge.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/ffind.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/gitcmp_helper.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/gl.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/linecount.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/multigit.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/py_audit.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/readable.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/remdir.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/rmdupe.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/rpylint.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/strreplace.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/tfm.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/tfparse.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/__init__.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/colour.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/dc_curses.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/dc_defaults.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/dc_util.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/dircolors.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/docker.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/files.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/gitlab.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/path.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/popup.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/process.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/run.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/tfm_pane.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/tidy.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/thingy/venv_template.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/trimpath.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/venv_create.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/xchmod.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy/yamlcheck.py +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy.egg-info/dependency_links.txt +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy.egg-info/requires.txt +0 -0
- {skilleter_thingy-0.2.0 → skilleter_thingy-0.2.1}/skilleter_thingy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skilleter_thingy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A collection of useful utilities, mainly aimed at making Git more friendly
|
|
5
5
|
Author-email: John Skilleter <john@skilleter.org.uk>
|
|
6
6
|
Project-URL: Home, https://skilleter.org.uk
|
|
@@ -597,10 +597,55 @@ YAML validator - checks that a file is valid YAML (use yamllint to verify that i
|
|
|
597
597
|
|
|
598
598
|
These will be moved to the skilleter-extras package in due course.
|
|
599
599
|
|
|
600
|
+
## borger
|
|
601
|
+
|
|
602
|
+
Wrapper for the borg backup utility to make it easier to use with a fixed set of options.
|
|
603
|
+
|
|
600
604
|
## consolecolours
|
|
601
605
|
|
|
602
606
|
Display all available colours in the console.
|
|
603
607
|
|
|
608
|
+
## diskspacecheck
|
|
609
|
+
|
|
610
|
+
Check how much free space is available on all filesystems, ignoring read-only filesystems, /dev and tmpfs.
|
|
611
|
+
|
|
612
|
+
Issue a warning if any are above 90% used.
|
|
613
|
+
|
|
614
|
+
## gphotosync & localphotosync
|
|
615
|
+
|
|
616
|
+
Utilities for syncing photos from Google Photos or a local directory to local storage
|
|
617
|
+
|
|
618
|
+
## moviemover
|
|
619
|
+
|
|
620
|
+
Search for files matching a wildcard in a directory tree and move them to an equivalent location in a different tree
|
|
621
|
+
|
|
622
|
+
## phototidier
|
|
623
|
+
|
|
624
|
+
Perform various tidying operations on a directory full of photos:
|
|
625
|
+
|
|
626
|
+
* Remove leading '$' and '_' from filenames
|
|
627
|
+
* Move files in hidden directories up 1 level
|
|
628
|
+
* If the EXIF data in a photo indicates that it was taken on date that doesn't match the name of the directory it is stored in (in YYYY-MM-DD format) then it is moved to the correct directory, creating it if necessary.
|
|
629
|
+
|
|
630
|
+
All move/rename operations are carried out safely with the file being moved having
|
|
631
|
+
a numeric suffix added to the name if it conflicts with an existing file.
|
|
632
|
+
|
|
633
|
+
## photodupe
|
|
634
|
+
|
|
635
|
+
Search for duplicate images in a directory tree
|
|
636
|
+
|
|
637
|
+
## splitpics
|
|
638
|
+
|
|
639
|
+
Copy a directory full of pictures to a destination, creating subdiretories with a fixed number of pictures in each in the destination directory for use with FAT filesystems and digital photo frames.
|
|
640
|
+
|
|
641
|
+
## sysmon
|
|
642
|
+
|
|
643
|
+
Simple console system monitor
|
|
644
|
+
|
|
645
|
+
## window-rename
|
|
646
|
+
|
|
647
|
+
Monitor window titles and rename them to fit an alphabetical grouping in 'Appname - Document' format.
|
|
648
|
+
|
|
604
649
|
# Obsolescent Commands
|
|
605
650
|
|
|
606
651
|
These commands will probably be retired in future versions of Thingy
|
|
@@ -574,10 +574,55 @@ YAML validator - checks that a file is valid YAML (use yamllint to verify that i
|
|
|
574
574
|
|
|
575
575
|
These will be moved to the skilleter-extras package in due course.
|
|
576
576
|
|
|
577
|
+
## borger
|
|
578
|
+
|
|
579
|
+
Wrapper for the borg backup utility to make it easier to use with a fixed set of options.
|
|
580
|
+
|
|
577
581
|
## consolecolours
|
|
578
582
|
|
|
579
583
|
Display all available colours in the console.
|
|
580
584
|
|
|
585
|
+
## diskspacecheck
|
|
586
|
+
|
|
587
|
+
Check how much free space is available on all filesystems, ignoring read-only filesystems, /dev and tmpfs.
|
|
588
|
+
|
|
589
|
+
Issue a warning if any are above 90% used.
|
|
590
|
+
|
|
591
|
+
## gphotosync & localphotosync
|
|
592
|
+
|
|
593
|
+
Utilities for syncing photos from Google Photos or a local directory to local storage
|
|
594
|
+
|
|
595
|
+
## moviemover
|
|
596
|
+
|
|
597
|
+
Search for files matching a wildcard in a directory tree and move them to an equivalent location in a different tree
|
|
598
|
+
|
|
599
|
+
## phototidier
|
|
600
|
+
|
|
601
|
+
Perform various tidying operations on a directory full of photos:
|
|
602
|
+
|
|
603
|
+
* Remove leading '$' and '_' from filenames
|
|
604
|
+
* Move files in hidden directories up 1 level
|
|
605
|
+
* If the EXIF data in a photo indicates that it was taken on date that doesn't match the name of the directory it is stored in (in YYYY-MM-DD format) then it is moved to the correct directory, creating it if necessary.
|
|
606
|
+
|
|
607
|
+
All move/rename operations are carried out safely with the file being moved having
|
|
608
|
+
a numeric suffix added to the name if it conflicts with an existing file.
|
|
609
|
+
|
|
610
|
+
## photodupe
|
|
611
|
+
|
|
612
|
+
Search for duplicate images in a directory tree
|
|
613
|
+
|
|
614
|
+
## splitpics
|
|
615
|
+
|
|
616
|
+
Copy a directory full of pictures to a destination, creating subdiretories with a fixed number of pictures in each in the destination directory for use with FAT filesystems and digital photo frames.
|
|
617
|
+
|
|
618
|
+
## sysmon
|
|
619
|
+
|
|
620
|
+
Simple console system monitor
|
|
621
|
+
|
|
622
|
+
## window-rename
|
|
623
|
+
|
|
624
|
+
Monitor window titles and rename them to fit an alphabetical grouping in 'Appname - Document' format.
|
|
625
|
+
|
|
581
626
|
# Obsolescent Commands
|
|
582
627
|
|
|
583
628
|
These commands will probably be retired in future versions of Thingy
|
|
@@ -7,7 +7,7 @@ name = "skilleter_thingy"
|
|
|
7
7
|
|
|
8
8
|
# Version must be incremented to install updated Thingy
|
|
9
9
|
|
|
10
|
-
version = "0.2.
|
|
10
|
+
version = "0.2.1"
|
|
11
11
|
|
|
12
12
|
authors = [
|
|
13
13
|
{name="John Skilleter", email="john@skilleter.org.uk"},
|
|
@@ -42,7 +42,9 @@ Home = "https://skilleter.org.uk"
|
|
|
42
42
|
|
|
43
43
|
[project.scripts]
|
|
44
44
|
addpath = "skilleter_thingy:addpath.addpath"
|
|
45
|
+
borger = "skilleter_thingy:borger.borger"
|
|
45
46
|
consolecolours = "skilleter_thingy:console_colours.console_colours"
|
|
47
|
+
diskspacecheck = "skilleter_thingy:diskspacecheck.diskspacecheck"
|
|
46
48
|
docker-purge = "skilleter_thingy:docker_purge.docker_purge"
|
|
47
49
|
ffind = "skilleter_thingy:ffind.ffind"
|
|
48
50
|
ggit = "skilleter_thingy:ggit.ggit"
|
|
@@ -64,19 +66,26 @@ gitprompt = "skilleter_thingy:gitprompt.gitprompt"
|
|
|
64
66
|
gl = "skilleter_thingy:gl.gl"
|
|
65
67
|
gphotosync = "skilleter_thingy:gphotosync.gphotosync"
|
|
66
68
|
linecount = "skilleter_thingy:linecount.linecount"
|
|
69
|
+
localphotosync = "skilleter_thingy:localphotosync.localphotosync"
|
|
67
70
|
mg = "skilleter_thingy:mg.mg"
|
|
71
|
+
moviemover = "skilleter_thingy:moviemover.moviemover"
|
|
68
72
|
multigit = "skilleter_thingy:multigit.multigit"
|
|
73
|
+
photodupe = "skilleter_thingy:photodupe.photodupe"
|
|
74
|
+
phototidier = "skilleter_thingy:phototidier.phototidier"
|
|
69
75
|
py-audit = "skilleter_thingy:py_audit.py_audit"
|
|
70
76
|
readable = "skilleter_thingy:readable.readable"
|
|
71
77
|
remdir = "skilleter_thingy:remdir.remdir"
|
|
72
78
|
rmdupe = "skilleter_thingy:rmdupe.rmdupe"
|
|
73
79
|
rpylint = "skilleter_thingy:rpylint.rpylint"
|
|
74
80
|
s3-sync = "skilleter_thingy:s3_sync.s3_sync"
|
|
81
|
+
splitpics = "skilleter_thingy:splitpics.splitpics"
|
|
75
82
|
strreplace = "skilleter_thingy:strreplace.strreplace"
|
|
83
|
+
sysmon = "skilleter_thingy:sysmon.sysmon"
|
|
76
84
|
tfm = "skilleter_thingy:tfm.tfm"
|
|
77
85
|
tfparse = "skilleter_thingy:tfparse.tfparse"
|
|
78
86
|
trimpath = "skilleter_thingy:trimpath.trimpath"
|
|
79
87
|
venv-create = "skilleter_thingy:venv_create.venv_create"
|
|
80
88
|
webwatch = "skilleter_thingy:webwatch.webwatch"
|
|
89
|
+
window-rename = "skilleter_thingy:window_rename.window_rename"
|
|
81
90
|
xchmod = "skilleter_thingy:xchmod.xchmod"
|
|
82
91
|
yamlcheck = "skilleter_thingy:yamlcheck.yamlcheck"
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Wrapper for the borg backup command
|
|
5
|
+
|
|
6
|
+
Copyright (C) 2018 John Skilleter
|
|
7
|
+
|
|
8
|
+
TODO: Major tidy-up as this is a translation of a Bash script.
|
|
9
|
+
TODO: Merge with the usb-backup script since both do almost the same job
|
|
10
|
+
TODO: Default configuration file should be named for the hostname
|
|
11
|
+
TODO: Move all configuration data into the configuration file
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
################################################################################
|
|
15
|
+
# Imports
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
import os
|
|
19
|
+
import time
|
|
20
|
+
import argparse
|
|
21
|
+
import configparser
|
|
22
|
+
import subprocess
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
################################################################################
|
|
26
|
+
# Variables
|
|
27
|
+
|
|
28
|
+
DEFAULT_CONFIG_FILE = Path('borger.ini')
|
|
29
|
+
|
|
30
|
+
COMMANDS = ('backup', 'mount', 'umount', 'compact', 'info', 'prune', 'check', 'init')
|
|
31
|
+
|
|
32
|
+
# TODO: NOT USED
|
|
33
|
+
PRUNE_OPTIONS = [
|
|
34
|
+
'--keep-within', '7d',
|
|
35
|
+
'--keep-daily', '30',
|
|
36
|
+
'--keep-weekly', '26',
|
|
37
|
+
'--keep-monthly', '24',
|
|
38
|
+
'--keep-yearly', '10',
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
################################################################################
|
|
42
|
+
|
|
43
|
+
def run(args, cmd):
|
|
44
|
+
"""Run a subprocess."""
|
|
45
|
+
|
|
46
|
+
if args.debug:
|
|
47
|
+
cmd_str = ' '.join(cmd)
|
|
48
|
+
print(f'Running "{cmd_str}"')
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
return subprocess.run(cmd, check=True)
|
|
52
|
+
except FileNotFoundError:
|
|
53
|
+
print('Borg backup is not installed')
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
################################################################################
|
|
57
|
+
|
|
58
|
+
def borg_backup(args, exclude_list):
|
|
59
|
+
"""Perform a backup."""
|
|
60
|
+
|
|
61
|
+
create_options = ['--compression', 'auto,lzma']
|
|
62
|
+
|
|
63
|
+
version = time.strftime('%Y-%m-%d-%H:%M:%S')
|
|
64
|
+
|
|
65
|
+
print(f'Creating backup version {version}')
|
|
66
|
+
|
|
67
|
+
if args.verbose:
|
|
68
|
+
create_options += ['--list', '--filter=AMC']
|
|
69
|
+
|
|
70
|
+
if args.dryrun:
|
|
71
|
+
create_options.append('--dry-run')
|
|
72
|
+
else:
|
|
73
|
+
create_options.append('--stats')
|
|
74
|
+
|
|
75
|
+
exclude_opts = []
|
|
76
|
+
|
|
77
|
+
if exclude_list:
|
|
78
|
+
for exclude in exclude_list:
|
|
79
|
+
exclude_opts += ['--exclude', exclude]
|
|
80
|
+
|
|
81
|
+
os.chdir(args.source)
|
|
82
|
+
|
|
83
|
+
run(args,
|
|
84
|
+
['borg'] + args.options + ['create', f'{str(args.destination)}::{version}', str(args.source)] + create_options +
|
|
85
|
+
['--show-rc', '--one-file-system', '--exclude-caches'] + exclude_opts)
|
|
86
|
+
|
|
87
|
+
################################################################################
|
|
88
|
+
|
|
89
|
+
def borg_prune(args):
|
|
90
|
+
"""Prune the repo by limiting the number of backups stored."""
|
|
91
|
+
|
|
92
|
+
print('Pruning old backups')
|
|
93
|
+
|
|
94
|
+
# Keep all backups for at least 7 days, 1 per day for 30 days, 1 per week for 2 years
|
|
95
|
+
# 1 per month for 4 years and 1 per year for 10 years.
|
|
96
|
+
|
|
97
|
+
run(args, ['borg'] + args.options + ['prune', str(args.destination)] + PRUNE_OPTIONS)
|
|
98
|
+
|
|
99
|
+
################################################################################
|
|
100
|
+
|
|
101
|
+
def borg_compact(args):
|
|
102
|
+
"""Compact the repo."""
|
|
103
|
+
|
|
104
|
+
print('Compacting the backup')
|
|
105
|
+
|
|
106
|
+
# Keep all backups for at least 7 days, 1 per day for 30 days, 1 per week for 2 years
|
|
107
|
+
# 1 per month for 4 years and 1 per year for 10 years.
|
|
108
|
+
|
|
109
|
+
run(args, ['borg'] + args.options + ['compact', str(args.destination)])
|
|
110
|
+
|
|
111
|
+
################################################################################
|
|
112
|
+
|
|
113
|
+
def borg_info(args):
|
|
114
|
+
"""Info."""
|
|
115
|
+
|
|
116
|
+
run(args, ['borg'] + args.options + ['info', str(args.destination)])
|
|
117
|
+
|
|
118
|
+
################################################################################
|
|
119
|
+
|
|
120
|
+
def borg_mount(args):
|
|
121
|
+
"""Mount."""
|
|
122
|
+
|
|
123
|
+
print(f'Mounting Borg backups at {args.mount_dir}')
|
|
124
|
+
|
|
125
|
+
mount = Path(args.mount_dir)
|
|
126
|
+
|
|
127
|
+
if not mount.is_dir():
|
|
128
|
+
mount.mkdir()
|
|
129
|
+
|
|
130
|
+
run(args, ['borg'] + args.options + ['mount', str(args.destination), str(mount)])
|
|
131
|
+
|
|
132
|
+
################################################################################
|
|
133
|
+
|
|
134
|
+
def borg_umount(args):
|
|
135
|
+
"""Unmount."""
|
|
136
|
+
|
|
137
|
+
print('Unmounting {args.mount}')
|
|
138
|
+
|
|
139
|
+
run(args, ['borg'] + args.options + ['umount', str(args.mount)])
|
|
140
|
+
|
|
141
|
+
################################################################################
|
|
142
|
+
|
|
143
|
+
def borg_check(args):
|
|
144
|
+
"""Check the status of a backup."""
|
|
145
|
+
|
|
146
|
+
run(args, ['borg'] + args.options + ['check', str(args.destination)])
|
|
147
|
+
|
|
148
|
+
################################################################################
|
|
149
|
+
|
|
150
|
+
def borg_init(args):
|
|
151
|
+
"""Initialise a backup."""
|
|
152
|
+
|
|
153
|
+
run(args, ['borg'] + args.options + ['init', str(args.destination), '--encryption=none'])
|
|
154
|
+
|
|
155
|
+
################################################################################
|
|
156
|
+
|
|
157
|
+
def process_excludes(exclude_data):
|
|
158
|
+
"""Process the include list from the configuration file."""
|
|
159
|
+
|
|
160
|
+
return exclude_data.replace('%', str(Path.cwd())).split('\n')
|
|
161
|
+
|
|
162
|
+
################################################################################
|
|
163
|
+
|
|
164
|
+
def main():
|
|
165
|
+
"""Entry point."""
|
|
166
|
+
|
|
167
|
+
command_list = ', '.join(COMMANDS)
|
|
168
|
+
|
|
169
|
+
parser = argparse.ArgumentParser(description='Wrapper app for Borg backup to make it easier to use')
|
|
170
|
+
parser.add_argument('--dryrun', '--dry-run', '-D', action='store_true', help='Dry-run comands')
|
|
171
|
+
parser.add_argument('--debug', '-d', action='store_true', help='Debug')
|
|
172
|
+
parser.add_argument('--verbose', '-v', action='store_true', help='Verbosity to the maximum')
|
|
173
|
+
parser.add_argument('--config', '-c', default=None, help='Specify the configuration file')
|
|
174
|
+
parser.add_argument('commands', nargs='+', help=f'One or more commands ({command_list})')
|
|
175
|
+
args = parser.parse_args()
|
|
176
|
+
|
|
177
|
+
# If no config file specified then look in all the usual places
|
|
178
|
+
|
|
179
|
+
if args.config:
|
|
180
|
+
args.config = Path(args.config)
|
|
181
|
+
elif DEFAULT_CONFIG_FILE.is_file():
|
|
182
|
+
args.config = DEFAULT_CONFIG_FILE
|
|
183
|
+
else:
|
|
184
|
+
args.config = Path.home() / DEFAULT_CONFIG_FILE
|
|
185
|
+
|
|
186
|
+
if not args.config.is_file():
|
|
187
|
+
args.config = Path(sys.argv[0]).parent / DEFAULT_CONFIG_FILE
|
|
188
|
+
|
|
189
|
+
# Check that the configuration file exists
|
|
190
|
+
|
|
191
|
+
if not args.config.is_file():
|
|
192
|
+
print(f'Configuration file "{args.config}" not found')
|
|
193
|
+
sys.exit(1)
|
|
194
|
+
|
|
195
|
+
# Default options
|
|
196
|
+
|
|
197
|
+
args.options = []
|
|
198
|
+
|
|
199
|
+
# Read the configuration file
|
|
200
|
+
|
|
201
|
+
config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
|
|
202
|
+
config.read(args.config)
|
|
203
|
+
|
|
204
|
+
if 'borger' not in config:
|
|
205
|
+
print('Invalid configuration file "args.config"')
|
|
206
|
+
sys.exit(1)
|
|
207
|
+
|
|
208
|
+
exclude = process_excludes(config['borger']['exclude']) if 'exclude' in config['borger'] else []
|
|
209
|
+
|
|
210
|
+
if 'prune' in config['borger']:
|
|
211
|
+
# TODO: Stuff
|
|
212
|
+
print('Parser for the prune option is not implemented yet')
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
|
|
215
|
+
if 'destination' in config['borger']:
|
|
216
|
+
args.destination = config['borger']['destination']
|
|
217
|
+
else:
|
|
218
|
+
print('Destination directory not specified')
|
|
219
|
+
sys.exit(1)
|
|
220
|
+
|
|
221
|
+
if 'source' in config['borger']:
|
|
222
|
+
args.source = Path(config['borger']['source'])
|
|
223
|
+
else:
|
|
224
|
+
print('Source directory not specified')
|
|
225
|
+
sys.exit(1)
|
|
226
|
+
|
|
227
|
+
# Initialise if necessary
|
|
228
|
+
|
|
229
|
+
if args.debug:
|
|
230
|
+
args.options.append('--verbose')
|
|
231
|
+
|
|
232
|
+
if args.verbose:
|
|
233
|
+
args.options.append('--progress')
|
|
234
|
+
|
|
235
|
+
# Decide what to do
|
|
236
|
+
|
|
237
|
+
for command in args.commands:
|
|
238
|
+
if command == 'backup':
|
|
239
|
+
borg_backup(args, exclude)
|
|
240
|
+
elif command == 'mount':
|
|
241
|
+
borg_mount(args)
|
|
242
|
+
elif command == 'umount':
|
|
243
|
+
borg_umount(args)
|
|
244
|
+
elif command == 'info':
|
|
245
|
+
borg_info(args)
|
|
246
|
+
elif command == 'prune':
|
|
247
|
+
borg_prune(args)
|
|
248
|
+
elif command == 'check':
|
|
249
|
+
borg_check(args)
|
|
250
|
+
elif command == 'init':
|
|
251
|
+
borg_init(args)
|
|
252
|
+
elif command == 'compact':
|
|
253
|
+
borg_compact(args)
|
|
254
|
+
else:
|
|
255
|
+
print(f'Unrecognized command: {command}')
|
|
256
|
+
sys.exit(2)
|
|
257
|
+
|
|
258
|
+
################################################################################
|
|
259
|
+
|
|
260
|
+
def borger():
|
|
261
|
+
"""Entry point"""
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
main()
|
|
265
|
+
except KeyboardInterrupt:
|
|
266
|
+
sys.exit(1)
|
|
267
|
+
except BrokenPipeError:
|
|
268
|
+
sys.exit(2)
|
|
269
|
+
|
|
270
|
+
################################################################################
|
|
271
|
+
|
|
272
|
+
if __name__ == '__main__':
|
|
273
|
+
borger()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Check how much free space is available on all filesystems, ignoring
|
|
5
|
+
read-only filesystems, /dev and tmpfs.
|
|
6
|
+
|
|
7
|
+
Issue a warning if any are above 90% used.
|
|
8
|
+
"""
|
|
9
|
+
################################################################################
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import argparse
|
|
13
|
+
import psutil
|
|
14
|
+
|
|
15
|
+
################################################################################
|
|
16
|
+
|
|
17
|
+
WARNING_LEVEL = 15
|
|
18
|
+
|
|
19
|
+
################################################################################
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
""" Do everything """
|
|
23
|
+
|
|
24
|
+
parser = argparse.ArgumentParser(description='Check for filesystems that are running low on space')
|
|
25
|
+
parser.add_argument('--level', action='store', type=int, default=WARNING_LEVEL,
|
|
26
|
+
help='Warning if less than this amount of space is available on any writeable, mounted filesystem (default=%d)' % WARNING_LEVEL)
|
|
27
|
+
args = parser.parse_args()
|
|
28
|
+
|
|
29
|
+
if args.level < 0 or args.level > 100:
|
|
30
|
+
print('Invalid value: %d' % args.level)
|
|
31
|
+
sys.exit(3)
|
|
32
|
+
|
|
33
|
+
disks = psutil.disk_partitions()
|
|
34
|
+
devices = []
|
|
35
|
+
warning = []
|
|
36
|
+
|
|
37
|
+
for disk in disks:
|
|
38
|
+
if 'ro' not in disk.opts.split(',') and disk.device not in devices:
|
|
39
|
+
devices.append(disk.device)
|
|
40
|
+
usage = psutil.disk_usage(disk.mountpoint)
|
|
41
|
+
|
|
42
|
+
disk_space = 100 - usage.percent
|
|
43
|
+
|
|
44
|
+
if disk_space < args.level:
|
|
45
|
+
warning.append('%s has only %2.1f%% space available' % (disk.mountpoint, disk_space))
|
|
46
|
+
|
|
47
|
+
if warning:
|
|
48
|
+
print('Filesystems with less than %d%% available space:' % args.level)
|
|
49
|
+
print('\n'.join(warning))
|
|
50
|
+
|
|
51
|
+
################################################################################
|
|
52
|
+
|
|
53
|
+
def diskspacecheck():
|
|
54
|
+
"""Entry point"""
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
main()
|
|
58
|
+
|
|
59
|
+
except KeyboardInterrupt:
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
except BrokenPipeError:
|
|
62
|
+
sys.exit(2)
|
|
63
|
+
|
|
64
|
+
################################################################################
|
|
65
|
+
|
|
66
|
+
if __name__ == '__main__':
|
|
67
|
+
diskspacecheck()
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"""
|
|
14
14
|
################################################################################
|
|
15
15
|
|
|
16
|
+
import os
|
|
16
17
|
import sys
|
|
17
18
|
import argparse
|
|
18
19
|
import fnmatch
|
|
@@ -21,6 +22,7 @@ import datetime
|
|
|
21
22
|
from dateutil.parser import parse
|
|
22
23
|
from dateutil.relativedelta import relativedelta
|
|
23
24
|
|
|
25
|
+
# TODO: Update to git2
|
|
24
26
|
import thingy.git as git
|
|
25
27
|
import thingy.colour as colour
|
|
26
28
|
|
|
@@ -34,10 +36,15 @@ def parse_command_line():
|
|
|
34
36
|
parser.add_argument('--all', '-a', action='store_true', help='List all branches, including remotes')
|
|
35
37
|
parser.add_argument('--delete', '-d', action='store_true',
|
|
36
38
|
help='Delete the specified branch(es), even if it is the current one (list of branches to delete must be supplied as parameters)')
|
|
39
|
+
parser.add_argument('--path', '-C', nargs=1, type=str, default=None,
|
|
40
|
+
help='Run the command in the specified directory')
|
|
37
41
|
parser.add_argument('branches', nargs='*', help='Filter the list of branches according to one or more patterns')
|
|
38
42
|
|
|
39
43
|
args = parser.parse_args()
|
|
40
44
|
|
|
45
|
+
if args.path:
|
|
46
|
+
os.chdir(args.path[0])
|
|
47
|
+
|
|
41
48
|
if args.delete and not args.branches:
|
|
42
49
|
colour.error('You must specify the branches to delete', prefix=True)
|
|
43
50
|
|
|
@@ -18,6 +18,7 @@ import sys
|
|
|
18
18
|
import logging
|
|
19
19
|
|
|
20
20
|
import thingy.colour as colour
|
|
21
|
+
# TODO: Update to git2
|
|
21
22
|
import thingy.git as git
|
|
22
23
|
|
|
23
24
|
################################################################################
|
|
@@ -43,6 +44,8 @@ def main():
|
|
|
43
44
|
parser.add_argument('--patch', '-p', action='store_true', help='Use the interactive patch selection interface to chose which changes to commit.')
|
|
44
45
|
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose mode')
|
|
45
46
|
parser.add_argument('--dry-run', '-D', action='store_true', help='Dry-run')
|
|
47
|
+
parser.add_argument('--path', '-C', nargs=1, type=str, default=None,
|
|
48
|
+
help='Run the command in the specified directory')
|
|
46
49
|
|
|
47
50
|
parser.add_argument('files', nargs='*', help='List of files to add to the commit')
|
|
48
51
|
|
|
@@ -54,6 +57,11 @@ def main():
|
|
|
54
57
|
logging.basicConfig(level=logging.INFO)
|
|
55
58
|
logging.info('Debug logging enabled')
|
|
56
59
|
|
|
60
|
+
# Change directory, if specified
|
|
61
|
+
|
|
62
|
+
if args.path:
|
|
63
|
+
os.chdir(args.path[0])
|
|
64
|
+
|
|
57
65
|
# 'Add' implies 'all'
|
|
58
66
|
|
|
59
67
|
if args.everything:
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
"""
|
|
10
10
|
################################################################################
|
|
11
11
|
|
|
12
|
+
import os
|
|
12
13
|
import sys
|
|
13
14
|
import argparse
|
|
14
15
|
import logging
|
|
15
16
|
|
|
17
|
+
# TODO: Update to git2
|
|
16
18
|
import thingy.git as git
|
|
17
19
|
import thingy.colour as colour
|
|
18
20
|
|
|
@@ -38,6 +40,8 @@ def parse_command_line():
|
|
|
38
40
|
parser.add_argument('--unmerged', '-u', action='store_true', dest='list_unmerged', help='List branches that have NOT been merged')
|
|
39
41
|
parser.add_argument('--yes', '-y', action='store_true', dest='force', help='Assume "yes" in response to any prompts (e.g. to delete branches)')
|
|
40
42
|
parser.add_argument('--debug', action='store_true', help='Enable debug output')
|
|
43
|
+
parser.add_argument('--path', '-C', nargs=1, type=str, default=None,
|
|
44
|
+
help='Run the command in the specified directory')
|
|
41
45
|
|
|
42
46
|
parser.add_argument('branches', nargs='*', help='List of branches to check (default is all branches)')
|
|
43
47
|
|
|
@@ -94,6 +98,11 @@ def main():
|
|
|
94
98
|
if args.debug:
|
|
95
99
|
logging.basicConfig(level=logging.INFO)
|
|
96
100
|
|
|
101
|
+
# Change directory, if specified
|
|
102
|
+
|
|
103
|
+
if args.path:
|
|
104
|
+
os.chdir(args.path[0])
|
|
105
|
+
|
|
97
106
|
# Get the list of all local branches
|
|
98
107
|
|
|
99
108
|
try:
|
|
@@ -280,6 +289,8 @@ def git_cleanup():
|
|
|
280
289
|
sys.exit(1)
|
|
281
290
|
except BrokenPipeError:
|
|
282
291
|
sys.exit(2)
|
|
292
|
+
except git.GitError as exc:
|
|
293
|
+
colour.error(exc.msg, status=exc.status, prefix=True)
|
|
283
294
|
|
|
284
295
|
################################################################################
|
|
285
296
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"""
|
|
21
21
|
################################################################################
|
|
22
22
|
|
|
23
|
+
import os
|
|
23
24
|
import logging
|
|
24
25
|
import sys
|
|
25
26
|
import argparse
|
|
@@ -68,6 +69,8 @@ def parse_arguments():
|
|
|
68
69
|
parser.add_argument('--debug', action='store_true', help='Enable debug output')
|
|
69
70
|
parser.add_argument('branchname', nargs=1, type=str,
|
|
70
71
|
help='The branch name (or a partial name that matches uniquely against a local branch, remote branch, commit ID or tag)')
|
|
72
|
+
parser.add_argument('--path', '-C', nargs=1, type=str, default=None,
|
|
73
|
+
help='Run the command in the specified directory')
|
|
71
74
|
|
|
72
75
|
args = parser.parse_args()
|
|
73
76
|
|
|
@@ -76,6 +79,9 @@ def parse_arguments():
|
|
|
76
79
|
if args.debug:
|
|
77
80
|
logging.basicConfig(level=logging.INFO)
|
|
78
81
|
|
|
82
|
+
if args.path:
|
|
83
|
+
os.chdir(args.path[0])
|
|
84
|
+
|
|
79
85
|
return args
|
|
80
86
|
|
|
81
87
|
################################################################################
|
|
@@ -198,9 +204,6 @@ def main():
|
|
|
198
204
|
else:
|
|
199
205
|
checkout_matching_branch(args, args.branchname[0])
|
|
200
206
|
|
|
201
|
-
except git.GitError as exc:
|
|
202
|
-
colour.error(exc.msg, exc.status)
|
|
203
|
-
|
|
204
207
|
################################################################################
|
|
205
208
|
|
|
206
209
|
def git_co():
|
|
@@ -213,6 +216,8 @@ def git_co():
|
|
|
213
216
|
sys.exit(1)
|
|
214
217
|
except BrokenPipeError:
|
|
215
218
|
sys.exit(2)
|
|
219
|
+
except git.GitError as exc:
|
|
220
|
+
colour.error(exc.msg, status=exc.status, prefix=True)
|
|
216
221
|
|
|
217
222
|
################################################################################
|
|
218
223
|
|