├── .gitignore ├── LICENSE ├── README.md ├── bcache-status ├── biggest-files ├── composer ├── cp2 ├── dirsize ├── do-management-script-updates ├── do-package-upgrades ├── filesize ├── fix-file-permissions ├── incus-backup-zfs ├── incus-network-forward ├── killpts ├── ll ├── lxd-attach ├── lxd-backup ├── lxd-backup-all ├── lxd-btrfs-manager ├── lxd-create ├── lxd-exec-all ├── lxd-file-delete-all ├── lxd-file-push-all ├── lxd-kill-container ├── lxd-move-container-storage ├── lxd-portforward ├── lxd-upgrade-all ├── open-ports ├── random-string ├── remove-old-kernel-versions ├── remove-old-snap-revisions ├── screen-open ├── service-restarted-since ├── set-timezone ├── uptimerobot-monitor-status ├── zfs-iotop └── zfs-list /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janxb/ServerUtils/11c39ae54888b19c5b37a2504bd1ca897dfd14be/.gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ServerUtils 2 | 3 | Here you can find multiple scripts for managing ubuntu server systems. I am using those on multiple systems, so most of them are in active development. 4 | 5 | Special attention is being spend on all the LXD-related stuff, those are in active use and developed further every time I see the need for improvement or find a bug. 6 | 7 | If you are using those scripts and see something which does look wrong, feel free to create an issue or submit a pull request! I will happily get in touch with you to get it merged. 8 | -------------------------------------------------------------------------------- /bcache-status: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Dumb script to dump (some) of bcache status 4 | # Copyright 2013 Darrick J. Wong. All rights reserved. 5 | # 6 | # This file is part of Bcache. Bcache is free software: you can 7 | # redistribute it and/or modify it under the terms of the GNU General Public 8 | # License as published by the Free Software Foundation, version 2. 9 | # 10 | # This program is distributed in the hope that it will be useful, but WITHOUT 11 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | # details. 14 | # 15 | # You should have received a copy of the GNU General Public License along with 16 | # this program; if not, write to the Free Software Foundation, Inc., 51 17 | # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | # 19 | 20 | import os 21 | import sys 22 | import argparse 23 | from datetime import timedelta, datetime 24 | from time import sleep 25 | 26 | MAX_KEY_LENGTH = 28 27 | DEV_BLOCK_PATH = '/dev/block/' 28 | SYSFS_BCACHE_PATH = '/sys/fs/bcache/' 29 | SYSFS_BLOCK_PATH = '/sys/block/' 30 | 31 | def file_to_lines(fname): 32 | try: 33 | with open(fname, "r") as fd: 34 | return fd.readlines() 35 | except: 36 | return [] 37 | 38 | def file_to_line(fname): 39 | ret = file_to_lines(fname) 40 | if ret: 41 | return ret[0].strip() 42 | return '' 43 | 44 | def str_to_bool(x): 45 | return x == '1' 46 | 47 | def format_sectors(x): 48 | '''Pretty print a sector count.''' 49 | sectors = int(x) 50 | asectors = abs(sectors) 51 | 52 | if asectors == 0: 53 | return '0B' 54 | elif asectors < 2048: 55 | return '%.2fKiB' % (sectors / 2) 56 | elif asectors < 2097152: 57 | return '%.2fMiB' % (sectors / 2048) 58 | elif asectors < 2147483648: 59 | return '%.2fGiB' % (sectors / 2097152) 60 | else: 61 | return '%.2fTiB' % (sectors / 2147483648) 62 | 63 | def interpret_sectors(x): 64 | '''Interpret a pretty-printed disk size.''' 65 | factors = { 66 | 'k': 1 << 10, 67 | 'M': 1 << 20, 68 | 'G': 1 << 30, 69 | 'T': 1 << 40, 70 | 'P': 1 << 50, 71 | 'E': 1 << 60, 72 | 'Z': 1 << 70, 73 | 'Y': 1 << 80, 74 | } 75 | 76 | if len(x)>0: 77 | factor = 1 78 | if x[-1] in factors: 79 | factor = factors[x[-1]] 80 | x = x[:-1] 81 | return int(float(x) * factor / 512) 82 | else: 83 | return 1 84 | 85 | def pretty_size(x): 86 | return format_sectors(interpret_sectors(x)) 87 | 88 | def pretty_time(sec): 89 | return timedelta(seconds=int(sec)) 90 | 91 | def dump_bdev(bdev_path): 92 | '''Dump a backing device stats.''' 93 | global MAX_KEY_LENGTH, devnum_map 94 | attrs = [ 95 | ('../dev', 'Device', lambda x: '%s (%s)' % (devnum_map.get(x, '?'), x)), 96 | ('../size', 'Size', format_sectors), 97 | ('cache_mode', 'Cache Mode', None), 98 | ('readahead', 'Readahead', None), 99 | ('sequential_cutoff', 'Sequential Cutoff', pretty_size), 100 | ('sequential_merge', 'Merge sequential?', str_to_bool), 101 | ('state', 'State', None), 102 | ('writeback_running', 'Writeback?', str_to_bool), 103 | ('dirty_data', 'Dirty Data', pretty_size), 104 | ] 105 | 106 | print('--- Backing Device ---') 107 | for (sysfs_name, display_name, conversion_func) in attrs: 108 | val = file_to_line('%s/%s' % (bdev_path, sysfs_name)) 109 | if conversion_func is not None: 110 | val = conversion_func(val) 111 | if display_name is None: 112 | display_name = sysfs_name 113 | print(' %-*s%s' % (MAX_KEY_LENGTH - 2, display_name, val)) 114 | 115 | def dump_cachedev(cachedev_path): 116 | '''Dump a cachding device stats.''' 117 | def fmt_cachesize(val): 118 | return '%s\t(%d%%)' % (format_sectors(val), float(val) / cache_size * 100) 119 | 120 | global MAX_KEY_LENGTH, devnum_map 121 | attrs = [ 122 | ('../dev', 'Device', lambda x: '%s (%s)' % (devnum_map.get(x, '?'), x)), 123 | ('../size', 'Size', format_sectors), 124 | ('block_size', 'Block Size', pretty_size), 125 | ('bucket_size', 'Bucket Size', pretty_size), 126 | ('cache_replacement_policy', 'Replacement Policy', None), 127 | ('discard', 'Discard?', str_to_bool), 128 | ('io_errors', 'I/O Errors', None), 129 | ('metadata_written', 'Metadata Written', pretty_size), 130 | ('written', 'Data Written', pretty_size), 131 | ('nbuckets', 'Buckets', None), 132 | (None, 'Cache Used', lambda x: fmt_cachesize(used_sectors)), 133 | (None, 'Cache Unused', lambda x: fmt_cachesize(unused_sectors)), 134 | ] 135 | 136 | stats = get_cache_priority_stats(cachedev_path) 137 | cache_size = int(file_to_line('%s/../size' % cachedev_path)) 138 | unused_sectors = float(stats['Unused'][:-1]) * cache_size / 100 139 | used_sectors = cache_size - unused_sectors 140 | 141 | print('--- Cache Device ---') 142 | for (sysfs_name, display_name, conversion_func) in attrs: 143 | if sysfs_name is not None: 144 | val = file_to_line('%s/%s' % (cachedev_path, sysfs_name)) 145 | if conversion_func is not None: 146 | val = conversion_func(val) 147 | if display_name is None: 148 | display_name = sysfs_name 149 | print(' %-*s%s' % (MAX_KEY_LENGTH - 2, display_name, val)) 150 | 151 | def hits_to_str(hits_str, misses_str): 152 | '''Render a hits/misses ratio as a string.''' 153 | hits = int(hits_str) 154 | misses = int(misses_str) 155 | 156 | ret = '%d' % hits 157 | if hits + misses != 0: 158 | ret = '%s\t(%.d%%)' % (ret, 100 * hits / (hits + misses)) 159 | return ret 160 | 161 | def dump_stats(sysfs_path, indent_str, stats): 162 | '''Dump stats on a bcache device.''' 163 | stat_types = [ 164 | ('five_minute', 'Last 5min'), 165 | ('hour', 'Last Hour'), 166 | ('day', 'Last Day'), 167 | ('total', 'Total'), 168 | ] 169 | attrs = ['bypassed', 'cache_bypass_hits', 'cache_bypass_misses', 'cache_hits', 'cache_misses'] 170 | display = [ 171 | ('Hits', lambda: hits_to_str(stat_data['cache_hits'], stat_data['cache_misses'])), 172 | ('Misses', lambda: stat_data['cache_misses']), 173 | ('Bypass Hits', lambda: hits_to_str(stat_data['cache_bypass_hits'], stat_data['cache_bypass_misses'])), 174 | ('Bypass Misses', lambda: stat_data['cache_bypass_misses']), 175 | ('Bypassed', lambda: pretty_size(stat_data['bypassed'])), 176 | ] 177 | 178 | for (sysfs_name, stat_display_name) in stat_types: 179 | if len(stats) > 0 and sysfs_name not in stats: 180 | continue 181 | stat_data = {} 182 | for attr in attrs: 183 | val = file_to_line('%s/stats_%s/%s' % (sysfs_path, sysfs_name, attr)) 184 | stat_data[attr] = val 185 | for (display_name, str_func) in display: 186 | d = '%s%s %s' % (indent_str, stat_display_name, display_name) 187 | print('%-*s%s' % (MAX_KEY_LENGTH, d, str_func())) 188 | 189 | def get_cache_priority_stats(cache): 190 | '''Retrieve priority stats from a cache.''' 191 | attrs = {} 192 | 193 | for line in file_to_lines('%s/priority_stats' % cache): 194 | x = line.split() 195 | key = x[0] 196 | value = x[1] 197 | attrs[key[:-1]] = value 198 | return attrs 199 | 200 | def dump_bcache(bcache_sysfs_path, stats, print_subdevices, devices): 201 | '''Dump bcache stats''' 202 | global devnum_map 203 | def fmt_cachesize(val): 204 | return '%s\t(%d%%)' % (format_sectors(val), 100.0 * val / cache_sectors) 205 | 206 | attrs_cache = [ 207 | (None, 'UUID', lambda x: os.path.basename(bcache_sysfs_path)), 208 | ('block_size', 'Block Size', pretty_size), 209 | ('bucket_size', 'Bucket Size', pretty_size), 210 | ('congested', 'Congested?', str_to_bool), 211 | ('congested_read_threshold_us', 'Read Congestion', lambda x: '%.1fms' % (int(x) / 1000)), 212 | ('congested_write_threshold_us', 'Write Congestion', lambda x: '%.1fms' % (int(x) / 1000)), 213 | (None, 'Total Cache Size', lambda x: format_sectors(cache_sectors)), 214 | (None, 'Total Cache Used', lambda x: fmt_cachesize(cache_used_sectors)), 215 | (None, 'Total Cache Unused', lambda x: fmt_cachesize(cache_unused_sectors)), 216 | ('btree_cache_size', 'BTree Cache Size', pretty_size), 217 | ('cache_available_percent', 'Evictable Cache', lambda x: '%s\t(%s%%)' % (format_sectors(float(x) * cache_sectors / 100), x)), 218 | (None, 'Replacement Policy', lambda x: replacement_policies.pop() if len(replacement_policies) == 1 else '(Unknown)'), 219 | ] 220 | attrs_bdev = [ 221 | ('dev/dev', 'Device', lambda x: '%s (%s)' % (devnum_map.get(x, '?'), x)), 222 | ('cache_mode', 'Cache Mode', None), 223 | ('dev/size', 'Total Size', format_sectors), 224 | ('dirty_data', 'Dirty Data', pretty_size), 225 | ('stripe_size', 'Stripe Size', pretty_size), 226 | ('sequential_cutoff', 'Sequential Cutoff', pretty_size), 227 | ('readahead_cache_policy', 'Readahead Cache', None), 228 | ('writeback_delay', 'Writeback Delay', pretty_time), 229 | ('writeback_percent', 'Writeback Percent', lambda x: '%s' % x + '%'), 230 | ('writeback_rate', 'Writeback rate', lambda x: '%s sectors' % x), 231 | ] 232 | 233 | bdevs = [] 234 | 235 | # Calculate aggregate data 236 | cache_name = '' 237 | cache_sectors = 0 238 | cache_unused_sectors = 0 239 | cache_modes = set() 240 | replacement_policies = set() 241 | for obj in os.listdir(bcache_sysfs_path): 242 | # print(obj) 243 | if not os.path.isdir('%s/%s' % (bcache_sysfs_path, obj)): 244 | continue 245 | if obj.startswith('cache'): 246 | cache_name = obj 247 | cache_size = int(file_to_line('%s/%s/../size' % (bcache_sysfs_path, obj))) 248 | ### print('%s/%s/../size' % (bcache_sysfs_path, obj)) 249 | cache_sectors += cache_size 250 | cstats = get_cache_priority_stats('%s/%s' % (bcache_sysfs_path, obj)) 251 | unused_size = float(cstats['Unused'][:-1]) * cache_size / 100 252 | cache_unused_sectors += unused_size 253 | replacement_policies.add(file_to_line('%s/%s/cache_replacement_policy' % (bcache_sysfs_path, obj))) 254 | cache_used_sectors = cache_sectors - cache_unused_sectors 255 | elif obj.startswith('bdev'): 256 | ##dump_stats('%s/%s' % (bcache_sysfs_path, obj)) 257 | bdevs.append('%s/%s' % (bcache_sysfs_path, obj)) 258 | bdevs.sort() 259 | 260 | # Dump cache stats 261 | print("--- %s ---" % cache_name) 262 | for (sysfs_name, display_name, conversion_func) in attrs_cache: 263 | if sysfs_name is not None: 264 | val = file_to_line('%s/%s' % (bcache_sysfs_path, sysfs_name)) 265 | else: 266 | val = None 267 | if conversion_func is not None: 268 | val = conversion_func(val) 269 | if display_name is None: 270 | display_name = sysfs_name 271 | print('%-*s%s' % (MAX_KEY_LENGTH, display_name, val)) 272 | dump_stats(bcache_sysfs_path, '', stats) 273 | 274 | for bdev in bdevs: 275 | print("--- %s ---" % os.path.basename(bdev)) 276 | for (sysfs_name, display_name, conversion_func) in attrs_bdev: 277 | if sysfs_name is not None: 278 | val = file_to_line('%s/%s' % (bdev, sysfs_name)) 279 | else: 280 | val = None 281 | if conversion_func is not None: 282 | val = conversion_func(val) 283 | if display_name is None: 284 | display_name = sysfs_name 285 | print('%-*s%s' % (MAX_KEY_LENGTH, display_name, val)) 286 | 287 | dump_stats(bdev, '', stats) 288 | 289 | # Dump sub-device stats 290 | if not print_subdevices: 291 | return 292 | for obj in os.listdir(bcache_sysfs_path): 293 | if not os.path.isdir('%s/%s' % (bcache_sysfs_path, obj)): 294 | continue 295 | if obj.startswith('bdev'): 296 | dump_bdev('%s/%s' % (bcache_sysfs_path, obj)) 297 | dump_stats('%s/%s' % (bcache_sysfs_path, obj), ' ', stats) 298 | elif obj.startswith('cache'): 299 | dump_cachedev('%s/%s' % (bcache_sysfs_path, obj)) 300 | 301 | def map_uuid_to_device(): 302 | '''Map bcache UUIDs to device files.''' 303 | global SYSFS_BLOCK_PATH 304 | ret = {} 305 | 306 | for bdev in os.listdir(SYSFS_BLOCK_PATH): 307 | link = '%s%s/bcache/cache' % (SYSFS_BLOCK_PATH, bdev) 308 | if not os.path.islink(link): 309 | continue 310 | basename = os.path.basename(os.readlink(link)) 311 | ret[basename] = file_to_line('%s%s/dev' % (SYSFS_BLOCK_PATH, bdev)) 312 | return ret 313 | 314 | def get_bcache_devices(): 315 | '''Map bcache UUIDs to device files.''' 316 | global SYSFS_BLOCK_PATH 317 | blist = {} 318 | 319 | for bdev in os.listdir(SYSFS_BLOCK_PATH): 320 | link = '%s%s/bcache/cache' % (SYSFS_BLOCK_PATH, bdev) 321 | if not os.path.islink(link): 322 | continue 323 | basename = os.path.basename(os.readlink(link)) 324 | blist[bdev] = basename 325 | return blist 326 | 327 | 328 | def map_devnum_to_device(): 329 | '''Map device numbers to device files.''' 330 | global DEV_BLOCK_PATH 331 | ret = {} 332 | 333 | for bdev in os.listdir(DEV_BLOCK_PATH): 334 | ret[bdev] = os.path.realpath('%s%s' % (DEV_BLOCK_PATH, bdev)) 335 | return ret 336 | 337 | def main(): 338 | '''Main function''' 339 | global SYSFS_BCACHE_PATH 340 | global uuid_map, devnum_map 341 | stats = set() 342 | reset_stats = False 343 | print_subdevices = False 344 | run_gc = False 345 | 346 | parser = argparse.ArgumentParser(add_help=False) 347 | parser.add_argument('--help', help='show this help message and exit', action='store_true') 348 | parser.add_argument('-f', '--five-minute', help='Print the last five minutes of stats.', action='store_true') 349 | parser.add_argument('-h', '--hour', help='Print the last hour of stats.', action='store_true') 350 | parser.add_argument('-d', '--day', help='Print the last day of stats.', action='store_true') 351 | parser.add_argument('-t', '--total', help='Print total stats.', action='store_true') 352 | parser.add_argument('-a', '--all', help='Print all stats.', action='store_true') 353 | parser.add_argument('-r', '--reset-stats', help='Reset stats after printing them.', action='store_true') 354 | parser.add_argument('-s', '--sub-status', help='Print subdevice status.', action='store_true') 355 | parser.add_argument('-g', '--gc', help='Invoke GC before printing status.', action='store_true') 356 | parser.add_argument('-R', '--repeat', help='Run periodically.', action='store_true') 357 | parser.add_argument('-I', '--interval', help='Update interval.', dest = 'interval', type = int, default = 3600) 358 | 359 | args = parser.parse_args() 360 | 361 | if args.help: 362 | parser.print_help() 363 | return 0 364 | 365 | if args.five_minute: 366 | stats.add('five_minute') 367 | if args.hour: 368 | stats.add('hour') 369 | if args.day: 370 | stats.add('day') 371 | if args.total: 372 | stats.add('total') 373 | if args.all: 374 | stats.add('five_minute') 375 | stats.add('hour') 376 | stats.add('day') 377 | stats.add('total') 378 | if args.reset_stats: 379 | reset_stats = True 380 | if args.sub_status: 381 | print_subdevices = True 382 | if args.gc: 383 | run_gc = True 384 | 385 | if not stats: 386 | stats.add('total') 387 | 388 | uuid_map = map_uuid_to_device() 389 | devnum_map = map_devnum_to_device() 390 | bcache_devices = get_bcache_devices() 391 | 392 | while True: 393 | 394 | now = datetime.now() 395 | print (now.strftime("============ %Y-%m-%d %H:%M:%S ==============")) 396 | 397 | for cache in os.listdir(SYSFS_BCACHE_PATH): 398 | if not os.path.isdir('%s%s' % (SYSFS_BCACHE_PATH, cache)): 399 | continue 400 | 401 | devices = [] 402 | for bcache, cset in bcache_devices.items(): 403 | if cset == cache: 404 | devices.append(bcache) 405 | 406 | if run_gc: 407 | with open('%s%s/internal/trigger_gc' % (SYSFS_BCACHE_PATH, cache), 'w') as fd: 408 | fd.write('1\n') 409 | 410 | dump_bcache('%s%s' % (SYSFS_BCACHE_PATH, cache), stats, print_subdevices, devices) 411 | 412 | if reset_stats: 413 | with open('%s%s/clear_stats' % (SYSFS_BCACHE_PATH, cache), 'w') as fd: 414 | fd.write('1\n') 415 | 416 | if not args.repeat: 417 | break 418 | 419 | sleep(args.interval) 420 | 421 | if __name__ == '__main__': 422 | main() 423 | -------------------------------------------------------------------------------- /biggest-files: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -d $1 ]; then 4 | du -hsx $1/* 2>/dev/null | sort -rh | head -10 5 | else 6 | echo "Directory not found.." 7 | exit 1 8 | fi 9 | exit 0 10 | -------------------------------------------------------------------------------- /composer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | `ls -r /usr/bin/keyhelp-php* 2>/dev/null | head -1` /usr/bin/composer $* 5 | -------------------------------------------------------------------------------- /cp2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | rsync -ah -W --no-compress --inplace --partial --progress "$1" "$2" 5 | -------------------------------------------------------------------------------- /dirsize: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################ 4 | # Script by # 5 | # # 6 | # Script for calculating the size of a folder # 7 | ################################################ 8 | 9 | 10 | if [ -d $1 ]; then 11 | echo "Size of $1: "$(a=(`du --summarize -h $1 2>/dev/null`);echo ${a[0]}) 12 | else 13 | echo "Directory not found.." 14 | exit 1 15 | fi 16 | exit 0 17 | -------------------------------------------------------------------------------- /do-management-script-updates: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$(dirname "$0")" ; pwd -P )" 4 | 5 | git -C $DIR pull 6 | -------------------------------------------------------------------------------- /do-package-upgrades: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | for i in "$@" 4 | do 5 | case $i in 6 | -y) 7 | YES=-y 8 | shift 9 | ;; 10 | *) 11 | echo System package upgrade script 12 | echo -y : always pass yes to upgrade/cleanup command 13 | exit 0 14 | ;; 15 | esac 16 | done 17 | 18 | do-management-script-updates 19 | apt update 20 | apt full-upgrade $YES 21 | apt autoremove $YES 22 | snap refresh 2>/dev/null || true 23 | -------------------------------------------------------------------------------- /filesize: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################ 4 | # Script by # 5 | # # 6 | # Script for calculating the size of a folder # 7 | ################################################ 8 | 9 | 10 | if [ -f $1 ]; then 11 | echo "Size of $1: "$(a=(`du -h $1`);echo ${a[0]}) 12 | else 13 | echo "File not found.." 14 | exit 1 15 | fi 16 | exit 0 17 | -------------------------------------------------------------------------------- /fix-file-permissions: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | find $1 -type d -exec chmod 755 {} \; 5 | find $1 -type f -exec chmod 644 {} \; 6 | -------------------------------------------------------------------------------- /incus-backup-zfs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | function is_available { 5 | command -v $1 >/dev/null 2>&1 || { echo "Required binary '$1' not available." >&2; exit 1; } 6 | } 7 | 8 | is_available yq 9 | is_available jq 10 | is_available numfmt 11 | is_available incus 12 | is_available zfs 13 | is_available pv 14 | is_available grep 15 | is_available awk 16 | 17 | ARGNUM=3 18 | if [ $# -lt $(($ARGNUM)) ] || [ $# -gt $ARGNUM ]; then 19 | COMMAND='help' 20 | else 21 | COMMAND=${1:-help} 22 | CONTAINER=$2 23 | TARGET=$3 24 | 25 | if [ $CONTAINER == '-a' ]; then 26 | if [ ! -d ${TARGET} ]; then 27 | echo "Error: directory '${TARGET}' does not exist." 28 | exit 1 29 | fi 30 | container_list="$(incus list -c ns | awk '!/NAME/{ if ( $4 == "RUNNING" ) print $2}')" 31 | for container in $container_list; do 32 | $0 $COMMAND $container $TARGET/$container.zfs 33 | done 34 | exit 0 35 | fi 36 | 37 | if [[ ! "$CONTAINER" =~ ^[a-zA-Z0-9] ]]; then 38 | echo "Error: container name needs to start with alphanumeric character." 39 | exit 1 40 | fi 41 | 42 | HASH=$(echo $CONTAINER | shasum | cut -b 1-30) 43 | SNAPSHOT_INCUS="$HASH" 44 | SNAPSHOT_ZFS="snapshot-$SNAPSHOT_INCUS" 45 | fi 46 | 47 | STORAGENAME=$(incus profile device get default root pool) 48 | POOLNAME=$(incus storage show $STORAGENAME | grep zfs.pool_name: | awk '{print $2}') 49 | POOLDRIVER=$(incus storage show $STORAGENAME | grep driver: | awk '{print $2}') 50 | 51 | [ "$POOLDRIVER" != 'zfs' ] && echo "Error: default storage pool is of type '$POOLDRIVER', only ZFS is supported." && exit 1 52 | 53 | case "$COMMAND" in 54 | "export") 55 | if [ -d ${TARGET} ]; then 56 | echo "Error: file '${TARGET}' is a directory." 57 | exit 1 58 | fi 59 | incus info $CONTAINER >/dev/null 60 | CONTAINER_STATE=$(incus query /1.0/instances/$CONTAINER | jq .status) 61 | incus snapshot delete $CONTAINER $SNAPSHOT_INCUS 2>/dev/null || true 62 | [ $CONTAINER_STATE == '"Running"' ] && incus stop $CONTAINER 2>/dev/null || true 63 | incus snapshot create --no-expiry $CONTAINER $SNAPSHOT_INCUS 64 | [ $CONTAINER_STATE == '"Running"' ] && incus start $CONTAINER 2>/dev/null || true 65 | SIZE=$(zfs send -Pnc $POOLNAME/containers/$CONTAINER@$SNAPSHOT_ZFS | grep size | awk '{print $2}') 66 | zfs send -c $POOLNAME/containers/$CONTAINER@$SNAPSHOT_ZFS | pv -s $SIZE > ${TARGET} 67 | incus snapshot delete $CONTAINER $SNAPSHOT_INCUS 68 | ;; 69 | "import") 70 | if [ ! -f ${TARGET} ]; then 71 | echo "Error: file '${TARGET}' does not exist." 72 | exit 1 73 | fi 74 | incus create $CONTAINER --empty >/dev/null 75 | pv < ${TARGET} | zfs receive -F $POOLNAME/containers/$CONTAINER 76 | zfs destroy $POOLNAME/containers/$CONTAINER@% 77 | ;; 78 | *) 79 | echo 80 | echo "Wrong parameters! Usage: incus-backup-zfs [ export | import ] [ CONTAINER | -a ] FILENAME" 81 | echo " (-a parameter exports all running containers)" 82 | echo "This script only works for containers on ZFS storage pools and exports to a native ZFS data file." 83 | ;; 84 | esac -------------------------------------------------------------------------------- /incus-network-forward: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | COMMAND=$1 6 | PROTOCOL=$2 7 | EXTERNAL_PORT=$3 8 | CONTAINER_NAME=$4 9 | CONTAINER_PORT=$5 10 | 11 | command -v incus >/dev/null 2>&1 && BINARY=incus || 12 | { 13 | command -v lxd >/dev/null 2>&1 && BINARY=lxc || 14 | { 15 | echo "did not find neither incus nor lxd binary in path." >&2; 16 | exit 1; 17 | } 18 | } 19 | 20 | command -v dig >/dev/null 2>&1 || { echo "Required package 'dnsutils' not available." >&2; exit 1; } 21 | 22 | EXT_IFACE=$(ip -o route show to default | awk '{print $5}' | head -n1) 23 | EXT_IP6ADDR=$(ip -f inet6 -o addr show $EXT_IFACE | cut -d\ -f 7 | cut -d/ -f 1 | head -n 1) 24 | EXT_IP4ADDR=$(ip -f inet -o addr show $EXT_IFACE | cut -d\ -f 7 | cut -d/ -f 1 | head -n 1) 25 | 26 | LXD_IFACE=$($BINARY profile device get default eth0 network) 27 | LXD_IPADDR=$(ip -f inet -o addr show $LXD_IFACE|cut -d\ -f 7 | cut -d/ -f 1) 28 | 29 | if [ -n "${CONTAINER_NAME}" ]; then 30 | IPV4=$(timeout 0.1 dig +short a $CONTAINER_NAME @$LXD_IPADDR || true) 31 | IPV6=$(timeout 0.1 dig +short aaaa $CONTAINER_NAME @$LXD_IPADDR || true) 32 | 33 | if [ -z $IPV4 ] || [ -z $IPV6 ]; then 34 | echo "could not fetch IP address for container $CONTAINER_NAME" 35 | exit 1 36 | fi 37 | fi 38 | 39 | $BINARY network forward create $LXD_IFACE $EXT_IP4ADDR 2>/dev/null || true 40 | $BINARY network forward create $LXD_IFACE $EXT_IP6ADDR 2>/dev/null || true 41 | 42 | case "$COMMAND" in 43 | add) 44 | $BINARY network forward port add $LXD_IFACE $EXT_IP4ADDR $PROTOCOL $EXTERNAL_PORT $IPV4 $CONTAINER_PORT 45 | $BINARY network forward port add $LXD_IFACE $EXT_IP6ADDR $PROTOCOL $EXTERNAL_PORT $IPV6 $CONTAINER_PORT 46 | ;; 47 | clear) 48 | $BINARY network forward port remove $LXD_IFACE $EXT_IP4ADDR --force || true 49 | $BINARY network forward port remove $LXD_IFACE $EXT_IP6ADDR --force || true 50 | ;; 51 | list) 52 | $BINARY network forward show $LXD_IFACE $EXT_IP4ADDR 53 | $BINARY network forward show $LXD_IFACE $EXT_IP6ADDR 54 | ;; 55 | *) 56 | echo "Usage: add {tcp,udp} SOURCEPORT DESTCONTAINER [DESTPORT] | list | clear" 57 | esac 58 | -------------------------------------------------------------------------------- /killpts: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################ 4 | # Script by # 5 | # # 6 | # Script for closing foreign tty-sessions # 7 | ################################################ 8 | 9 | pkill -9 -t pts/$1 10 | if [ $? -eq 0 ]; then 11 | echo "TTY-Session " pts/$1 " closed" 12 | else 13 | echo "Session not found (Hint: Search with )" 14 | exit 1 15 | fi 16 | exit 0 17 | -------------------------------------------------------------------------------- /ll: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LC_ALL=C ls -lah --time-style long-iso "$@" 4 | -------------------------------------------------------------------------------- /lxd-attach: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | lxc exec $1 -- script /dev/null -q -c bash 4 | -------------------------------------------------------------------------------- /lxd-backup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | command -v incus >/dev/null 2>&1 && BINARY=incus || 5 | { 6 | command -v lxd >/dev/null 2>&1 && BINARY=lxc || 7 | { 8 | echo "did not find neither incus nor lxd binary in path." >&2; 9 | exit 1; 10 | } 11 | } 12 | 13 | function log { 14 | echo "$(date '+%Y-%m-%d %H:%M:%S') | $1" 15 | } 16 | 17 | function do_import { 18 | ARGNUM=2 19 | if [ $# -lt $(($ARGNUM-1)) ] || [ $# -gt $ARGNUM ]; then 20 | echo 1>&2 "$0 import {imagePath} {containerName}" 21 | exit 2 22 | fi 23 | 24 | IMPORTPATH=$1 25 | IMPORTFILE=$(basename -- "$IMPORTPATH") 26 | DEFAULTCONTAINER=${IMPORTFILE%%.*} 27 | CONTAINER=${2:-$DEFAULTCONTAINER} 28 | HASH=$(echo $CONTAINER | shasum | cut -b 1-40) 29 | IMAGE="$HASH-import" 30 | 31 | log "importing template $IMPORTPATH as container $CONTAINER" 32 | 33 | log "importing image file" 34 | $BINARY image delete $IMAGE 2>/dev/null || true 35 | $BINARY image import $IMPORTPATH --alias=$IMAGE >/dev/null 36 | 37 | log "creating container" 38 | $BINARY init $IMAGE $CONTAINER >/dev/null || true 39 | 40 | log "cleaning up" 41 | $BINARY image delete $IMAGE 2> /dev/null 42 | 43 | log "done." 44 | 45 | } 46 | 47 | function do_export { 48 | ARGNUM=2 49 | if [ $# -lt $ARGNUM ] || [ $# -gt $ARGNUM ]; then 50 | echo 1>&2 "$0 export {containerName} {exportPath without .tar.gz}" 51 | exit 2 52 | fi 53 | 54 | if $BINARY info $1 2>/dev/null | egrep -iqc "status: running"; then 55 | ISRUNNING=true 56 | else 57 | ISRUNNING=false 58 | fi 59 | 60 | CONTAINER=$1 61 | EXPORTPATH=${2%/} 62 | if [[ -d $EXPORTPATH ]]; then 63 | EXPORTPATH+="/$CONTAINER" 64 | fi 65 | HASH=$(echo $CONTAINER | shasum | cut -b 1-40) 66 | IMAGE="$HASH-export" 67 | SNAPSHOT="$HASH-export" 68 | 69 | touch $EXPORTPATH.tmp 70 | rm $EXPORTPATH.tmp 71 | 72 | log "exporting container $CONTAINER to $EXPORTPATH.tar.gz" 73 | 74 | log "preparing snapshot" 75 | $BINARY delete $CONTAINER/$SNAPSHOT 2>/dev/null || true 76 | $BINARY image delete $IMAGE 2>/dev/null || true 77 | 78 | if [ "$ISRUNNING" = true ] ; then 79 | log "stopping container" 80 | $BINARY stop $CONTAINER 2>/dev/null 81 | fi 82 | 83 | log "creating snapshot" 84 | $BINARY snapshot $CONTAINER $SNAPSHOT >/dev/null 85 | 86 | if [ "$ISRUNNING" = true ] ; then 87 | log "starting container" 88 | $BINARY start $CONTAINER 89 | fi 90 | 91 | log "creating image" 92 | $BINARY publish $CONTAINER/$SNAPSHOT --alias=$IMAGE description="Exported from $CONTAINER at $(date '+%Y-%m-%d %H:%M')" >/dev/null 93 | 94 | log "exporting image" 95 | $BINARY image export $IMAGE $EXPORTPATH >/dev/null 96 | 97 | log "cleaning up" 98 | $BINARY delete $CONTAINER/$SNAPSHOT 99 | $BINARY image delete $IMAGE 100 | 101 | log "done." 102 | } 103 | 104 | function do_clean { 105 | ARGNUM=1 106 | if [ $# -lt $ARGNUM ] || [ $# -gt $ARGNUM ]; then 107 | echo 1>&2 "$0 clean {containerName}" 108 | exit 2 109 | fi 110 | 111 | CONTAINER=$1 112 | EXPORTPATH=$2 113 | HASH=$(echo $CONTAINER | shasum | cut -b 1-40) 114 | IMAGE="$HASH-export" 115 | 116 | log "Cleaning backup data for container $CONTAINER" 117 | 118 | log "Removing container snapshot.." 119 | $BINARY delete $CONTAINER/$IMAGE 2>/dev/null || true 120 | log "Removing image.." 121 | $BINARY image delete $IMAGE 2>/dev/null || true 122 | log "done." 123 | } 124 | 125 | case $1 in 126 | "clean") 127 | do_clean $2 128 | ;; 129 | "export") 130 | do_export $2 $3 131 | ;; 132 | "import") 133 | do_import $2 $3 134 | ;; 135 | *) 136 | echo "Wrong Parameters! Usage: [ export | import | clean | help ]" 137 | ;; 138 | esac 139 | -------------------------------------------------------------------------------- /lxd-backup-all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then 5 | echo "Usage: [UPTIMEROBOT_API_KEY]" 6 | exit 1 7 | fi 8 | 9 | UR_API_KEY=${3:-""} 10 | NUM_KEEP_BACKUPS=$2 11 | BACKUP_PATH=${1%/} 12 | RESTART_SLEEP_SECONDS=300 13 | 14 | if [ ${#BACKUP_PATH} -lt 5 ]; then 15 | echo "Error: please provide a backup path minimum 5 characters long" 16 | exit 1 17 | fi 18 | 19 | function u { 20 | if [ ! -z "$UR_API_KEY" ]; then 21 | uptimerobot-monitor-status --key=$UR_API_KEY --$1 --monitor="$2" 22 | fi 23 | } 24 | 25 | function c { 26 | EXPORT_PATH=$BACKUP_PATH/$1 27 | u pause $1 28 | savelog -n -l -c 100 $EXPORT_PATH.tar.gz >/dev/null 29 | for i in $(seq $(expr $NUM_KEEP_BACKUPS - 1) 100); do rm $EXPORT_PATH.tar.gz.$i &>/dev/null || true; done 30 | lxd-backup export $1 $EXPORT_PATH 31 | sleep $RESTART_SLEEP_SECONDS && u resume $1 & 32 | } 33 | 34 | container_list="$(lxc list -c ns | awk '!/NAME/{ if ( $4 == "RUNNING" ) print $2}')" 35 | echo Backing up all running LXD containers to $BACKUP_PATH 36 | 37 | for container in $container_list; do 38 | c $container 39 | done 40 | 41 | sleep $RESTART_SLEEP_SECONDS && u resume "" 42 | 43 | wait 44 | -------------------------------------------------------------------------------- /lxd-btrfs-manager: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | set -e 4 | 5 | ARG_SELF=$0 6 | 7 | while [ $# -gt 0 ]; do 8 | case "$1" in 9 | --pool=*) 10 | ARG_POOL="${1#*=}" 11 | ;; 12 | --path=*) 13 | ARG_PATH="${1#*=}" 14 | ;; 15 | mount|unmount|list|fstrim|defrag|size) 16 | COMMAND="$1" 17 | ;; 18 | *) 19 | echo " 20 | LXD BTRFS Manager 21 | Usage: 22 | lxd-btrfs-manager mount [--pool=] 23 | lxd-btrfs-manager unmount [--pool=] 24 | lxd-btrfs-manager list [--pool=] 25 | lxd-btrfs-manager size [--pool=] 26 | lxd-btrfs-manager fstrim [--pool=] 27 | lxd-btrfs-manager defrag [--pool=] [--path=] 28 | 29 | Options: 30 | --pool= Select a custom BTRFS pool name. Defaults to first found pool if empty. 31 | --path= Only defrag the specified sub-path. Defaults to the complete pool if empty. 32 | " 33 | exit 1 34 | esac 35 | shift 36 | done 37 | 38 | ROOT_FS_TYPE=$(df -Th / | awk 'FNR == 2 {print $2}') 39 | 40 | if [ -z "$ARG_POOL" ]; then 41 | if [ $ROOT_FS_TYPE == 'btrfs' ]; then 42 | ARG_POOL='/' 43 | else 44 | ARG_POOL=$(lxc storage list | awk 'FNR == 4 {print $6}'); 45 | fi 46 | fi 47 | 48 | if [ -z "$ARG_PATH" ]; then ARG_PATH="/"; fi 49 | 50 | btrfs filesystem show $ARG_POOL >/dev/null 51 | 52 | LOOPFILE_PATH=$(btrfs filesystem show $ARG_POOL | awk 'FNR == 3 {print $8}') 53 | IMAGEFILE_PATH=$(losetup | grep $LOOPFILE_PATH | awk 'FNR == 1 {print $6}') 54 | BTRFS_UUID=$(btrfs filesystem show $ARG_POOL | awk 'FNR == 1 {print $4}') 55 | TEMP_MOUNT_PATH=/tmp/btrfs_$BTRFS_UUID 56 | 57 | if [ $ROOT_FS_TYPE == 'btrfs' ]; then TEMP_MOUNT_PATH='/'; fi 58 | 59 | case "$COMMAND" in 60 | mount) 61 | if [ $ROOT_FS_TYPE != 'btrfs' ]; then 62 | mkdir $TEMP_MOUNT_PATH 63 | mount $LOOPFILE_PATH $TEMP_MOUNT_PATH 64 | fi 65 | echo "Mounted LXD storage $ARG_POOL on $TEMP_MOUNT_PATH" 66 | ;; 67 | unmount) 68 | if [ $ROOT_FS_TYPE != 'btrfs' ]; then 69 | umount $TEMP_MOUNT_PATH 70 | rmdir $TEMP_MOUNT_PATH 71 | fi 72 | echo "Unmounted LXD storage $ARG_POOL from $TEMP_MOUNT_PATH" 73 | ;; 74 | list) 75 | $ARG_SELF mount --pool=$ARG_POOL 76 | btrfs subvolume list -t --sort=path $TEMP_MOUNT_PATH 77 | df -h $TEMP_MOUNT_PATH 78 | $ARG_SELF unmount --pool=$ARG_POOL 79 | if [ -n "$IMAGEFILE_PATH" ]; then filesize $IMAGEFILE_PATH; fi 80 | lxc storage info default | head -n6 | tail -n2 | sed -e 's/^[[:space:]]*//' 81 | btrfs filesystem show $ARG_POOL 82 | ;; 83 | size) 84 | $ARG_SELF mount --pool=$ARG_POOL 85 | lxc storage info default | head -n6 | tail -n2 | sed -e 's/^[[:space:]]*//' 86 | btrfs filesystem show $ARG_POOL 87 | $ARG_SELF unmount --pool=$ARG_POOL 88 | ;; 89 | defrag) 90 | $ARG_SELF mount --pool=$ARG_POOL 91 | echo "Defragmenting path $ARG_PATH on BTRFS pool $ARG_POOL.." 92 | btrfs filesystem defragment -r $TEMP_MOUNT_PATH/$ARG_PATH 2>/dev/null || true 93 | $ARG_SELF unmount --pool=$ARG_POOL 94 | echo "Done." 95 | ;; 96 | fstrim) 97 | echo "Trimming BTRFS pool $ARG_POOL.." 98 | $ARG_SELF mount --pool=$ARG_POOL 99 | fstrim -v $TEMP_MOUNT_PATH 100 | sync $TEMP_MOUNT_PATH 101 | $ARG_SELF unmount --pool=$ARG_POOL 102 | if [ -n "$IMAGEFILE_PATH" ]; then 103 | echo "Waiting for BTRFS filesystem sync-to-disk.." 104 | sleep 10 105 | echo "Trimming loop image file.." 106 | fstrim -v $(dirname $IMAGEFILE_PATH) 107 | fi 108 | echo "Done." 109 | ;; 110 | esac 111 | -------------------------------------------------------------------------------- /lxd-create: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | if [ "$#" -ne 3 ]; then 5 | echo "Illegal number of parameters." 6 | echo "Usage: lxd-create OS_FAMILY OS_VERSION CONTAINER_NAME" 7 | echo "Example: lxd-create ubuntu 24.04 c1" 8 | echo "Example: lxd-create fedora 40 c1" 9 | exit 1 10 | fi 11 | 12 | OS_FAMILY=$1 13 | OS_VERSION=$2 14 | CONTAINER_NAME=$3 15 | 16 | case $OS_FAMILY in 17 | ubuntu) 18 | image=ubuntu:$OS_VERSION 19 | ostype=debian 20 | ;; 21 | debian) 22 | image=images:debian/$OS_VERSION 23 | ostype=debian 24 | ;; 25 | fedora) 26 | image=images:$OS_FAMILY/$OS_VERSION 27 | ostype=fedora 28 | ;; 29 | rockylinux | almalinux | oracle) 30 | image=images:$OS_FAMILY/$OS_VERSION 31 | ostype=rhel 32 | ;; 33 | *) 34 | echo "Unsupported OS_TYPE. Supported are: ubuntu, debian, fedora, oracle, rockylinux, almalinux" 35 | exit 2 36 | ;; 37 | esac 38 | 39 | lxc launch $image $CONTAINER_NAME 40 | 41 | echo "Waiting for network.." 42 | sleep 5 43 | 44 | echo "Updating package lists" 45 | case $ostype in 46 | debian) 47 | lxc exec $CONTAINER_NAME -- apt-get update &>/dev/null 48 | ;; 49 | fedora) 50 | lxc exec $CONTAINER_NAME -- dnf check-update &>/dev/null 51 | ;; 52 | rhel) 53 | lxc exec $CONTAINER_NAME -- dnf install epel-release -y &>/dev/null 54 | lxc exec $CONTAINER_NAME -- dnf check-update &>/dev/null 55 | ;; 56 | esac 57 | 58 | case $ostype in 59 | debian) 60 | TOOLS="wget nano git htop iotop iftop net-tools unattended-upgrades" 61 | echo "Installing additional packages ($TOOLS)" 62 | lxc exec $CONTAINER_NAME -- apt-get install $TOOLS -y &>/dev/null 63 | ;; 64 | fedora | rhel) 65 | TOOLS="wget nano git htop iftop" 66 | echo "Installing additional packages ($TOOLS)" 67 | lxc exec $CONTAINER_NAME -- dnf install $TOOLS -y &>/dev/null 68 | ;; 69 | esac 70 | 71 | SU_PATH="/usr/local/sbin" 72 | echo "Fetching ServerUtils into $SU_PATH" 73 | lxc exec $CONTAINER_NAME -- git clone https://github.com/janxb/ServerUtils.git $SU_PATH &>/dev/null 74 | 75 | echo "Upgrading packages" 76 | case $ostype in 77 | debian) 78 | lxc exec $CONTAINER_NAME -- do-package-upgrades -y &>/dev/null 79 | ;; 80 | fedora | rhel) 81 | lxc exec $CONTAINER_NAME -- dnf upgrade -y &>/dev/null 82 | ;; 83 | esac 84 | 85 | echo "Configuring unattended upgrades" 86 | case $ostype in 87 | debian) 88 | TMP_FILENAME="/tmp/"$(random-string) 89 | printf ' 90 | APT::Periodic::Enable "1"; 91 | APT::Periodic::Update-Package-Lists "1"; 92 | APT::Periodic::Download-Upgradeable-Packages "1"; 93 | APT::Periodic::Unattended-Upgrade "1"; 94 | APT::Periodic::AutocleanInterval "14"; 95 | APT::Periodic::Verbose "0"; 96 | ' > $TMP_FILENAME 97 | lxc file push $TMP_FILENAME "$CONTAINER_NAME/etc/apt/apt.conf.d/10periodic" &>/dev/null 98 | lxc file delete "$CONTAINER_NAME/etc/apt/apt.conf.d/20auto-upgrades" &>/dev/null || true 99 | rm $TMP_FILENAME 100 | TMP_FILENAME="/tmp/"$(random-string) 101 | printf ' 102 | Unattended-Upgrade::Origins-Pattern { 103 | "origin=*"; 104 | }; 105 | ' > $TMP_FILENAME 106 | lxc file push $TMP_FILENAME "$CONTAINER_NAME/etc/apt/apt.conf.d/50unattended-upgrades" &>/dev/null 107 | rm $TMP_FILENAME 108 | ;; 109 | fedora | rhel) 110 | lxc exec $CONTAINER_NAME -- dnf install dnf-automatic -y &>/dev/null 111 | lxc exec $CONTAINER_NAME -- systemctl enable --now dnf-automatic-install.timer &>/dev/null 112 | lxc exec $CONTAINER_NAME -- mkdir /etc/systemd/system/dnf-automatic-install.timer.d &>/dev/null 113 | TMP_FILENAME="/tmp/"$(random-string) 114 | printf ' 115 | [Timer] 116 | OnBootSec= 117 | OnCalendar=03:00 118 | ' > $TMP_FILENAME 119 | lxc file push $TMP_FILENAME "$CONTAINER_NAME/etc/systemd/system/dnf-automatic-install.timer.d/time.conf" #&>/dev/null 120 | lxc exec $CONTAINER_NAME -- systemctl daemon-reload &>/dev/null 121 | ;; 122 | esac 123 | 124 | 125 | echo Container created: $CONTAINER_NAME 126 | -------------------------------------------------------------------------------- /lxd-exec-all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Get containers list 4 | clist="$(lxc list -c ns | awk '!/NAME/{ if ( $4 == "RUNNING" ) print $2}')" 5 | 6 | for i in "$@" 7 | do 8 | case $i in 9 | -e=*|--exclude=*) 10 | EXCLUDE_LIST="${i#*=}" 11 | shift 12 | ;; 13 | -h|--help) 14 | echo -e= / --exclude= : exclude comma-separated container names 15 | exit 0 16 | ;; 17 | esac 18 | done 19 | 20 | for c in $clist 21 | do 22 | echo "" 23 | echo "$c" 24 | echo "---------------------------------------------------------" 25 | if [[ $EXCLUDE_LIST =~ (^|,)"$c"(,|$) ]] 26 | then 27 | echo "EXCLUDED" 28 | continue 29 | else 30 | lxc exec $c -- "$@" 31 | fi 32 | done 33 | -------------------------------------------------------------------------------- /lxd-file-delete-all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | echo "deleting file at $1 from all containers" 5 | 6 | container_list="$(lxc list -c ns | awk '!/NAME/{ if ( $4 == "RUNNING" ) print $2}')" 7 | for container in $container_list; do 8 | echo "processing container $container.." 9 | lxc file delete $container/"$1" || true 10 | done 11 | echo "done." -------------------------------------------------------------------------------- /lxd-file-push-all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | echo "pushing local file $1 to all containers, path $2" 5 | 6 | container_list="$(lxc list -c ns | awk '!/NAME/{ if ( $4 == "RUNNING" ) print $2}')" 7 | for container in $container_list; do 8 | echo "processing container $container.." 9 | lxc file push "$1" $container/"$2" || true 10 | done 11 | echo "done." -------------------------------------------------------------------------------- /lxd-kill-container: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | CNAME=$1 5 | 6 | CPID=$(lxc list -c pns $CNAME | awk 'NR==4{print $2}') 7 | if [ -z "$CPID" ]; then 8 | echo "ERROR: container $CNAME is not running." 9 | exit 1; 10 | fi; 11 | 12 | CPARENTPID=$(ps -o ppid= -p $CPID) 13 | 14 | kill -KILL $CPARENTPID 15 | 16 | echo container $CNAME with PID $CPID and parent PID $CPARENTPID successfully killed! 17 | -------------------------------------------------------------------------------- /lxd-move-container-storage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | container=$1 5 | newstorage=$2 6 | 7 | newcontainer=$container-$(random-string 10) 8 | 9 | echo "moving $container as $newcontainer to storage $newstorage" 10 | lxc move $container $newcontainer --storage=$2 11 | 12 | echo "renaming $newcontainer back to $container" 13 | lxc move $newcontainer $container 14 | 15 | echo "starting $container" 16 | lxc start $container 17 | 18 | echo "done." 19 | -------------------------------------------------------------------------------- /lxd-portforward: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | COMMAND=$1 6 | EXTERNAL_PORT=$2 7 | CONTAINER_NAME=$3 8 | CONTAINER_PORT=$4 9 | 10 | EXT_IFACE=$(ip -o route show to default | awk '{print $5}' | head -n1) 11 | LXD_IFACE="lxdbr0" 12 | LXD_IPADDR=$(ip -f inet -o addr show $LXD_IFACE|cut -d\ -f 7 | cut -d/ -f 1) 13 | 14 | if [ -n "${CONTAINER_NAME}" ]; then 15 | IPV4=$(dig +short a $CONTAINER_NAME @$LXD_IPADDR) 16 | IPV6=[$(dig +short aaaa $CONTAINER_NAME @$LXD_IPADDR)] 17 | fi 18 | 19 | case "$COMMAND" in 20 | add) 21 | nft add table ip nat 22 | nft "add chain ip nat prerouting { type nat hook prerouting priority -100; }" 23 | nft add table ip6 nat 24 | nft "add chain ip6 nat prerouting { type nat hook prerouting priority -100; }" 25 | 26 | if [ ! -z $IPV4 ] && [ ! -z $IPV6 ]; then 27 | nft "add rule ip nat prerouting iif $EXT_IFACE meta l4proto {tcp, udp} th dport $EXTERNAL_PORT dnat to $IPV4:$CONTAINER_PORT" 28 | #nft "add rule ip nat prerouting iif $EXT_IFACE udp dport { $EXTERNAL_PORT } dnat to $IPV4:$CONTAINER_PORT" 29 | nft "add rule ip6 nat prerouting iif $EXT_IFACE meta l4proto {tcp, udp} th dport $EXTERNAL_PORT dnat to $IPV6:$CONTAINER_PORT" 30 | #nft "add rule ip6 nat prerouting iif $EXT_IFACE udp dport { $EXTERNAL_PORT } dnat to $IPV6:$CONTAINER_PORT" 31 | fi;; 32 | del) 33 | echo "Sorry, but the 'del' command is not implemented for nft forwarding anymore. Please remove all rules and start again." 34 | ;; 35 | delall) 36 | nft delete table ip nat 37 | nft delete table ip6 nat 38 | ;; 39 | list) 40 | nft list ruleset ip | grep dport 41 | nft list ruleset ip6 | grep dport 42 | ;; 43 | *) 44 | echo "Usage: add/del SOURCEPORT DESTCONTAINER DESTPORT" 45 | esac 46 | -------------------------------------------------------------------------------- /lxd-upgrade-all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | _apt="/usr/bin/apt" 4 | _lxc="/usr/bin/lxc" 5 | _awk="/usr/bin/awk" 6 | 7 | 8 | for i in "$@" 9 | do 10 | case $i in 11 | -e=*|--exclude=*) 12 | EXCLUDE_LIST="${i#*=}" 13 | shift 14 | ;; 15 | -y) 16 | YES=-y 17 | shift 18 | ;; 19 | -s) 20 | HOST=true 21 | shift 22 | ;; 23 | -c) 24 | CHECK=true 25 | shift 26 | ;; 27 | *) 28 | echo LXD container upgrade script. 29 | echo -y : always pass yes to upgrade command 30 | echo -e=### : exclude comma-separated container names 31 | echo -s : self / update host system 32 | echo -c : check only / list upgradable packages 33 | exit 0 34 | ;; 35 | esac 36 | done 37 | 38 | if [ "$HOST" = true ] ; then 39 | echo 40 | echo 41 | echo fetching host system package data.. 42 | if [ "$CHECK" != true ] ; then 43 | echo 44 | echo 45 | echo upgrading host system.. 46 | apt -qq $YES full-upgrade 47 | apt -qq $YES autoremove 48 | else 49 | echo 50 | echo 51 | echo checking available host system updates.. 52 | apt list --upgradable 53 | fi 54 | fi 55 | 56 | echo 57 | echo 58 | echo fetching package data.. 59 | lxd-exec-all -e=$EXCLUDE_LIST apt -qq $YES update &>/dev/null 60 | 61 | 62 | if [ "$CHECK" != true ] ; then 63 | echo 64 | echo 65 | echo upgrading containers.. 66 | lxd-exec-all -e=$EXCLUDE_LIST apt -qq $YES full-upgrade 67 | echo 68 | echo 69 | echo cleaning containers.. 70 | lxd-exec-all -e=$EXCLUDE_LIST apt -qq $YES autoremove 71 | else 72 | echo 73 | echo 74 | echo checking available upgrades.. 75 | lxd-exec-all -e=$EXCLUDE_LIST apt list --upgradable 76 | fi 77 | -------------------------------------------------------------------------------- /open-ports: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ss -nlput 4 | -------------------------------------------------------------------------------- /random-string: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w ${1:-32} | head -n 1 5 | -------------------------------------------------------------------------------- /remove-old-kernel-versions: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | dpkg --list | grep 'linux-image' | awk '{ print $2 }' | sort -V | sed -n '/'`uname -r`'/q;p' | xargs sudo apt-get -y purge 5 | dpkg --list | grep 'linux-headers' | awk '{ print $2 }' | sort -V | sed -n '/'"$(uname -r | sed "s/\([0-9.-]*\)-\([^0-9]\+\)/\1/")"'/q;p' | xargs sudo apt-get -y purge 6 | -------------------------------------------------------------------------------- /remove-old-snap-revisions: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | snap list --all | awk '/disabled/{print $1, $3}' | 5 | while read snapname revision; do 6 | snap remove "$snapname" --revision="$revision" 7 | done 8 | -------------------------------------------------------------------------------- /screen-open: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | screen -DR $1 5 | -------------------------------------------------------------------------------- /service-restarted-since: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | SERVICE_NAME=$1 5 | SINCE_SECONDS=$2 6 | 7 | NOW=$(date "+%s") 8 | 9 | RAW_TIMESTAMP=$(systemctl show ${SERVICE_NAME} --property=ActiveEnterTimestamp | awk -F= '{print $NF}') 10 | 11 | PARSED_TIMESTAMP=$(date --date "${RAW_TIMESTAMP}" "+%s") 12 | 13 | if [ $((NOW - SINCE_SECONDS)) -lt $PARSED_TIMESTAMP ]; then 14 | echo "YES"; 15 | exit 0; 16 | else 17 | echo "NO"; 18 | exit 1; 19 | fi -------------------------------------------------------------------------------- /set-timezone: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | ln -fs /usr/share/zoneinfo/$1 /etc/localtime 5 | dpkg-reconfigure -f noninteractive tzdata 6 | -------------------------------------------------------------------------------- /uptimerobot-monitor-status: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ ! -x "$(command -v jq)" ]; then echo "Required binary 'jq' is missing."; exit 1; fi 5 | if [ ! -x "$(command -v recode)" ]; then echo "Required binary 'recode' is missing."; exit 1; fi 6 | if [ ! -x "$(command -v curl)" ]; then echo "Required binary 'curl' is missing."; exit 1; fi 7 | 8 | while [ $# -gt 0 ]; do 9 | case "$1" in 10 | --key=*) 11 | arg_key="${1#*=}" 12 | ;; 13 | --pause) 14 | arg_pause=true 15 | arg_action="Pausing" 16 | arg_action_numeric=0 17 | ;; 18 | --resume) 19 | arg_resume=true 20 | arg_action="Resuming" 21 | arg_action_numeric=1 22 | ;; 23 | --monitor=*) 24 | arg_monitor="${1#*=}" 25 | ;; 26 | *) 27 | cat << EOF 28 | Uptimerobot monitor status script. List, pause and resume all or specific uptimerobot monitors. 29 | Parameters: 30 | --key= API key of your uptimerobot account 31 | --pause Pause matching monitors 32 | --resume Resume matching monitors 33 | --monitor= (Optional) ID or name of the selected monitor 34 | EOF 35 | exit 1 36 | ;; 37 | esac 38 | shift 39 | done 40 | 41 | if [[ -n $arg_pause && -n $arg_resume ]]; then 42 | echo "Pause and resume can't be used together!" 43 | exit 1 44 | fi 45 | 46 | if [[ -z $arg_key ]]; then 47 | echo "Please provide the uptimerobot API key!" 48 | exit 1 49 | fi 50 | 51 | # if monitors argument is numeric, we can directly search by ID 52 | if [ ! -z "${arg_monitor##*[!0-9]*}" ] 53 | then all_monitors=$(curl -s 'https://api.uptimerobot.com/v2/getMonitors' -d "api_key=$arg_key&format=json&monitors=$arg_monitor") 54 | else all_monitors=$(curl -s 'https://api.uptimerobot.com/v2/getMonitors' -d "api_key=$arg_key&format=json&search=$arg_monitor") 55 | fi 56 | 57 | # wrapper function for filtering the all_monitors list 58 | monitors_filter() { 59 | echo $(echo "$all_monitors" | jq "$1") 60 | } 61 | 62 | monitors_count=$(($(monitors_filter '.monitors | length')-1)) 63 | 64 | for monitor_index in $(seq 0 $monitors_count); do 65 | monitor_id=$(monitors_filter ".monitors[$monitor_index].id") 66 | monitor_name=$(monitors_filter ".monitors[$monitor_index].friendly_name" | recode html..ascii | sed 's/\"//g') 67 | if [[ $(monitors_filter ".monitors[$monitor_index].status") -eq 2 ]] 68 | then monitor_status="Up " 69 | else monitor_status="Down" 70 | fi 71 | 72 | # list monitors 73 | if [ -z $arg_action_numeric ]; then 74 | echo "$monitor_id / $monitor_status / $monitor_name" 75 | 76 | # pause / resume monitors 77 | else 78 | echo -n "$arg_action monitor $monitor_id / $monitor_name" 79 | curl -s 'https://api.uptimerobot.com/v2/editMonitor' -d "api_key=$arg_key&format=json&id=$monitor_id&status=$arg_action_numeric" --output /dev/null 80 | echo ' - Done' 81 | fi 82 | 83 | done 84 | -------------------------------------------------------------------------------- /zfs-iotop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | watch -n 1 zpool iostat -v -y 1 1 -------------------------------------------------------------------------------- /zfs-list: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | TYPE=filesystem,volume 5 | COLUMNS=space,referenced,compressratio 6 | 7 | for i in "$@"; do 8 | case $i in 9 | -v) 10 | TYPE+=,snapshot 11 | shift 12 | ;; 13 | -o) 14 | COLUMNS+=,origin 15 | shift 16 | ;; 17 | -h|--help) 18 | echo " 19 | zfs-list 20 | -o show origin column 21 | -v include snapshots 22 | " 23 | exit 24 | shift 25 | ;; 26 | -*|--*) 27 | echo "Unknown option $i" 28 | exit 1 29 | ;; 30 | *) 31 | ;; 32 | esac 33 | done 34 | 35 | zfs list -o $COLUMNS -t $TYPE 36 | --------------------------------------------------------------------------------