def handle_error(self): """Handle any uncaptured error in the core. Overrides asyncore's handle_error. """ trace = traceback.format_exc() stderr(trace) LOGGER.error('Fatal error in core, please review exception log') # TODO: make not hardcoded logfile = codecs.open( os.path.join(self.config.core.logdir, 'exceptions.log'), 'a', encoding='utf-8' ) logfile.write('Fatal error in core, handle_error() was called\n') logfile.write('last raw line was %s' % self.raw) logfile.write(trace) logfile.write('Buffer:\n') logfile.write(self.buffer) logfile.write('----------------------------------------\n\n') logfile.close() if self.error_count > 10: if (datetime.now() - self.last_error_timestamp).seconds < 5: print >> sys.stderr, "Too many errors, can't continue" os._exit(1) self.last_error_timestamp = datetime.now() self.error_count = self.error_count + 1
def _modules(self): home = os.getcwd() modules_dir = os.path.join(home, 'modules') filenames = sopel.loader.enumerate_modules(self) os.sys.path.insert(0, modules_dir) for name, mod_spec in iteritems(filenames): path, type_ = mod_spec try: module, _ = sopel.loader.load_module(name, path, type_) except Exception as e: filename, lineno = sopel.tools.get_raising_file_and_line() rel_path = os.path.relpath(filename, os.path.dirname(__file__)) raising_stmt = "%s:%d" % (rel_path, lineno) stderr("Error loading %s: %s (%s)" % (name, e, raising_stmt)) else: if hasattr(module, 'configure'): prompt = name + ' module' if module.__doc__: doc = module.__doc__.split('\n', 1)[0] if doc: prompt = doc prompt = 'Configure {} (y/n)? [n]'.format(prompt) do_configure = get_input(prompt) do_configure = do_configure and do_configure.lower() == 'y' if do_configure: module.configure(self) self.save()
def handle_init(options): """Use config wizard to initialize a new configuration file for the bot :param options: parsed arguments :type options: ``argparse.Namespace`` .. note:: Due to how the config wizard works, the configuration filename's extension **must be** ``.cfg``. """ config_filename = utils.find_config( config.DEFAULT_HOMEDIR, getattr(options, 'config', None) or 'default') config_name, ext = os.path.splitext(config_filename) if ext and ext != '.cfg': tools.stderr('Configuration wizard accepts .cfg files only') return 1 elif not ext: config_filename = config_name + '.cfg' if os.path.isfile(config_filename): tools.stderr('Configuration file %s already exists' % config_filename) return 1 print('Starting Sopel config wizard for: %s' % config_filename) config._wizard('all', config_name)
def signal_handler(sig, frame): if sig == signal.SIGUSR1 or sig == signal.SIGTERM or sig == signal.SIGINT: tools.stderr('Got quit signal, shutting down.') p.quit('Closing') elif sig == signal.SIGUSR2 or sig == signal.SIGILL: tools.stderr('Got restart signal.') p.restart('Restarting')
def _timeout_check(self): while self.connected or self.connecting: if (datetime.now() - self.last_ping_time).seconds > int(self.config.core.timeout): stderr('Ping timeout reached after %s seconds, closing connection' % self.config.core.timeout) self.handle_close() break else: time.sleep(int(self.config.core.timeout))
def reload_module_tree(bot, name, seen=None, silent=False): from types import ModuleType old_module = sys.modules[name] if seen is None: seen = {} if name not in seen: seen[name] = [] old_callables = {} for obj_name, obj in iteritems(vars(old_module)): if callable(obj): if (getattr(obj, '__name__', None) == 'shutdown' and obj in bot.shutdown_methods): # If this is a shutdown method, call it first. try: stderr( "calling %s.%s" % ( obj.__module__, obj.__name__, ) ) obj(bot) except Exception as e: stderr( "Error calling shutdown method for module %s:%s" % ( obj.__module__, e ) ) bot.unregister(obj) elif (type(obj) is ModuleType and obj.__name__.startswith(name + '.') and obj.__name__ not in sys.builtin_module_names): # recurse into submodules, see issue 1056 if obj not in seen[name]: seen[name].append(obj) reload(obj) reload_module_tree(bot, obj.__name__, seen, silent) modules = sopel.loader.enumerate_modules(bot.config) if name not in modules: return # Only reload the top-level module, once recursion is finished # Also remove all references to sopel callables from top level of the # module, so that they will not get loaded again if reloading the # module does not override them. for obj_name in old_callables.keys(): delattr(old_module, obj_name) # Also delete the setup function # Sub-modules shouldn't have setup functions, so do after the recursion check if hasattr(old_module, "setup"): delattr(old_module, "setup") path, type_ = modules[name] load_module(bot, name, path, type_, silent)
def handle_close(self): self.connection_registered = False self._shutdown() stderr('Closed!') # This will eventually call asyncore dispatchers close method, which # will release the main thread. This should be called last to avoid # race conditions. self.close()
def wizard(filename): """Global Configuration Wizard :param str filename: name of the new file to be created :return: the created configuration object This wizard function helps the creation of a Sopel configuration file, with its core section and its plugins' sections. """ homedir, basename = os.path.split(filename) if not basename: raise config.ConfigurationError( 'Sopel requires a filename for its configuration, not a directory') try: if not os.path.isdir(homedir): print('Creating config directory at {}'.format(homedir)) os.makedirs(homedir) print('Config directory created') except Exception: tools.stderr('There was a problem creating {}'.format(homedir)) raise name, ext = os.path.splitext(basename) if not ext: # Always add .cfg if filename does not have an extension filename = os.path.join(homedir, name + '.cfg') elif ext != '.cfg': # It is possible to use a non-cfg file for Sopel # but the wizard does not allow it at the moment raise config.ConfigurationError( 'Sopel uses ".cfg" as configuration file extension, not "%s".' % ext) settings = config.Config(filename, validate=False) print("Please answer the following questions " "to create your configuration file (%s):\n" % filename) config.core_section.configure(settings) if settings.option( 'Would you like to see if there are any modules ' 'that need configuring' ): _plugins_wizard(settings) try: settings.save() except Exception: # TODO: Be specific tools.stderr("Encountered an error while writing the config file. " "This shouldn't happen. Check permissions.") raise print("Config file written successfully!") return settings
def _load(bot, plugin): # handle errors while loading (if any) try: plugin.load() if plugin.has_setup(): plugin.setup(bot) plugin.register(bot) except Exception as e: filename, lineno = tools.get_raising_file_and_line() rel_path = os.path.relpath(filename, os.path.dirname(__file__)) raising_stmt = "%s:%d" % (rel_path, lineno) tools.stderr( "Error loading %s: %s (%s)" % (plugin.name, e, raising_stmt)) raise
def command_restart(opts): """Restart a running Sopel instance""" # Get Configuration try: settings = utils.load_settings(opts) except config.ConfigurationNotFound as error: tools.stderr('Configuration "%s" not found' % error.filename) return ERR_CODE if settings.core.not_configured: tools.stderr('Sopel is not configured, can\'t stop') return ERR_CODE # Redirect Outputs utils.redirect_outputs(settings, opts.quiet) # Get Sopel's PID filename = get_pid_filename(opts, settings.core.pid_dir) pid = get_running_pid(filename) if pid is None or not tools.check_pid(pid): tools.stderr('Sopel is not running!') return ERR_CODE tools.stderr('Asking Sopel to restart') if hasattr(signal, 'SIGUSR2'): os.kill(pid, signal.SIGUSR2) else: # Windows will not generate SIGILL itself # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal os.kill(pid, signal.SIGILL)
def initiate_connect(self, host, port): stderr("Connecting to %s:%s..." % (host, port)) source_address = (self.config.core.bind_host, 0) if self.config.core.bind_host else None self.set_socket(socket.create_connection((host, port), source_address=source_address)) if self.config.core.use_ssl and has_ssl: self.send = self._ssl_send self.recv = self._ssl_recv elif not has_ssl and self.config.core.use_ssl: stderr("SSL is not avilable on your system, attempting connection " "without it") self.connect((host, port)) try: asyncore.loop() except KeyboardInterrupt: print("KeyboardInterrupt") self.quit("KeyboardInterrupt")
def _plugins_wizard(settings): usable_plugins = plugins.get_usable_plugins(settings) for plugin, is_enabled in usable_plugins.values(): if not is_enabled: # Do not configure non-enabled modules continue name = plugin.name try: _plugin_wizard(settings, plugin) except Exception as e: filename, lineno = tools.get_raising_file_and_line() rel_path = os.path.relpath(filename, os.path.dirname(__file__)) raising_stmt = "%s:%d" % (rel_path, lineno) tools.stderr("Error loading %s: %s (%s)" % (name, e, raising_stmt))
def error(self, trigger=None): """Called internally when a module causes an error.""" try: trace = traceback.format_exc() if sys.version_info.major < 3: trace = trace.decode("utf-8", errors="xmlcharrefreplace") stderr(trace) try: lines = list(reversed(trace.splitlines())) report = [lines[0].strip()] for line in lines: line = line.strip() if line.startswith('File "'): report.append(line[0].lower() + line[1:]) break else: report.append("source unknown") signature = "%s (%s)" % (report[0], report[1]) # TODO: make not hardcoded log_filename = os.path.join(self.config.core.logdir, "exceptions.log") with codecs.open(log_filename, "a", encoding="utf-8") as logfile: logfile.write("Signature: %s\n" % signature) if trigger: logfile.write( "from {} at {}. Message was: {}\n".format( trigger.nick, str(datetime.now()), trigger.group(0) ) ) logfile.write(trace) logfile.write("----------------------------------------\n\n") except Exception as e: stderr("Could not save full traceback!") LOGGER.error("Could not save traceback from %s to file: %s", trigger.sender, str(e)) if trigger and self.config.core.reply_errors and trigger.sender is not None: self.msg(trigger.sender, signature) if trigger: LOGGER.error("Exception from {}: {} ({})".format(trigger.sender, str(signature), trigger.raw)) except Exception as e: if trigger and self.config.core.reply_errors and trigger.sender is not None: self.msg(trigger.sender, "Got an error.") if trigger: LOGGER.error("Exception from {}: {} ({})".format(trigger.sender, str(e), trigger.raw))
def found_terminator(self): line = self.buffer if line.endswith('\r'): line = line[:-1] self.buffer = '' self.last_ping_time = datetime.now() pretrigger = PreTrigger(self.nick, line) if pretrigger.event == 'PING': self.write(('PONG', pretrigger.args[-1])) elif pretrigger.event == 'ERROR': LOGGER.error("ERROR recieved from server: %s", pretrigger.args[-1]) if self.hasquit: self.close_when_done() elif pretrigger.event == '433': stderr('Nickname already in use!') self.handle_close() self.dispatch(pretrigger)
def found_terminator(self): line = self.buffer if line.endswith("\r"): line = line[:-1] self.buffer = "" self.last_ping_time = datetime.now() pretrigger = PreTrigger(self.nick, line) if all(cap not in self.enabled_capabilities for cap in ["account-tag", "extended-join"]): pretrigger.tags.pop("account", None) if pretrigger.event == "PING": self.write(("PONG", pretrigger.args[-1])) elif pretrigger.event == "ERROR": LOGGER.error("ERROR recieved from server: %s", pretrigger.args[-1]) if self.hasquit: self.close_when_done() elif pretrigger.event == "433": stderr("Nickname already in use!") self.handle_close() self.dispatch(pretrigger)
def found_terminator(self): line = self.buffer if line.endswith('\r'): line = line[:-1] self.buffer = '' self.last_ping_time = datetime.now() pretrigger = PreTrigger(self.nick, line) if all(cap not in self.enabled_capabilities for cap in ['account-tag', 'extended-join']): pretrigger.tags.pop('account', None) if pretrigger.event == 'PING': self.write(('PONG', pretrigger.args[-1])) elif pretrigger.event == 'ERROR': LOGGER.error("ERROR received from server: %s", pretrigger.args[-1]) if self.hasquit: self.close_when_done() elif pretrigger.event == '433': stderr('Nickname already in use!') self.handle_close() self.dispatch(pretrigger)
def check_not_root(): """Check if root is running the bot. It raises a ``RuntimeError`` if the user has root privileges on Linux or if it is the ``Administrator`` account on Windows. """ opersystem = platform.system() if opersystem in ["Linux", "Darwin"]: # Linux/Mac if os.getuid() == 0 or os.geteuid() == 0: raise RuntimeError('Error: Do not run Sopel with root privileges.') elif opersystem in ["Windows"]: # Windows if os.environ.get("USERNAME") == "Administrator": raise RuntimeError('Error: Do not run Sopel as Administrator.') else: tools.stderr( "Warning: %s is an uncommon operating system platform. " "Sopel should still work, but please contact Sopel's developers " "if you experience issues." % opersystem)
def processJoin(bot, trigger): ips = getIPList(trigger.host) for ip in ips: bot.msg(bot.config.killbot.control_channel, 'doing lookup on %s' % ip) blResult = baseRBLLookup(ip) ban = False why = '' for r in blResult: stderr(r) if r[1] != False: if r[1] != None: ban = True why += "HIT: %s %s " % (r[0], r[1]) if ban == True: if trigger.sender == '##politics': bot.write(['MODE', trigger.sender, '+b', '*!*@' + trigger.host]) bot.write(['REMOVE', trigger.sender, trigger.nick], "This host has violated channel policy.") bot.msg(bot.config.killbot.control_channel, 'I would have banned %s on %s because of a blacklist hit.' % (trigger.nick, trigger.sender)) bot.msg(bot.config.killbot.control_channel, why) else: bot.msg(bot.config.killbot.control_channel, '%s is clean.' % trigger.host)
def handle_connect(self): if self.config.core.use_ssl and has_ssl: if not self.config.core.verify_ssl: self.ssl = ssl.wrap_socket(self.socket, do_handshake_on_connect=True, suppress_ragged_eofs=True) else: self.ssl = ssl.wrap_socket( self.socket, do_handshake_on_connect=True, suppress_ragged_eofs=True, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_certs, ) try: ssl.match_hostname(self.ssl.getpeercert(), self.config.core.host) except ssl.CertificateError: stderr("Invalid certficate, hostname mismatch!") os.unlink(self.config.core.pid_file_path) os._exit(1) self.set_socket(self.ssl) # Request list of server capabilities. IRCv3 servers will respond with # CAP * LS (which we handle in coretasks). v2 servers will respond with # 421 Unknown command, which we'll ignore self.write(("CAP", "LS", "302")) if self.config.core.auth_method == "server": password = self.config.core.auth_password self.write(("PASS", password)) self.write(("NICK", self.nick)) self.write(("USER", self.user, "+iw", self.nick), self.name) stderr("Connected.") self.last_ping_time = datetime.now() timeout_check_thread = threading.Thread(target=self._timeout_check) timeout_check_thread.daemon = True timeout_check_thread.start() ping_thread = threading.Thread(target=self._send_ping) ping_thread.daemon = True ping_thread.start()
def command_start(opts): """Start a Sopel instance""" # Step One: Get the configuration file and prepare to run try: config_module = get_configuration(opts) except config.ConfigurationError as e: tools.stderr(e) return ERR_CODE_NO_RESTART if config_module.core.not_configured: tools.stderr('Bot is not configured, can\'t start') return ERR_CODE_NO_RESTART # Step Two: Manage logfile, stdout and stderr utils.redirect_outputs(config_module, opts.quiet) # Step Three: Handle process-lifecycle options and manage the PID file pid_dir = config_module.core.pid_dir pid_file_path = get_pid_filename(opts, pid_dir) pid = get_running_pid(pid_file_path) if pid is not None and tools.check_pid(pid): tools.stderr('There\'s already a Sopel instance running ' 'with this config file.') tools.stderr('Try using either the `sopel stop` ' 'or the `sopel restart` command.') return ERR_CODE if opts.daemonize: child_pid = os.fork() if child_pid is not 0: return with open(pid_file_path, 'w') as pid_file: pid_file.write(str(os.getpid())) # Step Four: Run Sopel ret = run(config_module, pid_file_path) # Step Five: Shutdown Clean-Up os.unlink(pid_file_path) if ret == -1: # Restart os.execv(sys.executable, ['python'] + sys.argv) else: # Quit return ret
def plugins_wizard(filename): """Plugins Configuration Wizard :param str filename: path to an existing Sopel configuration :return: the configuration object This wizard function helps to configure plugins for an existing Sopel config file. """ if not os.path.isfile(filename): raise config.ConfigurationNotFound(filename) settings = config.Config(filename, validate=False) _plugins_wizard(settings) try: settings.save() except Exception: # TODO: Be specific tools.stderr("Encountered an error while writing the config file. " "This shouldn't happen. Check permissions.") raise return settings
def _shutdown(self): stderr("Calling shutdown for %d modules." % (len(self.shutdown_methods),)) for shutdown_method in self.shutdown_methods: try: stderr("calling %s.%s" % (shutdown_method.__module__, shutdown_method.__name__)) shutdown_method(self) except Exception as e: stderr("Error calling shutdown method for module %s:%s" % (shutdown_method.__module__, e))
def main(argv=None): """Sopel run script entry point""" try: # Step One: Parse The Command Line parser = build_parser() # make sure to have an action first (`legacy` by default) # TODO: `start` should be the default in Sopel 8 argv = argv or sys.argv[1:] if not argv: argv = ['legacy'] elif argv[0].startswith('-') and argv[0] not in ['-h', '--help']: argv = ['legacy'] + argv opts = parser.parse_args(argv) # Step Two: "Do not run as root" checks try: check_not_root() except RuntimeError as err: tools.stderr('%s' % err) return ERR_CODE # Step Three: Handle command action = getattr(opts, 'action', 'legacy') command = { 'legacy': command_legacy, 'start': command_start, 'configure': command_configure, 'stop': command_stop, 'restart': command_restart, }.get(action) return command(opts) except KeyboardInterrupt: print("\n\nInterrupted") return ERR_CODE
def run(config, pid_file, daemon=False): import sopel.bot as bot import sopel.web as web import sopel.logger from sopel.tools import stderr delay = 20 # Inject ca_certs from config to web for SSL validation of web requests if not config.core.ca_certs: stderr('Could not open CA certificates file. SSL will not ' 'work properly.') web.ca_certs = config.core.ca_certs def signal_handler(sig, frame): if sig == signal.SIGUSR1 or sig == signal.SIGTERM: stderr('Got quit signal, shutting down.') p.quit('Closing') while True: try: p = bot.Sopel(config, daemon=daemon) if hasattr(signal, 'SIGUSR1'): signal.signal(signal.SIGUSR1, signal_handler) if hasattr(signal, 'SIGTERM'): signal.signal(signal.SIGTERM, signal_handler) sopel.logger.setup_logging(p) p.run(config.core.host, int(config.core.port)) except KeyboardInterrupt: break except Exception: trace = traceback.format_exc() try: stderr(trace) except: pass logfile = open(os.path.join(config.core.logdir, 'exceptions.log'), 'a') logfile.write('Critical exception in core') logfile.write(trace) logfile.write('----------------------------------------\n\n') logfile.close() os.unlink(pid_file) os._exit(1) if not isinstance(delay, int): break if p.hasquit: break stderr('Warning: Disconnected. Reconnecting in %s seconds...' % delay) time.sleep(delay) os.unlink(pid_file) os._exit(0)
def getIPList(hostname): #takes a hostname or ip address, returns list containing #ip addresses that hostname resolves to, or original ip address #always returns a list ipList = [] if hostname.find('/') != -1: #this is a cloak, dns lookups don't matter return ipList if validateIP(hostname) == True: ipList.append('%s' % hostname) return ipList else: stderr('resolving %s' % hostname) try: ipv4 = dnsResolver.query(hostname,'A') except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): ipv4 = [] try: ipv6 = dnsResolver.query(hostname,'AAAA') except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): ipv6 = [] for a in ipv4: stderr(' resolved A record to %s' % a) ipList.append('%s' % a) for aaaa in ipv6: stderr(' resolved AAAA record to %s' % aaaa) ipList.append('%s' % aaaa) return ipList #for some reason we couldn't find anything stderr('getIPList fell through for %s' % hostname) return ipList
def log_raw(self, line, prefix): """Log raw line to the raw log.""" if not self.config.core.log_raw: return if not os.path.isdir(self.config.core.logdir): try: os.mkdir(self.config.core.logdir) except Exception as e: stderr("There was a problem creating the logs directory.") stderr("%s %s" % (str(e.__class__), str(e))) stderr("Please fix this and then run Sopel again.") os._exit(1) f = codecs.open(os.path.join(self.config.core.logdir, "raw.log"), "a", encoding="utf-8") f.write(prefix + unicode(time.time()) + "\t") temp = line.replace("\n", "") f.write(temp) f.write("\n") f.close()
def log_raw(self, line, prefix): """Log raw line to the raw log.""" if not self.config.core.log_raw: return if not os.path.isdir(self.config.core.logdir): try: os.mkdir(self.config.core.logdir) except Exception as e: stderr('There was a problem creating the logs directory.') stderr('%s %s' % (str(e.__class__), str(e))) stderr('Please fix this and then run Sopel again.') os._exit(1) f = codecs.open(os.path.join(self.config.core.logdir, 'raw.log'), 'a', encoding='utf-8') f.write(prefix + str(time.time()) + "\t") temp = line.replace('\n', '') f.write(temp) f.write("\n") f.close()
def _shutdown(self): stderr( 'Calling shutdown for %d modules.' % (len(self.shutdown_methods),) ) for shutdown_method in self.shutdown_methods: try: stderr( "calling %s.%s" % ( shutdown_method.__module__, shutdown_method.__name__, ) ) shutdown_method(self) except Exception as e: stderr( "Error calling shutdown method for module %s:%s" % ( shutdown_method.__module__, e ) ) # Avoid calling shutdown methods if we already have. self.shutdown_methods = []
def handle_get(options): """Read the settings to display the value of <section> <key>""" try: settings = utils.load_settings(options) except Exception as error: tools.stderr(error) return 2 section = options.section option = options.option # Making sure the section.option exists if not settings.parser.has_section(section): tools.stderr('Section "%s" does not exist' % section) return 1 if not settings.parser.has_option(section, option): tools.stderr( 'Section "%s" does not have a "%s" option' % (section, option)) return 1 # Display the value print(settings.get(section, option))
def handle_get(options): """Read the settings to display the value of <section> <key>""" try: settings = utils.load_settings(options) except Exception as error: tools.stderr(error) return 2 section = options.section option = options.option # Making sure the section.option exists if not settings.parser.has_section(section): tools.stderr('Section "%s" does not exist' % section) return 1 if not settings.parser.has_option(section, option): tools.stderr('Section "%s" does not have a "%s" option' % (section, option)) return 1 # Display the value print(settings.get(section, option)) return 0 # successful operation
import argparse import logging import os import platform import signal import sys import time from sopel import __version__, bot, config, logger, tools from . import utils # This is in case someone somehow manages to install Sopel on an old version # of pip (<9.0.0), which doesn't know about `python_requires`, or tries to run # from source on an unsupported version of Python. if sys.version_info < (3, 7): tools.stderr('Error: Sopel requires Python 3.7+.') sys.exit(1) # Py3.7 EOL: https://www.python.org/dev/peps/pep-0537/#and-beyond-schedule if sys.version_info < (3, 8): # TODO check this warning before releasing Sopel 8.0 print( 'Warning: Python 3.7 will reach end of life by June 2022 ' 'and will receive no further updates. ' 'Sopel 9.0 will drop support for it.', file=sys.stderr, ) LOGGER = logging.getLogger(__name__) ERR_CODE = 1
def _shutdown(self): # Stop Job Scheduler stderr('Stopping the Job Scheduler.') self.scheduler.stop() try: self.scheduler.join(timeout=15) except RuntimeError: stderr('Unable to stop the Job Scheduler.') else: stderr('Job Scheduler stopped.') self.scheduler.clear_jobs() # Shutdown plugins stderr('Calling shutdown for %d modules.' % (len(self.shutdown_methods), )) for shutdown_method in self.shutdown_methods: try: stderr("calling %s.%s" % ( shutdown_method.__module__, shutdown_method.__name__, )) shutdown_method(self) except Exception as e: stderr("Error calling shutdown method for module %s:%s" % (shutdown_method.__module__, e)) # Avoid calling shutdown methods if we already have. self.shutdown_methods = []
def startup_reconnect(bot, trigger): # Startup stderr("[Sopel-startupmonologue] " + bot.nick + " has reconnected.") bot.osd(" has reconnected.", bot.channels.keys(), 'ACTION')
def main(argv=None): try: # Step One: Parse The Command Line parser = build_parser() opts = parser.parse_args(argv or None) # Step Two: "Do not run as root" checks try: check_not_root() except RuntimeError as err: stderr('%s' % err) return ERR_CODE # Step Three: Handle "No config needed" options if opts.version: print_version() return if opts.wizard: _wizard('all', opts.config) return if opts.mod_wizard: _wizard('mod', opts.config) return if opts.list_configs: print_config() return # Step Four: Get the configuration file and prepare to run try: config_module = get_configuration(opts) except ConfigurationError as e: stderr(e) return ERR_CODE_NO_RESTART if config_module.core.not_configured: stderr('Bot is not configured, can\'t start') return ERR_CODE_NO_RESTART # Step Five: Manage logfile, stdout and stderr logfile = os.path.os.path.join(config_module.core.logdir, 'stdio.log') sys.stderr = tools.OutputRedirect(logfile, True, opts.quiet) sys.stdout = tools.OutputRedirect(logfile, False, opts.quiet) # Step Six: Handle process-lifecycle options and manage the PID file pid_dir = config_module.core.pid_dir pid_file_path = get_pid_filename(opts, pid_dir) old_pid = get_running_pid(pid_file_path) if old_pid is not None and tools.check_pid(old_pid): if not opts.quit and not opts.kill and not opts.restart: stderr( 'There\'s already a Sopel instance running with this config file' ) stderr( 'Try using either the --quit, --restart, or --kill option') return ERR_CODE elif opts.kill: stderr('Killing the Sopel') os.kill(old_pid, signal.SIGKILL) return elif opts.quit: stderr('Signaling Sopel to stop gracefully') if hasattr(signal, 'SIGUSR1'): os.kill(old_pid, signal.SIGUSR1) else: # Windows will not generate SIGTERM itself # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal os.kill(old_pid, signal.SIGTERM) return elif opts.restart: stderr('Asking Sopel to restart') if hasattr(signal, 'SIGUSR2'): os.kill(old_pid, signal.SIGUSR2) else: # Windows will not generate SIGILL itself # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal os.kill(old_pid, signal.SIGILL) return elif opts.kill or opts.quit or opts.restart: stderr('Sopel is not running!') return ERR_CODE if opts.daemonize: child_pid = os.fork() if child_pid is not 0: return with open(pid_file_path, 'w') as pid_file: pid_file.write(str(os.getpid())) # Step Seven: Initialize and run Sopel ret = run(config_module, pid_file_path) os.unlink(pid_file_path) if ret == -1: os.execv(sys.executable, ['python'] + sys.argv) else: return ret except KeyboardInterrupt: print("\n\nInterrupted") return ERR_CODE
def main(argv=None): global homedir # Step One: Parse The Command Line try: parser = argparse.ArgumentParser(description='Sopel IRC Bot', usage='%(prog)s [options]') parser.add_argument('-c', '--config', metavar='filename', help='use a specific configuration file') parser.add_argument("-d", '--fork', action="store_true", dest="daemonize", help="Daemonize sopel") parser.add_argument("-q", '--quit', action="store_true", dest="quit", help="Gracefully quit Sopel") parser.add_argument("-k", '--kill', action="store_true", dest="kill", help="Kill Sopel") parser.add_argument("-l", '--list', action="store_true", dest="list_configs", help="List all config files found") parser.add_argument("-m", '--migrate', action="store_true", dest="migrate_configs", help="Migrate config files to the new format") parser.add_argument('--quiet', action="store_true", dest="quiet", help="Supress all output") parser.add_argument('-w', '--configure-all', action='store_true', dest='wizard', help='Run the configuration wizard.') parser.add_argument('--configure-modules', action='store_true', dest='mod_wizard', help=( 'Run the configuration wizard, but only for the ' 'module configuration options.')) parser.add_argument('-v', '--version', action="store_true", dest="version", help="Show version number and exit") opts = parser.parse_args() # Step Two: "Do not run as root" checks. try: # Linux/Mac if os.getuid() == 0 or os.geteuid() == 0: stderr('Error: Do not run Sopel with root privileges.') sys.exit(1) except AttributeError: # Windows if os.environ.get("USERNAME") == "Administrator": stderr('Error: Do not run Sopel as Administrator.') sys.exit(1) if opts.version: py_ver = '%s.%s.%s' % (sys.version_info.major, sys.version_info.minor, sys.version_info.micro) print('Sopel %s (running on python %s)' % (__version__, py_ver)) print('http://sopel.chat/') return elif opts.wizard: _wizard('all', opts.config) return elif opts.mod_wizard: _wizard('mod', opts.config) return if opts.list_configs: configs = enumerate_configs() print('Config files in ~/.sopel:') if len(configs) is 0: print('\tNone found') else: for config in configs: print('\t%s' % config) print('-------------------------') return config_name = opts.config or 'default' configpath = find_config(config_name) if not os.path.isfile(configpath): print("Welcome to Sopel!\nI can't seem to find the configuration file, so let's generate it!\n") if not configpath.endswith('.cfg'): configpath = configpath + '.cfg' _create_config(configpath) configpath = find_config(config_name) try: config_module = Config(configpath) except ConfigurationError as e: stderr(e) sys.exit(2) if config_module.core.not_configured: stderr('Bot is not configured, can\'t start') # exit with code 2 to prevent auto restart on fail by systemd sys.exit(2) logfile = os.path.os.path.join(config_module.core.logdir, 'stdio.log') config_module._is_daemonized = opts.daemonize sys.stderr = tools.OutputRedirect(logfile, True, opts.quiet) sys.stdout = tools.OutputRedirect(logfile, False, opts.quiet) # Handle --quit, --kill and saving the PID to file pid_dir = config_module.core.pid_dir if opts.config is None: pid_file_path = os.path.join(pid_dir, 'sopel.pid') else: basename = os.path.basename(opts.config) if basename.endswith('.cfg'): basename = basename[:-4] pid_file_path = os.path.join(pid_dir, 'sopel-%s.pid' % basename) if os.path.isfile(pid_file_path): with open(pid_file_path, 'r') as pid_file: try: old_pid = int(pid_file.read()) except ValueError: old_pid = None if old_pid is not None and tools.check_pid(old_pid): if not opts.quit and not opts.kill: stderr('There\'s already a Sopel instance running with this config file') stderr('Try using the --quit or the --kill options') sys.exit(1) elif opts.kill: stderr('Killing the sopel') os.kill(old_pid, signal.SIGKILL) sys.exit(0) elif opts.quit: stderr('Signaling Sopel to stop gracefully') if hasattr(signal, 'SIGUSR1'): os.kill(old_pid, signal.SIGUSR1) else: os.kill(old_pid, signal.SIGTERM) sys.exit(0) elif old_pid is None or (not tools.check_pid(old_pid) and (opts.kill or opts.quit)): stderr('Sopel is not running!') sys.exit(1) elif opts.quit or opts.kill: stderr('Sopel is not running!') sys.exit(1) if opts.daemonize: child_pid = os.fork() if child_pid is not 0: sys.exit() with open(pid_file_path, 'w') as pid_file: pid_file.write(str(os.getpid())) # Step Five: Initialise And Run sopel run(config_module, pid_file_path) except KeyboardInterrupt: print("\n\nInterrupted") os._exit(1)
def command_legacy(opts): """Legacy Sopel run script The ``legacy`` command manages the old-style ``sopel`` command line tool. Most of its features are replaced by the following commands: * ``sopel start`` replaces the default behavior (run the bot) * ``sopel stop`` replaces the ``--quit/--kill`` options * ``sopel restart`` replaces the ``--restart`` option * ``sopel configure`` replaces the ``-w/--configure-all/--configure-modules`` options The ``-v`` option for "version" is deprecated, ``-V/--version`` should be used instead. .. seealso:: The github issue `#1471`__ tracks various changes requested for future versions of Sopel, some of them related to this legacy command. .. __: https://github.com/sopel-irc/sopel/issues/1471 """ # Step One: Handle "No config needed" options if opts.version: print_version() return elif opts.version_legacy: tools.stderr('WARNING: option -v is deprecated; ' 'use `sopel -V/--version` instead') print_version() return if opts.wizard: tools.stderr('WARNING: option -w/--configure-all is deprecated; ' 'use `sopel configure` instead') _wizard('all', opts.config) return if opts.mod_wizard: tools.stderr('WARNING: option --configure-modules is deprecated; ' 'use `sopel configure --modules` instead') _wizard('mod', opts.config) return if opts.list_configs: print_config() return # Step Two: Get the configuration file and prepare to run try: config_module = get_configuration(opts) except ConfigurationError as e: tools.stderr(e) return ERR_CODE_NO_RESTART if config_module.core.not_configured: tools.stderr('Bot is not configured, can\'t start') return ERR_CODE_NO_RESTART # Step Three: Manage logfile, stdout and stderr utils.redirect_outputs(config_module, opts.quiet) # Step Four: Handle process-lifecycle options and manage the PID file pid_dir = config_module.core.pid_dir pid_file_path = get_pid_filename(opts, pid_dir) old_pid = get_running_pid(pid_file_path) if old_pid is not None and tools.check_pid(old_pid): if not opts.quit and not opts.kill and not opts.restart: tools.stderr( 'There\'s already a Sopel instance running with this config file' ) tools.stderr( 'Try using either the `sopel stop` command or the `sopel restart` command' ) return ERR_CODE elif opts.kill: tools.stderr('WARNING: option -k/--kill is deprecated; ' 'use `sopel stop --kill` instead') tools.stderr('Killing the Sopel') os.kill(old_pid, signal.SIGKILL) return elif opts.quit: tools.stderr('WARNING: options -q/--quit is deprecated; ' 'use `sopel stop` instead') tools.stderr('Signaling Sopel to stop gracefully') if hasattr(signal, 'SIGUSR1'): os.kill(old_pid, signal.SIGUSR1) else: # Windows will not generate SIGTERM itself # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal os.kill(old_pid, signal.SIGTERM) return elif opts.restart: tools.stderr('WARNING: options --restart is deprecated; ' 'use `sopel restart` instead') tools.stderr('Asking Sopel to restart') if hasattr(signal, 'SIGUSR2'): os.kill(old_pid, signal.SIGUSR2) else: # Windows will not generate SIGILL itself # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal os.kill(old_pid, signal.SIGILL) return elif opts.kill or opts.quit or opts.restart: tools.stderr('Sopel is not running!') return ERR_CODE if opts.daemonize: child_pid = os.fork() if child_pid is not 0: return with open(pid_file_path, 'w') as pid_file: pid_file.write(str(os.getpid())) # Step Five: Initialize and run Sopel ret = run(config_module, pid_file_path) os.unlink(pid_file_path) if ret == -1: os.execv(sys.executable, ['python'] + sys.argv) else: return ret
def handle_connect(self): """ Connect to IRC server, handle TLS and authenticate user if an account exists. """ # handle potential TLS connection if self.config.core.use_ssl and has_ssl: if not self.config.core.verify_ssl: self.ssl = ssl.wrap_socket(self.socket, do_handshake_on_connect=True, suppress_ragged_eofs=True) else: self.ssl = ssl.wrap_socket(self.socket, do_handshake_on_connect=True, suppress_ragged_eofs=True, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_certs) # connect to host specified in config first try: ssl.match_hostname(self.ssl.getpeercert(), self.config.core.host) except ssl.CertificateError: # the host in config and certificate don't match LOGGER.error("hostname mismatch between configuration and certificate") # check (via exception) if a CNAME matches as a fallback has_matched = False for hostname in self._get_cnames(self.config.core.host): try: ssl.match_hostname(self.ssl.getpeercert(), hostname) LOGGER.warning("using {0} instead of {1} for TLS connection" .format(hostname, self.config.core.host)) has_matched = True break except ssl.CertificateError: pass if not has_matched: # everything is broken stderr("Invalid certificate, hostname mismatch!") LOGGER.error("invalid certificate, no hostname matches") if hasattr(self.config.core, 'pid_file_path'): os.unlink(self.config.core.pid_file_path) os._exit(1) self.set_socket(self.ssl) # Request list of server capabilities. IRCv3 servers will respond with # CAP * LS (which we handle in coretasks). v2 servers will respond with # 421 Unknown command, which we'll ignore self.write(('CAP', 'LS', '302')) # authenticate account if needed if self.config.core.auth_method == 'server': password = self.config.core.auth_password self.write(('PASS', password)) self.write(('NICK', self.nick)) self.write(('USER', self.user, '+iw', self.nick), self.name) # maintain connection stderr('Connected.') self.last_ping_time = datetime.now() timeout_check_thread = threading.Thread(target=self._timeout_check) timeout_check_thread.daemon = True timeout_check_thread.start() ping_thread = threading.Thread(target=self._send_ping) ping_thread.daemon = True ping_thread.start()
def check_python_version(): if sys.version_info < (2, 7): stderr(u'Error: You need at least Python 2.7!') sys.exit(1)
def signal_handler(sig, frame): if sig == signal.SIGUSR1 or sig == signal.SIGTERM or sig == signal.SIGINT: stderr('Got quit signal, shutting down.') p.quit('Closing')
def parse_event_001(bot, trigger): if 'RPL_Welcome' not in bot.memory: bot.memory['RPL_Welcome'] = trigger.args[1] stderr("\n" + trigger.event + " " + str(trigger.args) + "\n")
def setup_thread(bot): bot.memory['Sopel-CommandsQuery'] = dict() for comtype in ['module', 'nickname', 'rule']: bot.memory['Sopel-CommandsQuery'][comtype + "_commands"] = dict() bot.memory['Sopel-CommandsQuery'][comtype + "_commands_count"] = 0 filepathlisting = [] # main Modules directory main_dir = os.path.dirname(os.path.abspath(sopel.__file__)) modules_dir = os.path.join(main_dir, 'modules') filepathlisting.append(modules_dir) # Home Directory home_modules_dir = os.path.join(bot.config.homedir, 'modules') if os.path.isdir(home_modules_dir): filepathlisting.append(home_modules_dir) # pypi installed try: import sopel_modules pypi_modules = os.path.dirname(os.path.abspath(sopel_modules.__file__)) pypi_modules_dir = os.path.join(pypi_modules, 'modules') filepathlisting.append(pypi_modules_dir) except Exception: pass # Extra directories filepathlist = [] for directory in bot.config.core.extra: filepathlisting.append(directory) for directory in filepathlisting: for pathname in os.listdir(directory): path = os.path.join(directory, pathname) if (os.path.isfile(path) and path.endswith('.py') and not path.startswith('_')): filepathlist.append(str(path)) # CoreTasks ct_path = os.path.join(main_dir, 'coretasks.py') filepathlist.append(ct_path) for modulefile in filepathlist: module_file_lines = [] module_file = open(modulefile, 'r') lines = module_file.readlines() for line in lines: module_file_lines.append(line) module_file.close() dict_from_file = dict() filelinelist = [] for line in module_file_lines: if str(line).startswith("@"): line = str(line)[1:] # Commands if str(line).startswith( tuple([ "commands", "module.commands", "sopel.module.commands" ])): comtype = "module" line = str(line).split("commands(")[-1] line = str("(" + line) validcoms = eval(str(line)) if isinstance(validcoms, tuple): validcoms = list(validcoms) else: validcoms = [validcoms] validcomdict = {"comtype": comtype, "validcoms": validcoms} filelinelist.append(validcomdict) elif str(line).startswith( tuple([ "nickname_commands", "module.nickname_commands", "sopel.module.nickname_commands" ])): comtype = "nickname" line = str(line).split("commands(")[-1] line = str("(" + line) validcoms = eval(str(line)) if isinstance(validcoms, tuple): validcoms = list(validcoms) else: validcoms = [validcoms] nickified = [] for nickcom in validcoms: nickified.append(str(bot.nick) + " " + nickcom) validcomdict = {"comtype": comtype, "validcoms": nickified} filelinelist.append(validcomdict) elif str(line).startswith( tuple(["rule", "module.rule", "sopel.module.rule"])): comtype = "rule" line = str(line).split("rule(")[-1] validcoms = [str("(" + line)] validcomdict = {"comtype": comtype, "validcoms": validcoms} filelinelist.append(validcomdict) for atlinefound in filelinelist: comtype = atlinefound["comtype"] validcoms = atlinefound["validcoms"] comtypedict = str(comtype + "_commands") bot.memory['Sopel-CommandsQuery'][comtypedict + "_count"] += 1 # default command to filename if "validcoms" not in dict_from_file.keys(): dict_from_file["validcoms"] = validcoms maincom = dict_from_file["validcoms"][0] if len(dict_from_file["validcoms"]) > 1: comaliases = spicemanip.main(dict_from_file["validcoms"], '2+', 'list') else: comaliases = [] bot.memory['Sopel-CommandsQuery'][comtypedict][ maincom] = dict_from_file for comalias in comaliases: if comalias not in bot.memory['Sopel-CommandsQuery'][ comtypedict].keys(): bot.memory['Sopel-CommandsQuery'][comtypedict][ comalias] = { "aliasfor": maincom } for comtype in ['module_commands', 'nickname_commands', 'rule_commands']: stderr("[Sopel-CommandsQuery] Found " + str(len(bot.memory['Sopel-CommandsQuery'][comtype].keys())) + " " + comtype + " commands.") if botevents_installed: set_bot_event(bot, "Sopel-CommandsQuery")
def setup(bot): stderr("[Sopel-CommandsQuery] Evaluating Core Commands List") threading.Thread(target=setup_thread, args=(bot, )).start()
def query_detection(bot, trigger): while "Sopel-CommandsQuery" not in bot.memory: pass # command must start with if not str(trigger).startswith(tuple(['?'])): return stderr(trigger.args) commands_list = dict() for commandstype in bot.memory['Sopel-CommandsQuery'].keys(): if not commandstype.endswith("_count"): for com in bot.memory['Sopel-CommandsQuery'][commandstype].keys(): if com not in commands_list.keys(): commands_list[com] = bot.memory['Sopel-CommandsQuery'][ commandstype][com] triggerargsarray = spicemanip.main(trigger, 'create') # command issued, check if valid querycommand = spicemanip.main(triggerargsarray, 1).lower()[1:] if len(querycommand) == 1: commandlist = [] for command in commands_list.keys(): if command.lower().startswith(querycommand): commandlist.append(command) if commandlist == []: bot.notice("No commands match " + str(querycommand) + ".", trigger.nick) return else: bot.notice( "The following commands match " + str(querycommand) + ": " + spicemanip.main(commandlist, 'andlist') + ".", trigger.nick) return elif querycommand.endswith(tuple(["+"])): querycommand = querycommand[:-1] if querycommand not in commands_list.keys(): bot.notice("The " + str(querycommand) + " does not appear to be valid.") return realcom = querycommand if "aliasfor" in commands_list[querycommand].keys(): realcom = commands_list[querycommand]["aliasfor"] validcomlist = commands_list[realcom]["validcoms"] bot.notice( "The following commands match " + str(querycommand) + ": " + spicemanip.main(validcomlist, 'andlist') + ".", trigger.nick) return elif querycommand.endswith(tuple(['?'])): querycommand = querycommand[:-1] sim_com, sim_num = [], [] for com in commands_list.keys(): similarlevel = SequenceMatcher(None, querycommand.lower(), com.lower()).ratio() sim_com.append(com) sim_num.append(similarlevel) sim_num, sim_com = (list(x) for x in zip( *sorted(zip(sim_num, sim_com), key=itemgetter(0)))) closestmatch = spicemanip.main(sim_com, 'reverse', "list") listnumb, relist = 1, [] for item in closestmatch: if listnumb <= 10: relist.append(str(item)) listnumb += 1 bot.notice( "The following commands may match " + str(querycommand) + ": " + spicemanip.main(relist, 'andlist') + ".", trigger.nick) return elif querycommand in commands_list.keys(): bot.notice( "The following commands match " + str(querycommand) + ": " + str(querycommand) + ".", trigger.nick) return elif not querycommand: return else: commandlist = [] for command in commands_list.keys(): if command.lower().startswith(querycommand): commandlist.append(command) if commandlist == []: bot.notice("No commands match " + str(querycommand) + ".", trigger.nick) return else: bot.notice( "The following commands match " + str(querycommand) + ": " + spicemanip.main(commandlist, 'andlist') + ".", trigger.nick) return
def run(settings, pid_file, daemon=False): delay = 20 # Acts as a welcome message, showing the program and platform version at start print_version() if not settings.core.ca_certs: tools.stderr( 'Could not open CA certificates file. SSL will not work properly!') def signal_handler(sig, frame): if sig == signal.SIGUSR1 or sig == signal.SIGTERM or sig == signal.SIGINT: LOGGER.warning('Got quit signal, shutting down.') p.quit('Closing') elif sig == signal.SIGUSR2 or sig == signal.SIGILL: LOGGER.warning('Got restart signal, shutting down and restarting.') p.restart('Restarting') # Define empty variable `p` for bot p = None while True: if p and p.hasquit: # Check if `hasquit` was set for bot during disconnected phase break try: p = bot.Sopel(settings, daemon=daemon) if hasattr(signal, 'SIGUSR1'): signal.signal(signal.SIGUSR1, signal_handler) if hasattr(signal, 'SIGTERM'): signal.signal(signal.SIGTERM, signal_handler) if hasattr(signal, 'SIGINT'): signal.signal(signal.SIGINT, signal_handler) if hasattr(signal, 'SIGUSR2'): signal.signal(signal.SIGUSR2, signal_handler) if hasattr(signal, 'SIGILL'): signal.signal(signal.SIGILL, signal_handler) p.setup() except KeyboardInterrupt: break except Exception: # In that case, there is nothing we can do. # If the bot can't setup itself, then it won't run. # This is a critical case scenario, where the user should have # direct access to the exception traceback right in the console. # Besides, we can't know if logging has been set up or not, so # we can't rely on that here. tools.stderr('Unexpected error in bot setup') raise try: p.run(settings.core.host, int(settings.core.port)) except KeyboardInterrupt: break except Exception: err_log = logging.getLogger('sopel.exceptions') err_log.exception('Critical exception in core') err_log.error('----------------------------------------') # TODO: This should be handled by command_start # All we should need here is a return value, but replacing the # os._exit() call below (at the end) broke ^C. # This one is much harder to test, so until that one's sorted it # isn't worth the risk of trying to remove this one. os.unlink(pid_file) os._exit(1) if not isinstance(delay, int): break if p.wantsrestart: return -1 if p.hasquit: break LOGGER.warning('Disconnected. Reconnecting in %s seconds...', delay) time.sleep(delay) # TODO: This should be handled by command_start # All we should need here is a return value, but making this # a return makes Sopel hang on ^C after it says "Closed!" os.unlink(pid_file) os._exit(0)
def setup(bot): stderr("[SpiceBot_DatabaseCache] Setting up Database Cache.") bot.memory['SpiceBot_DatabaseCache'] = dict()
""" from __future__ import unicode_literals, absolute_import, print_function, division import argparse import logging import os import platform import signal import sys import time from sopel import bot, config, logger, tools, __version__ from . import utils if sys.version_info < (2, 7): tools.stderr('Error: Requires Python 2.7 or later. Try python2.7 sopel') sys.exit(1) if sys.version_info.major == 2: if time.time() >= 1577836800: # 2020-01-01 00:00:00 UTC state = 'is near end of life' else: state = 'has reached end of life and will receive no further updates' tools.stderr( 'Warning: Python 2.x %s. Sopel will drop support in version 8.0.' % state) if sys.version_info.major == 3 and sys.version_info.minor < 3: tools.stderr('Error: When running on Python 3, Python 3.3 is required.') sys.exit(1) LOGGER = logging.getLogger(__name__)
def run(settings, pid_file, daemon=False): """Run the bot with these ``settings``. :param settings: settings with which to run the bot :type settings: :class:`sopel.config.Config` :param str pid_file: path to the bot's PID file :param bool daemon: tell if the bot should be run as a daemon """ delay = 20 # Acts as a welcome message, showing the program and platform version at start print_version() # Also show the location of the config file used to load settings print("\nLoaded config file: {}".format(settings.filename)) if not settings.core.ca_certs: tools.stderr( 'Could not open CA certificates file. SSL will not work properly!') # Define empty variable `p` for bot p = None while True: if p and p.hasquit: # Check if `hasquit` was set for bot during disconnected phase break try: p = bot.Sopel(settings, daemon=daemon) p.setup() p.set_signal_handlers() except KeyboardInterrupt: tools.stderr('Bot setup interrupted') break except Exception: # In that case, there is nothing we can do. # If the bot can't setup itself, then it won't run. # This is a critical case scenario, where the user should have # direct access to the exception traceback right in the console. # Besides, we can't know if logging has been set up or not, so # we can't rely on that here. tools.stderr('Unexpected error in bot setup') raise try: p.run(settings.core.host, int(settings.core.port)) except KeyboardInterrupt: break except Exception: err_log = logging.getLogger('sopel.exceptions') err_log.exception('Critical exception in core') err_log.error('----------------------------------------') # TODO: This should be handled by command_start # All we should need here is a return value, but replacing the # os._exit() call below (at the end) broke ^C. # This one is much harder to test, so until that one's sorted it # isn't worth the risk of trying to remove this one. os.unlink(pid_file) os._exit(1) if not isinstance(delay, int): break if p.wantsrestart: return -1 if p.hasquit: break LOGGER.warning('Disconnected. Reconnecting in %s seconds...', delay) time.sleep(delay) # TODO: This should be handled by command_start # All we should need here is a return value, but making this # a return makes Sopel hang on ^C after it says "Closed!" os.unlink(pid_file) os._exit(0)
def main(argv=None): global homedir # Step One: Parse The Command Line try: parser = optparse.OptionParser('%prog [options]') parser.add_option('-c', '--config', metavar='filename', help='use a specific configuration file') parser.add_option("-d", '--fork', action="store_true", dest="deamonize", help="Deamonize sopel") parser.add_option("-q", '--quit', action="store_true", dest="quit", help="Gracefully quit Sopel") parser.add_option("-k", '--kill', action="store_true", dest="kill", help="Kill Sopel") parser.add_option( '--exit-on-error', action="store_true", dest="exit_on_error", help="Exit immediately on every error instead of trying to recover" ) parser.add_option("-l", '--list', action="store_true", dest="list_configs", help="List all config files found") parser.add_option("-m", '--migrate', action="store_true", dest="migrate_configs", help="Migrate config files to the new format") parser.add_option('--quiet', action="store_true", dest="quiet", help="Supress all output") parser.add_option('-w', '--configure-all', action='store_true', dest='wizard', help='Run the configuration wizard.') parser.add_option( '--configure-modules', action='store_true', dest='mod_wizard', help= 'Run the configuration wizard, but only for the module configuration options.' ) parser.add_option( '--configure-database', action='store_true', dest='db_wizard', help= 'Run the configuration wizard, but only for the database configuration options.' ) opts, args = parser.parse_args(argv) if opts.wizard: wizard('all', opts.config) return elif opts.mod_wizard: wizard('mod', opts.config) return elif opts.db_wizard: wizard('db', opts.config) return check_python_version() if opts.list_configs is not None: configs = enumerate_configs() print 'Archives de configuation:' if len(configs[0]) is 0: print u'\tJe n\'ai trouvé pas rien' else: for config in configs: print '\t%s' % config print '-------------------------' return config_name = opts.config or 'default' configpath = find_config(config_name) if not os.path.isfile(configpath): print u"Welcome to JoikervgBot configuration wizard! -- ¡Bienvenido al asistente de configuración de JoikervgBot! -- Bienvenue à l'assistant de configuration de JoikervgBot!\n" if not configpath.endswith('.cfg'): configpath = configpath + '.cfg' create_config(configpath) configpath = find_config(config_name) try: config_module = Config(configpath) except ConfigurationError as e: stderr(e) sys.exit(2) if config_module.core.not_configured: stderr( u'The bot is not configured. -- El bot no está configurado. -- Le bot n\'est pas configuré.' ) # exit with code 2 to prevent auto restart on fail by systemd sys.exit(2) if not config_module.has_option('core', 'homedir'): config_module.dotdir = homedir config_module.homedir = homedir else: homedir = config_module.core.homedir config_module.dotdir = config_module.core.homedir if not config_module.core.logdir: config_module.core.logdir = os.path.join(homedir, 'logs') logfile = os.path.os.path.join(config_module.logdir, 'stdio.log') if not os.path.isdir(config_module.logdir): os.mkdir(config_module.logdir) if opts.exit_on_error: config_module.exit_on_error = True else: config_module.exit_on_error = False if opts.quiet is None: opts.quiet = False sys.stderr = tools.OutputRedirect(logfile, True, opts.quiet) sys.stdout = tools.OutputRedirect(logfile, False, opts.quiet) #Handle --quit, --kill and saving the PID to file pid_dir = config_module.core.pid_dir or homedir if opts.config is None: pid_file_path = os.path.join(pid_dir, 'sopel.pid') else: basename = os.path.basename(opts.config) if basename.endswith('.cfg'): basename = basename[:-4] pid_file_path = os.path.join(pid_dir, 'sopel-%s.pid' % basename) if os.path.isfile(pid_file_path): pid_file = open(pid_file_path, 'r') old_pid = int(pid_file.read()) pid_file.close() if tools.check_pid(old_pid): if opts.quit is None and opts.kill is None: stderr( u'There is already a JoikervgBot running. -- Ya hay un bot ejecutándose. -- Il y a déjà un bot en fonctionnement.' ) stderr(u'Try -- Intenta: --quit o --kill') sys.exit(1) elif opts.kill: stderr( u'Killing JoikervgBot. -- Matando a JoikervgBot. -- Tuent JoikervgBot.' ) os.kill(old_pid, signal.SIGKILL) sys.exit(0) elif opts.quit: stderr( u'Quitting JoikervgBot. -- Desconectando a JoikervgBot. -- Déconnectant JoikervgBot.' ) if hasattr(signal, 'SIGUSR1'): os.kill(old_pid, signal.SIGUSR1) else: os.kill(old_pid, signal.SIGTERM) sys.exit(0) elif not tools.check_pid(old_pid) and (opts.kill or opts.quit): stderr( u'The bot is not running. -- El bot no se está ejecutando. -- Le bot n\'est pas en fonctionnement.' ) sys.exit(1) elif opts.quit is not None or opts.kill is not None: stderr( u'The bot is not running. -- El bot no se está ejecutando. -- Le bot n\'est pas en fonctionnement.' ) sys.exit(1) if opts.deamonize is not None: child_pid = os.fork() if child_pid is not 0: sys.exit() pid_file = open(pid_file_path, 'w') pid_file.write(str(os.getpid())) pid_file.close() config_module.pid_file_path = pid_file_path # Step Five: Initialise And Run sopel run(config_module) except KeyboardInterrupt: print "\n\nKeyboard Interrupt" os._exit(1)
import logging import os import platform import signal import sys import time from sopel import bot, config, logger, tools, __version__ from . import utils # This is in case someone somehow manages to install Sopel on an old version # of pip (<9.0.0), which doesn't know about `python_requires`, or tries to run # from source on an unsupported version of Python. if sys.version_info < (2, 7) or (sys.version_info.major >= 3 and sys.version_info < (3, 3)): tools.stderr('Error: Sopel requires Python 2.7+ or 3.3+.') sys.exit(1) if sys.version_info.major == 2: now = time.time() state = 'has reached end of life' if now >= 1588291200: # 2020-05-01 00:00:00 UTC state += ' and will receive no further updates' tools.stderr( 'Warning: Python 2.x %s. Sopel 8.0 will drop support for it.' % state) LOGGER = logging.getLogger(__name__) ERR_CODE = 1 """Error code: program exited with an error""" ERR_CODE_NO_RESTART = 2 """Error code: program exited with an error and should not be restarted
def run(self, host, port=6667): try: self.initiate_connect(host, port) except socket.error as e: stderr('Connection error: %s' % e) self.handle_close()
def setup(bot): stderr("[Sopel-BotEvents] Starting Module Events Logging") threading.Thread(target=setup_thread, args=(bot, )).start()
import argparse import os import platform import signal import sys import time import traceback from sopel import bot, logger, tools, __version__ from sopel.config import (Config, _create_config, ConfigurationError, ConfigurationNotFound, DEFAULT_HOMEDIR, _wizard) from . import utils if sys.version_info < (2, 7): tools.stderr('Error: Requires Python 2.7 or later. Try python2.7 sopel') sys.exit(1) if sys.version_info.major == 2: tools.stderr( 'Warning: Python 2.x is near end of life. Sopel support at that point is TBD.' ) if sys.version_info.major == 3 and sys.version_info.minor < 3: tools.stderr('Error: When running on Python 3, Python 3.3 is required.') sys.exit(1) ERR_CODE = 1 """Error code: program exited with an error""" ERR_CODE_NO_RESTART = 2 """Error code: program exited with an error and should not be restarted This error code is used to prevent systemd from restarting the bot when it
def handle_disable(options): """Disable Sopel plugins. :param options: parsed arguments :type options: :class:`argparse.Namespace` :return: 0 if everything went fine; 1 if the plugin doesn't exist, or if attempting to disable coretasks (required) """ plugin_names = options.names force = options.force ensure_remove = options.remove settings = utils.load_settings(options) usable_plugins = plugins.get_usable_plugins(settings) actually_disabled = [] # coretasks is sacred if 'coretasks' in plugin_names: tools.stderr('Plugin coretasks cannot be disabled.') return 1 # do nothing and return an error code unknown_plugins = [ name for name in plugin_names if name not in usable_plugins ] if unknown_plugins: display_unknown_plugins(unknown_plugins) return 1 # do nothing and return an error code # remove from enabled if asked if ensure_remove: settings.core.enable = [ name for name in settings.core.enable if name not in plugin_names ] settings.save() # disable plugin (when needed) actually_disabled = tuple( name for name in plugin_names if _handle_disable_plugin(settings, name, force) ) # save if required if actually_disabled: settings.save() else: return 0 # nothing to disable or save, but not an error case # display plugins actually disabled by the command print(utils.get_many_text( actually_disabled, one='Plugin {item} disabled.', two='Plugins {first} and {second} disabled.', many='Plugins {left}, and {last} disabled.' )) return 0
# coding=utf-8 """ Sopel - An IRC Bot Copyright 2008, Sean B. Palmer, inamidst.com Copyright © 2012-2014, Elad Alfassa <*****@*****.**> Licensed under the Eiffel Forum License 2. http://sopel.chat """ from __future__ import unicode_literals, absolute_import, print_function, division import sys from sopel.tools import stderr if sys.version_info < (2, 7): stderr('Error: Requires Python 2.7 or later. Try python2.7 sopel') sys.exit(1) if sys.version_info.major == 3 and sys.version_info.minor < 3: stderr('Error: When running on Python 3, Python 3.3 is required.') sys.exit(1) import os import argparse import signal from sopel.__init__ import run, __version__ from sopel.config import Config, _create_config, ConfigurationError, _wizard import sopel.tools as tools homedir = os.path.join(os.path.expanduser('~'), '.sopel')
def setup(self): stderr("\nWelcome to Sopel. Loading modules...\n\n") modules = sopel.loader.enumerate_modules(self.config) error_count = 0 success_count = 0 for name in modules: path, type_ = modules[name] try: module, _ = sopel.loader.load_module(name, path, type_) except Exception as e: error_count = error_count + 1 filename, lineno = tools.get_raising_file_and_line() rel_path = os.path.relpath(filename, os.path.dirname(__file__)) raising_stmt = "%s:%d" % (rel_path, lineno) stderr("Error loading %s: %s (%s)" % (name, e, raising_stmt)) else: try: if hasattr(module, 'setup'): module.setup(self) relevant_parts = sopel.loader.clean_module( module, self.config) except Exception as e: error_count = error_count + 1 filename, lineno = tools.get_raising_file_and_line() rel_path = os.path.relpath(filename, os.path.dirname(__file__)) raising_stmt = "%s:%d" % (rel_path, lineno) stderr("Error in %s setup procedure: %s (%s)" % (name, e, raising_stmt)) else: self.register(*relevant_parts) success_count += 1 if len(modules) > 1: # coretasks is counted stderr('\n\nRegistered %d modules,' % (success_count - 1)) stderr('%d modules failed to load\n\n' % error_count) else: stderr("Warning: Couldn't load any modules")
def setup(self): """Set up the Sopel instance.""" load_success = 0 load_error = 0 load_disabled = 0 stderr("Welcome to Sopel. Loading modules...") usable_plugins = plugins.get_usable_plugins(self.config) for name, info in usable_plugins.items(): plugin, is_enabled = info if not is_enabled: load_disabled = load_disabled + 1 continue try: plugin.load() except Exception as e: load_error = load_error + 1 filename, lineno = tools.get_raising_file_and_line() rel_path = os.path.relpath(filename, os.path.dirname(__file__)) raising_stmt = "%s:%d" % (rel_path, lineno) stderr("Error loading %s: %s (%s)" % (name, e, raising_stmt)) else: try: if plugin.has_setup(): plugin.setup(self) plugin.register(self) except Exception as e: load_error = load_error + 1 filename, lineno = tools.get_raising_file_and_line() rel_path = os.path.relpath(filename, os.path.dirname(__file__)) raising_stmt = "%s:%d" % (rel_path, lineno) stderr("Error in %s setup procedure: %s (%s)" % (name, e, raising_stmt)) else: load_success = load_success + 1 print('Loaded: %s' % name) total = sum([load_success, load_error, load_disabled]) if total and load_success: stderr('Registered %d modules' % (load_success - 1)) stderr('%d modules failed to load' % load_error) stderr('%d modules disabled' % load_disabled) else: stderr("Warning: Couldn't load any modules")
import logging import os import platform import signal import sys import time from sopel import __version__, bot, config, logger, tools from . import utils # This is in case someone somehow manages to install Sopel on an old version # of pip (<9.0.0), which doesn't know about `python_requires`, or tries to run # from source on an unsupported version of Python. if sys.version_info < (2, 7) or (sys.version_info.major >= 3 and sys.version_info < (3, 3)): tools.stderr('Error: Sopel requires Python 2.7+ or 3.3+.') sys.exit(1) if sys.version_info.major == 2: tools.stderr( 'Warning: Python 2.x has reached end of life and will receive ' 'no further updates. Sopel 8.0 will drop support for it.') LOGGER = logging.getLogger(__name__) ERR_CODE = 1 """Error code: program exited with an error""" ERR_CODE_NO_RESTART = 2 """Error code: program exited with an error and should not be restarted This error code is used to prevent systemd from restarting the bot when it encounters such an error case.
def run(config, pid_file, daemon=False): import sopel.bot as bot import sopel.logger from sopel.tools import stderr delay = 20 # Inject ca_certs from config to web for SSL validation of web requests if not config.core.ca_certs: stderr('Could not open CA certificates file. SSL will not ' 'work properly.') def signal_handler(sig, frame): if sig == signal.SIGUSR1 or sig == signal.SIGTERM or sig == signal.SIGINT: stderr('Got quit signal, shutting down.') p.quit('Closing') elif sig == signal.SIGUSR2 or sig == signal.SIGILL: stderr('Got restart signal.') p.restart('Restarting') while True: try: p = bot.Sopel(config, daemon=daemon) if hasattr(signal, 'SIGUSR1'): signal.signal(signal.SIGUSR1, signal_handler) if hasattr(signal, 'SIGTERM'): signal.signal(signal.SIGTERM, signal_handler) if hasattr(signal, 'SIGINT'): signal.signal(signal.SIGINT, signal_handler) if hasattr(signal, 'SIGUSR2'): signal.signal(signal.SIGUSR2, signal_handler) if hasattr(signal, 'SIGILL'): signal.signal(signal.SIGILL, signal_handler) sopel.logger.setup_logging(p) p.run(config.core.host, int(config.core.port)) except KeyboardInterrupt: break except Exception: # TODO: Be specific trace = traceback.format_exc() try: stderr(trace) except Exception: # TODO: Be specific pass logfile = open(os.path.join(config.core.logdir, 'exceptions.log'), 'a') logfile.write('Critical exception in core') logfile.write(trace) logfile.write('----------------------------------------\n\n') logfile.close() # TODO: This should be handled in run_script # All we should need here is a return value, but replacing the # os._exit() call below (at the end) broke ^C. # This one is much harder to test, so until that one's sorted it # isn't worth the risk of trying to remove this one. os.unlink(pid_file) os._exit(1) if not isinstance(delay, int): break if p.wantsrestart: return -1 if p.hasquit: break stderr('Warning: Disconnected. Reconnecting in %s seconds...' % delay) time.sleep(delay) # TODO: This should be handled in run_script # All we should need here is a return value, but making this # a return makes Sopel hang on ^C after it says "Closed!" os.unlink(pid_file) os._exit(0)
def setup(bot): stderr("[SpiceBot_Update] Initial Setup processing...") bot.config.define_section("SpiceBot_Update", SpiceBot_Update_MainSection, validate=False)