| 1 #!/usr/local/bin/python3 |
1 #!/usr/local/bin/python3 |
| 2 |
2 |
| |
3 # Common modules |
| 3 import os |
4 import os |
| 4 import sys |
5 import sys |
| 5 import logging |
|
| 6 import logging.handlers |
|
| 7 import argparse |
6 import argparse |
| 8 import platform |
7 import platform |
| 9 |
8 import branding |
| 10 # |
9 # Own modules needed at this stage |
| 11 # Branding |
10 import locations |
| 12 # |
|
| 13 appname="borgend" |
|
| 14 appname_stylised="Borgend" |
|
| 15 |
|
| 16 # |
|
| 17 # Logging configuration |
|
| 18 # |
|
| 19 |
|
| 20 loglevel=logging.INFO |
|
| 21 logfmt="%(asctime)s:%(levelname)s:%(name)s:%(message)s" |
|
| 22 fifolog_capacity=1000 |
|
| 23 fifolog_fmt="%(asctime)s:%(levelname)s:%(message)s" |
|
| 24 |
|
| 25 # |
|
| 26 # Setup logger, needed by the config module to be loaded next |
|
| 27 # |
|
| 28 |
|
| 29 logger=logging.getLogger(appname) |
|
| 30 logger.handlers.clear() |
|
| 31 logger.setLevel(loglevel) |
|
| 32 stderrlog=logging.StreamHandler() |
|
| 33 stderrlog.setLevel(logging.WARNING) |
|
| 34 logger.addHandler(stderrlog) |
|
| 35 logger.propagate=True |
|
| 36 |
|
| 37 # |
|
| 38 # Import our own modules. This needs to be done here |
|
| 39 # for the things above to be available to them |
|
| 40 # |
|
| 41 |
|
| 42 import config |
|
| 43 from scheduler import Scheduler |
|
| 44 from fifolog import FIFOHandler |
|
| 45 from repository import Repository |
|
| 46 |
11 |
| 47 # |
12 # |
| 48 # Argument processing |
13 # Argument processing |
| 49 # |
14 # |
| 50 |
15 |
| 51 def do_args(): |
16 parser=argparse.ArgumentParser( |
| 52 parser=argparse.ArgumentParser( |
17 description=branding.appname_stylised + ": BorgBackup scheduler, queue, and tray icon.", |
| 53 description=appname_stylised + ': BorgBackup scheduler and tray icon.', |
18 epilog='Configuration file location:\n\n %s\n ' % locations.cfgfile, |
| 54 epilog='Configuration file location:\n\n %s\n ' % config.cfgfile, |
19 formatter_class=argparse.RawDescriptionHelpFormatter) |
| 55 formatter_class=argparse.RawDescriptionHelpFormatter) |
|
| 56 |
20 |
| 57 parser.add_argument( |
21 parser.add_argument( |
| 58 '--no-tray', |
22 '--no-tray', |
| 59 dest='notray', |
23 dest='notray', |
| 60 action='store_true', |
24 action='store_true', |
| 61 help='Do not show the tray icon') |
25 help='Do not show the tray icon') |
| 62 |
26 |
| 63 parser.add_argument( |
27 parser.add_argument( |
| 64 '--debug', |
28 '--debug', |
| 65 dest='debug', |
29 dest='debug', |
| 66 action='store_true', |
30 action='store_true', |
| 67 help='Set logging level to debug') |
31 help='Set logging level to debug') |
| 68 |
32 |
| 69 return parser.parse_args() |
33 args=parser.parse_args() |
| 70 |
34 |
| 71 # |
35 # |
| 72 # Main routine |
36 # Done parsing args, import our own modules, and launch everything |
| 73 # |
37 # |
| 74 |
38 |
| 75 # First, setup our own logging handlers |
39 import branding |
| 76 fifolog=FIFOHandler(fifolog_capacity) |
40 import config |
| 77 logger.addHandler(fifolog) |
41 import dreamtime |
| 78 fifolog.setFormatter(logging.Formatter(fifolog_fmt)) |
42 import loggers |
| |
43 import logging |
| |
44 from scheduler import Scheduler |
| |
45 from repository import Repository |
| |
46 from backup import Backup |
| 79 |
47 |
| 80 if __name__=="__main__": |
48 logger=loggers.mainlogger |
| 81 # Parse args. Let argparse handle errors/exit if there are any |
|
| 82 args= do_args() |
|
| 83 tray = None |
|
| 84 repos=[] |
|
| 85 backups=[] |
|
| 86 |
49 |
| 87 try: |
50 if args.debug: |
| 88 args=do_args() |
51 logger.setLevel(logging.DEBUG) |
| 89 |
52 |
| 90 if args.debug: |
53 tray = None |
| 91 logger.setLevel(logging.DEBUG) |
54 repos=[] |
| |
55 backups=[] |
| 92 |
56 |
| 93 if not os.path.isdir(config.logs_dir): |
57 try: |
| 94 os.makedirs(config.logs_dir) |
58 dreamtime.start_monitoring() |
| 95 |
59 |
| 96 handler=logging.handlers.TimedRotatingFileHandler( |
60 scheduler = Scheduler() |
| 97 os.path.join(config.logs_dir, appname+'.log'), |
61 scheduler.start() |
| 98 when='D', interval=1) |
|
| 99 handler.setFormatter(logging.Formatter(logfmt)) |
|
| 100 logger.addHandler(handler) |
|
| 101 |
62 |
| 102 from threading import Thread |
63 repoconfigs=config.settings['repositories'] |
| 103 from backup import Backup |
|
| 104 from queue import Queue |
|
| 105 import signal, os |
|
| 106 import dreamtime |
|
| 107 |
64 |
| 108 dreamtime.start_monitoring() |
65 for i in range(len(repoconfigs)): |
| |
66 logger.info('Setting up repository %d' % i) |
| |
67 r=Repository(i, repoconfigs[i]) |
| |
68 repos.append(r) |
| 109 |
69 |
| 110 scheduler = Scheduler() |
70 backupconfigs=config.settings['backups'] |
| 111 scheduler.start() |
|
| 112 |
71 |
| 113 repoconfigs=config.settings['repositories'] |
72 for i in range(len(backupconfigs)): |
| |
73 logger.info('Setting up backup set %d' % i) |
| |
74 b=Backup(i, backupconfigs[i], scheduler) |
| |
75 backups.append(b) |
| 114 |
76 |
| 115 for i in range(len(repoconfigs)): |
77 for r in repos: |
| 116 logger.info('Setting up repository %d' % i) |
78 r.start() |
| 117 r=Repository(i, repoconfigs[i]) |
|
| 118 repos.append(r) |
|
| 119 |
79 |
| 120 backupconfigs=config.settings['backups'] |
80 for b in backups: |
| |
81 b.start() |
| 121 |
82 |
| 122 for i in range(len(backupconfigs)): |
83 if args.notray or platform.system()!='Darwin': |
| 123 logger.info('Setting up backup set %d' % i) |
84 # Wait for scheduler to finish |
| 124 b=Backup(i, backupconfigs[i], scheduler) |
85 scheduler.join() |
| 125 backups.append(b) |
86 else: |
| |
87 # Start UI, and let it handle exit control |
| |
88 from ui import BorgendTray |
| |
89 tray=BorgendTray(backups); |
| |
90 tray.run() |
| 126 |
91 |
| 127 for r in repos: |
92 except Exception as err: |
| 128 r.start() |
93 # TODO: Should write errors here to stderr; |
| |
94 # perhaps add an extra stderr logger for error level messages |
| |
95 logger.exception("Exception fell through: exiting") |
| 129 |
96 |
| 130 for b in backups: |
97 finally: |
| 131 b.start() |
98 for b in backups: |
| |
99 b.terminate() |
| 132 |
100 |
| 133 if args.notray or platform.system()!='Darwin': |
101 for r in repos: |
| 134 # Wait for scheduler to finish |
102 r.terminate() |
| 135 scheduler.join() |
|
| 136 else: |
|
| 137 # Start UI, and let it handle exit control |
|
| 138 from ui import BorgendTray |
|
| 139 tray=BorgendTray(backups); |
|
| 140 tray.run() |
|
| 141 |
103 |
| 142 except Exception as err: |
104 if tray: |
| 143 # TODO: Should write errors here to stderr; |
105 tray.quit() |
| 144 # perhaps add an extra stderr logger for error level messages |
106 else: |
| 145 logger.exception("Exception fell through to outer level: exiting") |
107 logging.shutdown() |
| 146 |
108 |
| 147 finally: |
|
| 148 for b in backups: |
|
| 149 b.terminate() |
|
| 150 |
|
| 151 for r in repos: |
|
| 152 r.terminate() |
|
| 153 |
|
| 154 if tray: |
|
| 155 tray.quit() |
|
| 156 else: |
|
| 157 logging.shutdown() |
|
| 158 |
|
| 159 # |
|
| 160 # This shit is fucked, disables ^C etc., and threading doesn't seem to help |
|
| 161 # |
|
| 162 |
|
| 163 # ui_thread=Thread(target=tray.run) |
|
| 164 # ui_thread.daemon=True |
|
| 165 # ui_thread.start() |
|
| 166 |
|
| 167 # def quit_signal_handler(signum, frame): |
|
| 168 # print('Signal handler called with signal %s' % str(signum)) |
|
| 169 # ui_thread.terminate() |
|
| 170 # os.exit() |
|
| 171 |
|
| 172 # signal.signal(signal.SIGTERM, quit_signal_handler) |
|
| 173 # signal.signal(signal.SIGINT, quit_signal_handler) |
|
| 174 |
|