skilleter-thingy 0.0.39__py3-none-any.whl → 0.0.41__py3-none-any.whl
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/__init__.py +6 -0
- skilleter_thingy/addpath.py +107 -0
- skilleter_thingy/borger.py +269 -0
- skilleter_thingy/console_colours.py +63 -0
- skilleter_thingy/diskspacecheck.py +67 -0
- skilleter_thingy/docker_purge.py +113 -0
- skilleter_thingy/ffind.py +536 -0
- skilleter_thingy/ggit.py +90 -0
- skilleter_thingy/ggrep.py +154 -0
- skilleter_thingy/git_br.py +180 -0
- skilleter_thingy/git_ca.py +142 -0
- skilleter_thingy/git_cleanup.py +287 -0
- skilleter_thingy/git_co.py +220 -0
- skilleter_thingy/git_common.py +61 -0
- skilleter_thingy/git_hold.py +154 -0
- skilleter_thingy/git_mr.py +92 -0
- skilleter_thingy/git_parent.py +77 -0
- skilleter_thingy/git_review.py +1428 -0
- skilleter_thingy/git_update.py +385 -0
- skilleter_thingy/git_wt.py +96 -0
- skilleter_thingy/gitcmp_helper.py +322 -0
- skilleter_thingy/gitprompt.py +274 -0
- skilleter_thingy/gl.py +174 -0
- skilleter_thingy/gphotosync.py +610 -0
- skilleter_thingy/linecount.py +155 -0
- skilleter_thingy/moviemover.py +133 -0
- skilleter_thingy/photodupe.py +136 -0
- skilleter_thingy/phototidier.py +248 -0
- skilleter_thingy/py_audit.py +131 -0
- skilleter_thingy/readable.py +270 -0
- skilleter_thingy/remdir.py +126 -0
- skilleter_thingy/rmdupe.py +550 -0
- skilleter_thingy/rpylint.py +91 -0
- skilleter_thingy/splitpics.py +99 -0
- skilleter_thingy/strreplace.py +82 -0
- skilleter_thingy/sysmon.py +435 -0
- skilleter_thingy/tfm.py +920 -0
- skilleter_thingy/tfparse.py +101 -0
- skilleter_thingy/thingy/__init__.py +6 -0
- skilleter_thingy/thingy/colour.py +213 -0
- skilleter_thingy/thingy/dc_curses.py +278 -0
- skilleter_thingy/thingy/dc_defaults.py +221 -0
- skilleter_thingy/thingy/dc_util.py +50 -0
- skilleter_thingy/thingy/dircolors.py +308 -0
- skilleter_thingy/thingy/docker.py +95 -0
- skilleter_thingy/thingy/files.py +142 -0
- skilleter_thingy/thingy/git.py +1371 -0
- skilleter_thingy/thingy/git2.py +1307 -0
- skilleter_thingy/thingy/gitlab.py +193 -0
- skilleter_thingy/thingy/logger.py +112 -0
- skilleter_thingy/thingy/path.py +156 -0
- skilleter_thingy/thingy/popup.py +87 -0
- skilleter_thingy/thingy/process.py +112 -0
- skilleter_thingy/thingy/run.py +334 -0
- skilleter_thingy/thingy/tfm_pane.py +595 -0
- skilleter_thingy/thingy/tidy.py +160 -0
- skilleter_thingy/trimpath.py +84 -0
- skilleter_thingy/window_rename.py +92 -0
- skilleter_thingy/xchmod.py +125 -0
- skilleter_thingy/yamlcheck.py +89 -0
- {skilleter_thingy-0.0.39.dist-info → skilleter_thingy-0.0.41.dist-info}/METADATA +1 -1
- skilleter_thingy-0.0.41.dist-info/RECORD +66 -0
- skilleter_thingy-0.0.41.dist-info/top_level.txt +1 -0
- skilleter_thingy-0.0.39.dist-info/RECORD +0 -6
- skilleter_thingy-0.0.39.dist-info/top_level.txt +0 -1
- {skilleter_thingy-0.0.39.dist-info → skilleter_thingy-0.0.41.dist-info}/LICENSE +0 -0
- {skilleter_thingy-0.0.39.dist-info → skilleter_thingy-0.0.41.dist-info}/WHEEL +0 -0
- {skilleter_thingy-0.0.39.dist-info → skilleter_thingy-0.0.41.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Thingy addpath command
|
|
5
|
+
|
|
6
|
+
Copyright (C) 2018 John Skilleter
|
|
7
|
+
|
|
8
|
+
Update a $PATH-type variable by adding or removing entries.
|
|
9
|
+
|
|
10
|
+
Intended to be used as in:
|
|
11
|
+
|
|
12
|
+
export PATH=$(addpath $PATH --add /opt/bin)
|
|
13
|
+
"""
|
|
14
|
+
################################################################################
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
import os
|
|
18
|
+
import argparse
|
|
19
|
+
|
|
20
|
+
################################################################################
|
|
21
|
+
|
|
22
|
+
def pathmod(pathentries, separator, pathlist, prefix=False, suffix=False, delete=False, force=False):
|
|
23
|
+
""" Modify a path.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# Only do something if the list of paths to add exists
|
|
27
|
+
|
|
28
|
+
if pathlist:
|
|
29
|
+
# Join the list path entries together then split them into individual entries
|
|
30
|
+
# Allows for a list of entries in the form ['a:b:c', 'd', 'e']
|
|
31
|
+
|
|
32
|
+
paths = separator.join(pathlist).split(separator)
|
|
33
|
+
|
|
34
|
+
# Process each entry
|
|
35
|
+
|
|
36
|
+
for entry in paths:
|
|
37
|
+
# Do nothing (except delete) if the path does not exist and we aren't forcing
|
|
38
|
+
|
|
39
|
+
if not entry or (not os.path.isdir(entry) and not (force or delete)):
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
# If we are removing or adding/moving an entry remove any existing entry
|
|
43
|
+
|
|
44
|
+
if (delete or prefix or suffix) and entry in pathentries:
|
|
45
|
+
pathentries.remove(entry)
|
|
46
|
+
|
|
47
|
+
# Prefix or suffix the entry
|
|
48
|
+
|
|
49
|
+
if not delete and entry not in pathentries:
|
|
50
|
+
if suffix:
|
|
51
|
+
pathentries.append(entry)
|
|
52
|
+
else:
|
|
53
|
+
pathentries.insert(0, entry)
|
|
54
|
+
|
|
55
|
+
return pathentries
|
|
56
|
+
|
|
57
|
+
################################################################################
|
|
58
|
+
|
|
59
|
+
def main():
|
|
60
|
+
""" Main function - handles command line, outputs result to stdout """
|
|
61
|
+
|
|
62
|
+
parser = argparse.ArgumentParser(description='Add or remove entries from a path list (e.g. as used by the PATH environment variable)')
|
|
63
|
+
parser.add_argument('--add', action='append', help='Add an entry to the front of the path (do nothing if it is already present in the path)')
|
|
64
|
+
parser.add_argument('--prefix', action='append', help='Add an entry to the front of the path (or move it there if it is already present)')
|
|
65
|
+
parser.add_argument('--suffix', action='append', help='Add an entry to the end of the path (or move it there if it is already present)')
|
|
66
|
+
parser.add_argument('--remove', action='append', help='Remove an entry from the path (do nothing if it is not present')
|
|
67
|
+
parser.add_argument('--force', default=False, help='Add entries even if a corresponding directory does not exist')
|
|
68
|
+
parser.add_argument('--separator', action='store', default=':', help='Override the default path separator')
|
|
69
|
+
parser.add_argument('path', nargs=1, help='The path to modify')
|
|
70
|
+
|
|
71
|
+
args = parser.parse_args()
|
|
72
|
+
|
|
73
|
+
# Split the given path into component parts
|
|
74
|
+
|
|
75
|
+
pathsplit = [pathentry for pathentry in args.path[0].split(args.separator) if pathentry]
|
|
76
|
+
|
|
77
|
+
# Process the additions, suffixations, prefixanisms and deletes.
|
|
78
|
+
|
|
79
|
+
pathmod(pathsplit, args.separator, args.add)
|
|
80
|
+
pathmod(pathsplit, args.separator, args.prefix, prefix=True)
|
|
81
|
+
pathmod(pathsplit, args.separator, args.suffix, suffix=True)
|
|
82
|
+
pathmod(pathsplit, args.separator, args.remove, delete=True)
|
|
83
|
+
|
|
84
|
+
# Glue the path back together
|
|
85
|
+
|
|
86
|
+
pathjoin = args.separator.join(pathsplit)
|
|
87
|
+
|
|
88
|
+
# Output the updated path to stdout
|
|
89
|
+
|
|
90
|
+
print(pathjoin)
|
|
91
|
+
|
|
92
|
+
################################################################################
|
|
93
|
+
|
|
94
|
+
def addpath():
|
|
95
|
+
"""Entry point"""
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
main()
|
|
99
|
+
except KeyboardInterrupt:
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
except BrokenPipeError:
|
|
102
|
+
sys.exit(2)
|
|
103
|
+
|
|
104
|
+
################################################################################
|
|
105
|
+
|
|
106
|
+
if __name__ == '__main__':
|
|
107
|
+
addpath()
|
|
@@ -0,0 +1,269 @@
|
|
|
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
|
+
return subprocess.run(cmd, check=True)
|
|
51
|
+
|
|
52
|
+
################################################################################
|
|
53
|
+
|
|
54
|
+
def borg_backup(args, exclude_list):
|
|
55
|
+
"""Perform a backup."""
|
|
56
|
+
|
|
57
|
+
create_options = ['--compression', 'auto,lzma']
|
|
58
|
+
|
|
59
|
+
version = time.strftime('%Y-%m-%d-%H:%M:%S')
|
|
60
|
+
|
|
61
|
+
print(f'Creating backup version {version}')
|
|
62
|
+
|
|
63
|
+
if args.verbose:
|
|
64
|
+
create_options += ['--list', '--filter=AMC']
|
|
65
|
+
|
|
66
|
+
if args.dryrun:
|
|
67
|
+
create_options.append('--dry-run')
|
|
68
|
+
else:
|
|
69
|
+
create_options.append('--stats')
|
|
70
|
+
|
|
71
|
+
exclude_opts = []
|
|
72
|
+
|
|
73
|
+
if exclude_list:
|
|
74
|
+
for exclude in exclude_list:
|
|
75
|
+
exclude_opts += ['--exclude', exclude]
|
|
76
|
+
|
|
77
|
+
os.chdir(args.source)
|
|
78
|
+
|
|
79
|
+
run(args,
|
|
80
|
+
['borg'] + args.options + ['create', f'{str(args.destination)}::{version}', str(args.source)] + create_options +
|
|
81
|
+
['--show-rc', '--one-file-system', '--exclude-caches'] + exclude_opts)
|
|
82
|
+
|
|
83
|
+
################################################################################
|
|
84
|
+
|
|
85
|
+
def borg_prune(args):
|
|
86
|
+
"""Prune the repo by limiting the number of backups stored."""
|
|
87
|
+
|
|
88
|
+
print('Pruning old backups')
|
|
89
|
+
|
|
90
|
+
# Keep all backups for at least 7 days, 1 per day for 30 days, 1 per week for 2 years
|
|
91
|
+
# 1 per month for 4 years and 1 per year for 10 years.
|
|
92
|
+
|
|
93
|
+
run(args, ['borg'] + args.options + ['prune', str(args.destination)] + PRUNE_OPTIONS)
|
|
94
|
+
|
|
95
|
+
################################################################################
|
|
96
|
+
|
|
97
|
+
def borg_compact(args):
|
|
98
|
+
"""Compact the repo."""
|
|
99
|
+
|
|
100
|
+
print('Compacting the backup')
|
|
101
|
+
|
|
102
|
+
# Keep all backups for at least 7 days, 1 per day for 30 days, 1 per week for 2 years
|
|
103
|
+
# 1 per month for 4 years and 1 per year for 10 years.
|
|
104
|
+
|
|
105
|
+
run(args, ['borg'] + args.options + ['compact', str(args.destination)])
|
|
106
|
+
|
|
107
|
+
################################################################################
|
|
108
|
+
|
|
109
|
+
def borg_info(args):
|
|
110
|
+
"""Info."""
|
|
111
|
+
|
|
112
|
+
run(args, ['borg'] + args.options + ['info', str(args.destination)])
|
|
113
|
+
|
|
114
|
+
################################################################################
|
|
115
|
+
|
|
116
|
+
def borg_mount(args):
|
|
117
|
+
"""Mount."""
|
|
118
|
+
|
|
119
|
+
print(f'Mounting Borg backups at {args.mount_dir}')
|
|
120
|
+
|
|
121
|
+
mount = Path(args.mount_dir)
|
|
122
|
+
|
|
123
|
+
if not mount.is_dir():
|
|
124
|
+
mount.mkdir()
|
|
125
|
+
|
|
126
|
+
run(args, ['borg'] + args.options + ['mount', str(args.destination), str(mount)])
|
|
127
|
+
|
|
128
|
+
################################################################################
|
|
129
|
+
|
|
130
|
+
def borg_umount(args):
|
|
131
|
+
"""Unmount."""
|
|
132
|
+
|
|
133
|
+
print('Unmounting {args.mount}')
|
|
134
|
+
|
|
135
|
+
run(args, ['borg'] + args.options + ['umount', str(args.mount)])
|
|
136
|
+
|
|
137
|
+
################################################################################
|
|
138
|
+
|
|
139
|
+
def borg_check(args):
|
|
140
|
+
"""Check the status of a backup."""
|
|
141
|
+
|
|
142
|
+
run(args, ['borg'] + args.options + ['check', str(args.destination)])
|
|
143
|
+
|
|
144
|
+
################################################################################
|
|
145
|
+
|
|
146
|
+
def borg_init(args):
|
|
147
|
+
"""Initialise a backup."""
|
|
148
|
+
|
|
149
|
+
run(args, ['borg'] + args.options + ['init', str(args.destination), '--encryption=none'])
|
|
150
|
+
|
|
151
|
+
################################################################################
|
|
152
|
+
|
|
153
|
+
def process_excludes(exclude_data):
|
|
154
|
+
"""Process the include list from the configuration file."""
|
|
155
|
+
|
|
156
|
+
return exclude_data.replace('%', str(Path.cwd())).split('\n')
|
|
157
|
+
|
|
158
|
+
################################################################################
|
|
159
|
+
|
|
160
|
+
def main():
|
|
161
|
+
"""Entry point."""
|
|
162
|
+
|
|
163
|
+
command_list = ', '.join(COMMANDS)
|
|
164
|
+
|
|
165
|
+
parser = argparse.ArgumentParser(description='Wrapper app for Borg backup to make it easier to use')
|
|
166
|
+
parser.add_argument('--dryrun', '--dry-run', '-D', action='store_true', help='Dry-run comands')
|
|
167
|
+
parser.add_argument('--debug', '-d', action='store_true', help='Debug')
|
|
168
|
+
parser.add_argument('--verbose', '-v', action='store_true', help='Verbosity to the maximum')
|
|
169
|
+
parser.add_argument('--config', '-c', default=None, help='Specify the configuration file')
|
|
170
|
+
parser.add_argument('commands', nargs='+', help=f'One or more commands ({command_list})')
|
|
171
|
+
args = parser.parse_args()
|
|
172
|
+
|
|
173
|
+
# If no config file specified then look in all the usual places
|
|
174
|
+
|
|
175
|
+
if args.config:
|
|
176
|
+
args.config = Path(args.config)
|
|
177
|
+
elif DEFAULT_CONFIG_FILE.is_file():
|
|
178
|
+
args.config = DEFAULT_CONFIG_FILE
|
|
179
|
+
else:
|
|
180
|
+
args.config = Path.home() / DEFAULT_CONFIG_FILE
|
|
181
|
+
|
|
182
|
+
if not args.config.is_file():
|
|
183
|
+
args.config = Path(sys.argv[0]).parent / DEFAULT_CONFIG_FILE
|
|
184
|
+
|
|
185
|
+
# Check that the configuration file exists
|
|
186
|
+
|
|
187
|
+
if not args.config.is_file():
|
|
188
|
+
print(f'Configuration file "{args.config}" not found')
|
|
189
|
+
sys.exit(1)
|
|
190
|
+
|
|
191
|
+
# Default options
|
|
192
|
+
|
|
193
|
+
args.options = []
|
|
194
|
+
|
|
195
|
+
# Read the configuration file
|
|
196
|
+
|
|
197
|
+
config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
|
|
198
|
+
config.read(args.config)
|
|
199
|
+
|
|
200
|
+
if 'borger' not in config:
|
|
201
|
+
print('Invalid configuration file "args.config"')
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
|
|
204
|
+
exclude = process_excludes(config['borger']['exclude']) if 'exclude' in config['borger'] else []
|
|
205
|
+
|
|
206
|
+
if 'prune' in config['borger']:
|
|
207
|
+
# TODO: Stuff
|
|
208
|
+
print('Parser for the prune option is not implemented yet')
|
|
209
|
+
sys.exit(1)
|
|
210
|
+
|
|
211
|
+
if 'destination' in config['borger']:
|
|
212
|
+
args.destination = config['borger']['destination']
|
|
213
|
+
else:
|
|
214
|
+
print('Destination directory not specified')
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
if 'source' in config['borger']:
|
|
218
|
+
args.source = Path(config['borger']['source'])
|
|
219
|
+
else:
|
|
220
|
+
print('Source directory not specified')
|
|
221
|
+
sys.exit(1)
|
|
222
|
+
|
|
223
|
+
# Initialise if necessary
|
|
224
|
+
|
|
225
|
+
if args.debug:
|
|
226
|
+
args.options.append('--verbose')
|
|
227
|
+
|
|
228
|
+
if args.verbose:
|
|
229
|
+
args.options.append('--progress')
|
|
230
|
+
|
|
231
|
+
# Decide what to do
|
|
232
|
+
|
|
233
|
+
for command in args.commands:
|
|
234
|
+
if command == 'backup':
|
|
235
|
+
borg_backup(args, exclude)
|
|
236
|
+
elif command == 'mount':
|
|
237
|
+
borg_mount(args)
|
|
238
|
+
elif command == 'umount':
|
|
239
|
+
borg_umount(args)
|
|
240
|
+
elif command == 'info':
|
|
241
|
+
borg_info(args)
|
|
242
|
+
elif command == 'prune':
|
|
243
|
+
borg_prune(args)
|
|
244
|
+
elif command == 'check':
|
|
245
|
+
borg_check(args)
|
|
246
|
+
elif command == 'init':
|
|
247
|
+
borg_init(args)
|
|
248
|
+
elif command == 'compact':
|
|
249
|
+
borg_compact(args)
|
|
250
|
+
else:
|
|
251
|
+
print(f'Unrecognized command: {command}')
|
|
252
|
+
sys.exit(2)
|
|
253
|
+
|
|
254
|
+
################################################################################
|
|
255
|
+
|
|
256
|
+
def borger():
|
|
257
|
+
"""Entry point"""
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
main()
|
|
261
|
+
except KeyboardInterrupt:
|
|
262
|
+
sys.exit(1)
|
|
263
|
+
except BrokenPipeError:
|
|
264
|
+
sys.exit(2)
|
|
265
|
+
|
|
266
|
+
################################################################################
|
|
267
|
+
|
|
268
|
+
if __name__ == '__main__':
|
|
269
|
+
borger()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Output all console colours
|
|
5
|
+
|
|
6
|
+
Copyright (C) 2017-18 John Skilleter
|
|
7
|
+
|
|
8
|
+
Licence: GPL v3 or later
|
|
9
|
+
"""
|
|
10
|
+
################################################################################
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
import thingy.colour as colour
|
|
15
|
+
|
|
16
|
+
################################################################################
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
""" Main function - draw the colour grid """
|
|
20
|
+
|
|
21
|
+
# Extended ANSI colour are slightly weird.
|
|
22
|
+
# Colours 0-15 are the standard basic colours
|
|
23
|
+
# Colours 16-231 form a 6x6x6 colour cube
|
|
24
|
+
# Colours 232-255 are greyscale range with colours 0 and 15 as black and white
|
|
25
|
+
|
|
26
|
+
for code in range(0, 256):
|
|
27
|
+
if code in (8, 16) or (code > 16 and (code - 16) % 6 == 0):
|
|
28
|
+
colour.write('')
|
|
29
|
+
|
|
30
|
+
if (code - 16) % 36 == 0:
|
|
31
|
+
colour.write('')
|
|
32
|
+
|
|
33
|
+
# Set the foreground code to be white for dark backgrounds
|
|
34
|
+
|
|
35
|
+
foreground = 15 if code in (0, 1, 4, 5, 8, 12) \
|
|
36
|
+
or (16 <= code <= 33) \
|
|
37
|
+
or (52 <= code <= 69) \
|
|
38
|
+
or (88 <= code <= 105) \
|
|
39
|
+
or (124 <= code <= 135) \
|
|
40
|
+
or (160 <= code <= 171) \
|
|
41
|
+
or (196 <= code <= 201) \
|
|
42
|
+
or (232 <= code <= 243) else 0
|
|
43
|
+
|
|
44
|
+
colour.write('[B%d][%d] %3d [NORMAL] ' % (code, foreground, code), newline=False)
|
|
45
|
+
|
|
46
|
+
print()
|
|
47
|
+
|
|
48
|
+
################################################################################
|
|
49
|
+
|
|
50
|
+
def console_colours():
|
|
51
|
+
"""Entry point"""
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
main()
|
|
55
|
+
except KeyboardInterrupt:
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
except BrokenPipeError:
|
|
58
|
+
sys.exit(2)
|
|
59
|
+
|
|
60
|
+
################################################################################
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
|
+
console_colours()
|
|
@@ -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()
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
################################################################################
|
|
4
|
+
""" Thingy docker-purge command
|
|
5
|
+
|
|
6
|
+
Copyright (C) 2017 John Skilleter
|
|
7
|
+
|
|
8
|
+
Initial version - contains only basic error checking and limited debug output.
|
|
9
|
+
"""
|
|
10
|
+
################################################################################
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import re
|
|
14
|
+
import argparse
|
|
15
|
+
|
|
16
|
+
import thingy.logger as logger
|
|
17
|
+
import thingy.docker as docker
|
|
18
|
+
|
|
19
|
+
################################################################################
|
|
20
|
+
|
|
21
|
+
def initialise():
|
|
22
|
+
""" Parse the command line """
|
|
23
|
+
|
|
24
|
+
parser = argparse.ArgumentParser(description='Purge docker instances and images')
|
|
25
|
+
|
|
26
|
+
parser.add_argument('-s', '--stop', action='store_true', help='Stop Docker instances')
|
|
27
|
+
parser.add_argument('-k', '--kill', action='store_true', help='Kill Docker instances')
|
|
28
|
+
parser.add_argument('-r', '--remove', action='store_true', help='Remove Docker images')
|
|
29
|
+
parser.add_argument('-l', '--list', action='store_true', help='List what would be done without doing it')
|
|
30
|
+
parser.add_argument('-f', '--force', action='store_true', help='Forcibly kill/remove instances')
|
|
31
|
+
parser.add_argument('--debug', action='store_true', help='Enable debug output')
|
|
32
|
+
parser.add_argument('images', nargs='*', help='List of Docker containers (regular expression)')
|
|
33
|
+
|
|
34
|
+
args = parser.parse_args()
|
|
35
|
+
|
|
36
|
+
# Configure logging
|
|
37
|
+
|
|
38
|
+
log = logger.init('git-prompt')
|
|
39
|
+
|
|
40
|
+
if args.debug:
|
|
41
|
+
log.setLevel(logger.DEBUG)
|
|
42
|
+
log.info('Debug logging enabled')
|
|
43
|
+
|
|
44
|
+
# Default is to stop matching images
|
|
45
|
+
|
|
46
|
+
if not args.stop and not args.kill and not args.remove:
|
|
47
|
+
args.stop = True
|
|
48
|
+
|
|
49
|
+
# Default is to match all containers
|
|
50
|
+
|
|
51
|
+
if not args.images:
|
|
52
|
+
args.images = '.*'
|
|
53
|
+
else:
|
|
54
|
+
args.images = '|'.join(args.images)
|
|
55
|
+
|
|
56
|
+
log.info('Arguments: %s', args)
|
|
57
|
+
return args
|
|
58
|
+
|
|
59
|
+
################################################################################
|
|
60
|
+
|
|
61
|
+
def main(args):
|
|
62
|
+
""" Main code """
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
if args.stop or args.kill:
|
|
66
|
+
for instance in docker.instances():
|
|
67
|
+
if re.match(args.images, instance):
|
|
68
|
+
|
|
69
|
+
print('Stopping instance: %s' % instance)
|
|
70
|
+
|
|
71
|
+
if not args.list:
|
|
72
|
+
docker.stop(instance, force=args.force)
|
|
73
|
+
|
|
74
|
+
if args.kill:
|
|
75
|
+
for instance in docker.instances(all=True):
|
|
76
|
+
if re.match(args.images, instance):
|
|
77
|
+
|
|
78
|
+
print('Removing instance: %s' % instance)
|
|
79
|
+
|
|
80
|
+
if not args.list:
|
|
81
|
+
docker.rm(instance)
|
|
82
|
+
|
|
83
|
+
if args.remove:
|
|
84
|
+
for image in docker.images():
|
|
85
|
+
if re.match(args.images, image):
|
|
86
|
+
|
|
87
|
+
print('Removing image: %s' % image)
|
|
88
|
+
|
|
89
|
+
if not args.list:
|
|
90
|
+
docker.rmi(image, force=args.force)
|
|
91
|
+
|
|
92
|
+
except docker.DockerError as exc:
|
|
93
|
+
sys.stderr.write(str(exc))
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
################################################################################
|
|
97
|
+
|
|
98
|
+
def docker_purge():
|
|
99
|
+
"""Entry point"""
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
config = initialise()
|
|
103
|
+
main(config)
|
|
104
|
+
|
|
105
|
+
except KeyboardInterrupt:
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
except BrokenPipeError:
|
|
108
|
+
sys.exit(2)
|
|
109
|
+
|
|
110
|
+
################################################################################
|
|
111
|
+
|
|
112
|
+
if __name__ == '__main__':
|
|
113
|
+
docker_purge()
|