def _run(self, cfg, board, kwargs): self.interval = 900 # only set trusted domains, if nextcloud is installed if not self.nc.is_installed: log.debug("cannot set trusted_domains - uninitialized nextcloud") self.interval = 15 return False try: trusted_domains = self.nc.get_config("trusted_domains") except NextcloudError: log.warning("cannot get trusted_domains from nextcloud") self.interval = 15 return False default_entry = trusted_domains[0] entries = [default_entry] + self.static_entries[:] if cfg["config"].get("domain"): entries.append(cfg["config"]["domain"]) if cfg["config"].get("proxy_active") and cfg["config"].get("proxy_domain"): entries.append(cfg["config"]["proxy_domain"]) if any(entry not in trusted_domains for entry in entries): try: self.nc.set_config("trusted_domains", entries) except NextcloudError: log.warning("failed to write all trusted_domains") self.interval = 15
def _run(self, cfg, board, kwargs): self.interval = None # if the custom-dns-config is active, ensure ddclient is up if cfg["config"]["dns_mode"] == "config_done" and not services.is_active("ddclient"): services.restart("ddclient") # ensure that neither updater nor factory-reset are masked (paranoia!) services.unmask("nextbox-updater") services.unmask("nextbox-factory-reset") # log a welcome + version log.info(f"Hello World - I am NextBox - call me: v{nextbox_version()} - let'sa gooo") # update/upgrade now shield.set_led_state("updating") log.info("running 'apt-get update'") cache = apt.cache.Cache() cache.update() cache.open() # which debian package pkg = cfg["config"]["debian_package"] try: pkg_obj = cache[pkg] except KeyError: log.error(f"self-update failed: designated package: {pkg} not found!") log.error("falling back to 'nextbox' - retrying upgrade...") pkg = "nextbox" try: pkg_obj = cache[pkg] except KeyError: log.error("CRITICAL: failed to find 'nextbox' in apt-cache") # we should never ever end here, this means that the nextbox # debian package is not available... # nextbox debian (ppa) repository not available ???!! return # install package (i.e., other nextbox package is already installed) # will trigger for e.g., 'nextbox' to 'nextbox-testing' switching if not pkg_obj.is_installed: log.info(f"installing debian package: {pkg} (start service: nextbox-updater)") services.start("nextbox-updater") elif pkg_obj.is_upgradable: log.info(f"upgrading debian package: {pkg} (start service: nextbox-updater)") services.start("nextbox-updater") else: log.debug(f"no need to upgrade or install debian package: {pkg}")
def _run(self, cfg, board, kwargs): # only enable, if nextcloud is installed if not self.nc.is_installed: log.debug("cannot enable nextbox-app - uninitialized nextcloud") return # try to enable try: if self.nc.enable_nextbox_app(): self.interval = 3600 log.info("enabled nextcloud nextbox-app") except NextcloudError: pass
def start(self): if self.started: log.warning(f"trying to start already started: {self.cmd}") return False self.cmd = self.cmd if not self.shell else " ".join(self.cmd) self.proc = subprocess.Popen(self.cmd, shell=self.shell, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) self.started = time.time() log.debug(f"started {self.cmd} ({'non-' if not self.block else ''}blocking)") # return if non-blocking, else wait + pass-on output if not self.block: return True log.debug("blocking call, waiting now...") while self.proc.returncode is None: try: self.proc.wait(timeout=10) except subprocess.TimeoutExpired: log.debug("blocking-cmd timeout (10secs)") self._handle_stdout_stream(poll_block_ms=1000) log.debug("blocking call finished") return True
def retries(cls, count, *vargs, **kwargs): # cannot retry, if non-blocking if not kwargs.get("block"): log.warning("trying CommandRunner-retries w/o block: no retries are done!") return cls(*vargs, **kwargs) # if 'block' is set, retry until returncode == 0 for idx in range(count): res = cls(*vargs, **kwargs) if res.returncode == 0: return res log.debug(f"retry #{idx+1}, returncode: {res.returncode}") res.log_output() # failed retries log.error("retried hard for {count} times, failed anyways") return res
def _run(self, cfg, board, kwargs): token = cfg["config"]["desec_token"] dns_mode = cfg["config"]["dns_mode"] domain = cfg["config"]["domain"] if dns_mode not in ["desec_done", "off"]: if not services.is_active("ddclient"): services.restart("ddclient") services.enable("ddclient") return if dns_mode == "off": if services.is_active("ddclient"): services.stop("ddclient") services.disable("ddclient") return if dns_mode == "desec_done": if services.is_active("ddclient"): services.stop("ddclient") services.disable("ddclient") dns = DNSManager() ipv4 = dns.get_ipv4() ipv6 = dns.get_ipv6() headers = {"Authorization": f"Token {token}"} params = {"hostname": domain} if ipv4: params["myipv4"] = ipv4 if ipv6: params["myipv6"] = ipv6 res = requests.get("https://update.dedyn.io", params=params, headers=headers) if res.ok: log.info(f"updated deSEC IPv4 ({ipv4}) and IPv6 ({ipv6}) address for '{domain}'") else: log.warning(f"failed updating IPs ({ipv4} & {ipv6}) for domain: '{domain}'") log.debug(f"result: {res.text} status_code: {res.status_code} url: {res.url}")
def handle_config(): if request.method == "GET": data = dict(cfg["config"]) try: data["conf"] = Path(DDCLIENT_CONFIG_PATH).read_text("utf-8") except FileNotFoundError: data["conf"] = "" return success(data=data) # save dyndns related values to configuration elif request.method == "POST": run_jobs = [] for key in request.form: val = request.form.get(key) # special config-value 'conf' represents ddclient-config-contents if key == "conf": old_conf = Path(DDCLIENT_CONFIG_PATH).read_text("utf-8") if old_conf != val: log.info("writing ddclient config and restarting service") Path(DDCLIENT_CONFIG_PATH).write_text(val, "utf-8") elif len(val.strip()) == 0: log.info("writing empty ddclient config") Path(DDCLIENT_CONFIG_PATH).write_text(val, "utf-8") services.stop("ddclient") services.disable("ddclient") if len(val.strip()) > 0: services.enable("ddclient") services.restart("ddclient") run_jobs.append("DynDNSUpdate") elif key in AVAIL_CONFIGS and val is not None: # only allow valid DYNDNS_MODES if key == "dns_mode" and val not in DYNDNS_MODES: log.warning( f"key: 'dns_mode' has invalid value: {val} - skipping") continue # start DynDNS update on "desec_done" elif key == "dns_mode" and val == "desec_done": run_jobs.append("DynDNSUpdate") # start TrustedDomains update on new domain elif "domain" in key: run_jobs.append("TrustedDomains") # deactivate proxy on request elif key == "proxy_active" and val.lower() == "false": proxy_tunnel = ProxyTunnel() proxy_tunnel.stop() # skip if 'val' is empty elif val is None: log.debug(f"skipping key: '{key}' -> no value provided") continue # convert to bool, ugly? if val.lower() in ["true", "false"]: val = val.lower() == "true" # put key-value into cfg and save (yes, saving each value) cfg["config"][key] = val log.debug(f"saving key: '{key}' with value: '{val}'") cfg.save() # run jobs collected during configuration update if len(run_jobs) > 0: for job in run_jobs: job_queue.put(job) return success("DynDNS configuration saved")
def full_import(self, src_path): if not isinstance(src_path, Path): src_path = Path(src_path) steps = [ ("sql", lambda: self.import_sql(src_path)), ("config", lambda: self.import_config_dir(src_path)), ("nextbox", lambda: self.import_nextbox_dir(src_path)), ("data", lambda: self.import_dir("data", src_path)), ("apps", lambda: self.import_dir("apps", src_path)), ("letsencrypt", lambda: self.import_dir("letsencrypt", src_path)), ] if not src_path.exists(): os.makedirs(src_path.as_posix()) self.check_backup(src_path) failed = False log.info("starting full import") for step_key, step_func in steps: log.debug(f"full import step: {step_key}") # blocking step (sql) ret = step_func() # blocking call, eval directly and yield state if step_key in ["sql", "nextbox", "config"]: if ret == True: log.debug(f"finished import step: {step_key}") yield ("finished", (step_key, "import"), 100) continue else: log.debug(f"failed import step: {step_key}") failed = True yield ("failed", (step_key, "import"), 100) break # non-blocking call(s) check_progress() and yield state while True: act, desc, percent = self.check_progress() if act != "active": if act == "failed": log.debug(f"failed import step: {step_key}") failed = True else: log.debug(f"finished import step: {step_key}") yield (act, desc, 100) break time.sleep(1) yield (act, desc, percent) # final restore steps if not failed: # recreate apache config based on current (imported) config conf_path = Path(self.dirs["nextbox"]) / self.nextbox_conf new_cfg = yaml.safe_load(conf_path.open()).get("config", {}) has_https = new_cfg.get("https_port") if has_https: domain = new_cfg.get("domain") certs = Certificates() my_cert = certs.get_cert(domain) if not my_cert: log.error( f"expected https/ssl, but could not find certificate: {domain}" ) log.error("switching apache2 config to non-ssl") certs.set_apache_config(ssl=False) #### naaah this is evil: new_cfg["https_port"] = None dct = {"config": new_cfg} yaml.safe_dump(dct, conf_path.open("w")) else: log.info( f"found certificate for {domain} - activating ssl") certs.write_apache_ssl_conf(my_cert["domains"][0], my_cert["fullchain_path"], my_cert["privkey_path"]) if not certs.set_apache_config(ssl=True): log.error("failed enabling ssl config for apache") else: log.info("re-enabled ssl for imported configuration") log.debug("wrote apache config according to restored data") # restart daemon log.info("finalized import - all seems good!") log.info(".... restarting daemon") services.restart("nextbox-daemon") yield ("completed", ("all", "import"), 100)
def set_env_data(self, docker_env_path, data_dct): with Path(docker_env_path).open("w") as fd: for key, val in sorted(data_dct.items()): fd.write(f"{key}={val}\n") log.debug(f"(re)wrote {docker_env_path} during restore")
def run(self, cfg, board, kwargs): """Overridden Thread.run() is run inside the new thread after start()""" log.debug(f"starting worker job: {self.name}") self.last_run = dt.now() self._run(cfg, board, kwargs) log.debug(f"finished worker job: {self.name}")