def regenerate_firewall(data, range=[]): """ Flush and regenerate arkOS firewall chain. If ``range`` is not specified, network module will guess what they are. :param SecurityPolicy data: Security policies to enact :param list range: Range(s) of local network(s) ('192.168.0.0/24') """ signals.emit("security", "pre_fw_regen") flush_chain("arkos-apps") default_range = range or network.get_active_ranges() # For each policy in the system, add a rule for x in data: range = getattr(x, "allowed_ranges", default_range) for port in x.ports: if x.policy == 2: add_rule("ACCEPT", port[0], port[1], ["anywhere"]) elif x.policy == 1: add_rule("ACCEPT", port[0], port[1], range) else: add_rule("REJECT", port[0], port[1]) shell("iptables -A arkos-apps -j RETURN") save_rules() signals.emit("security", "post_fw_regen")
def add(self): """Add group.""" shell("groupadd {0}".format(self.name)) self.update() for x in grp.getgrall(): if x.gr_name == self.name: self.gid = x.gr_gid
def post_install(self, name, path, vars, dbinfo={}): # UPDATE: Config DB info and enable modules index_php = os.path.join(path, 'index.php') os.remove(index_php) with open(index_php, "w") as f: f.write(switching_index) # see below shell('chown -R http ' + path)
def update_password(self, passwd): """ Set password. :param str passwd: password to set """ shell("passwd {0}".format(self.name), stdin="{0}\n{1}\n".format(passwd, passwd))
def firstrun(): data = request.get_json() resize_boards = [ "Raspberry Pi", "Raspberry Pi 2", "Raspberry Pi 3", "Cubieboard2", "Cubietruck", "BeagleBone Black", "ODROID-U" ] if data.get("resize_sd_card", None)\ and config.get("enviro", "board") in resize_boards: part = 1 if config.get("enviro", "board").startswith("Cubie") else 2 p1str = 'd\nn\np\n1\n\n\nw\n' p2str = 'd\n2\nn\np\n2\n\n\nw\n' shell('fdisk /dev/mmcblk0', stdin=(p1str if part == 1 else p2str)) if not os.path.exists('/etc/cron.d'): os.mkdir('/etc/cron.d') with open('/etc/cron.d/resize', 'w') as f: f.write('@reboot root e2fsck -fy /dev/mmcblk0p{0}\n'.format(part)) f.write('@reboot root resize2fs /dev/mmcblk0p{0}\n'.format(part)) f.write('@reboot root rm /etc/cron.d/resize\n') f.close() if data.get("use_gpu_mem", None) \ and config.get("enviro", "board").startswith("Raspberry"): f = filesystems.get("mmcblk0p1") if not f.is_mounted: f.mountpoint = "/boot" f.mount() cfgdata = [] if os.path.exists('/boot/config.txt'): with open("/boot/config.txt", "r") as f: for x in f.readlines(): if x.startswith("gpu_mem"): x = "gpu_mem=16\n" cfgdata.append(x) if "gpu_mem=16\n" not in cfgdata: cfgdata.append("gpu_mem=16\n") with open("/boot/config.txt", "w") as f: f.writelines(cfgdata) else: with open("/boot/config.txt", "w") as f: f.write("gpu_mem=16\n") if data.get("cubie_mac", None) \ and config.get("enviro", "board").startswith("Cubie"): if config.get("enviro", "board") == "Cubieboard2": with open('/boot/uEnv.txt', 'w') as f: opt_str = 'extraargs=mac_addr={0}\n' f.write(opt_str.format(data.get("cubie_mac"))) elif config.get("enviro", "board") == "Cubietruck": with open('/etc/modprobe.d/gmac.conf', 'w') as f: opt_str = 'options sunxi_gmac mac_str="{0}"\n' f.write(opt_str.format(data.get("cubie_mac"))) if data.get("install"): as_job(install, data["install"]) rootpwd = "" if data.get("protectRoot"): rootpwd = random_string(16) shell("passwd root", stdin="{0}\n{0}\n".format(rootpwd)) security.initialize_firewall() return jsonify(rootpwd=rootpwd)
def update_gem(*gems, **kwargs): # Updates a set of presently-installed Ruby gems. verify_path() gemlist = shell("gem list")["stdout"].split("\n") for x in gems: if not any(x==s for s in gemlist) or force: d = shell("gem update -N --no-user-install %s" % x) if d["code"] != 0: logger.error("Gem update '%s' failed: %s"%(x,str(d["stderr"]))) raise Exception("Gem update '%s' failed. See logs for more info"%x)
def composer_install(path): # Install a PHP application bundle via Composer. verify_composer() cwd = os.getcwd() os.chdir(path) shell("composer self-update") s = shell("composer install") os.chdir(cwd) if s["code"] != 0: raise Exception("Composer failed to install this app's bundle. Error: %s"%str(s["stderr"]))
def install_gem(*gems, **kwargs): # Installs a set of Ruby gems to the system. verify_path() gemlist = shell("gem list")["stdout"].split("\n") for x in gems: if not any(x==s for s in gemlist) or force: d = shell("gem install -N --no-user-install %s" % x) if d["code"] != 0: logger.error("Gem install '%s' failed: %s"%(x,str(d["stderr"]))) raise Exception("Gem install '%s' failed. See logs for more info"%x)
def post_install(self, extra_vars, dbpasswd=""): # Get around top-level zip restriction (FIXME 0.7.2) if "paperwork-master" in os.listdir(self.path): tmp_path = os.path.abspath(os.path.join(self.path, "../pwrk-tmp")) os.rename(os.path.join(self.path, "paperwork-master/frontend"), tmp_path) os.rename(os.path.join(self.path, ".arkos"), os.path.join(tmp_path, ".arkos")) shutil.rmtree(self.path) os.rename(tmp_path, self.path) # Make sure that the correct PHP settings are enabled php.enable_mod('gd', 'opcache', 'mysql', 'pdo_mysql', 'mcrypt') php.enable_mod('apcu', config_file="/etc/php/conf.d/apcu.ini") dbstr = "mysql, localhost, 3389, {0}, {1}, {0}"\ .format(self.id, dbpasswd) with open(os.path.join(self.path, 'app/storage/db_settings'), 'w') as f: f.write(dbstr) php.composer_install(self.path) nodejs.install("gulp", as_global=True) nodejs.install_from_package(self.path, stat=None) cwd = os.getcwd() os.chdir(self.path) s = shell("bower install --allow-root", stdin='y\n') if s["code"] != 0: raise Exception("Failed to run bower: {0}".format(s["stderr"])) s = shell("gulp") if s["code"] != 0: raise Exception("Failed to run gulp: {0}".format(s["stderr"])) s = shell("php artisan migrate --force") if s["code"] != 0: raise Exception("Failed to run artisan: {0}".format(s["stderr"])) os.chdir(cwd) # Make sure the webapps config points to the public directory. c = nginx.loadf(os.path.join('/etc/nginx/sites-available', self.id)) for x in c.servers: if x.filter('Key', 'root'): x.filter('Key', 'root')[0].value = \ os.path.join(self.path, 'public') nginx.dumpf(c, os.path.join('/etc/nginx/sites-available', self.id)) uid, gid = users.get_system("http").uid, groups.get_system("http").gid for r, d, f in os.walk(os.path.join(self.path, 'app')): for x in d: os.chmod(os.path.join(r, x), 0o755) os.chown(os.path.join(r, x), uid, gid) for x in f: os.chmod(os.path.join(r, x), 0o644) os.chown(os.path.join(r, x), uid, gid) if os.path.exists(os.path.join(self.path, 'app/storage/setup')): os.unlink(os.path.join(self.path, 'app/storage/setup'))
def update(self, pkg, ver): # General update procedure shell('unzip -o -d %s %s' % (self.path, pkg)) uid = users.get_system("ghost").uid for r, d, f in os.walk(self.path): for x in d: os.chown(os.path.join(r, x), uid, -1) for x in f: os.chown(os.path.join(r, x), uid, -1) nodejs.install_from_package(self.path, 'production', {'sqlite': '/usr/bin/sqlite3', 'python': '/usr/bin/python2'}) services.get(self.id).restart()
def remove_share(self): """Remove a share.""" with open("/etc/exports", "r") as f: data = f.readlines() for i, x in enumerate(data): if x.startswith('#'): continue if shlex.split(x)[0] == self.path: data.pop(i) with open("/etc/exports", "w") as f: f.writelines(data) shell("exportfs -arv")
def post_install(self, vars, dbpasswd=""): # Get around top-level zip restriction (FIXME 0.7.2) if "paperwork-master" in os.listdir(self.path): tmp_path = os.path.abspath(os.path.join(self.path, "../pwrk-tmp")) os.rename(os.path.join(self.path, "paperwork-master/frontend"), tmp_path) os.rename(os.path.join(self.path, ".arkos"), os.path.join(tmp_path, ".arkos")) shutil.rmtree(self.path) os.rename(tmp_path, self.path) # Make sure that the correct PHP settings are enabled php.enable_mod('gd', 'opcache', 'mysql', 'pdo_mysql', 'mcrypt') php.enable_mod('apcu', config_file="/etc/php/conf.d/apcu.ini") dbstr = "mysql, localhost, 3389, {0}, {1}, {0}".format(self.id, dbpasswd) with open(os.path.join(self.path, 'app/storage/db_settings'), 'w') as f: f.write(dbstr) php.composer_install(self.path) nodejs.install("gulp", as_global=True) nodejs.install_from_package(self.path, stat=None) cwd = os.getcwd() os.chdir(self.path) s = shell("bower install --allow-root", stdin='y\n') if s["code"] != 0: raise Exception("Failed to run bower: %s" % s["stderr"]) s = shell("gulp") if s["code"] != 0: raise Exception("Failed to run gulp: %s" % s["stderr"]) s = shell("php artisan migrate --force") if s["code"] != 0: raise Exception("Failed to run artisan: %s" % s["stderr"]) os.chdir(cwd) # Make sure the webapps config points to the public directory. c = nginx.loadf(os.path.join('/etc/nginx/sites-available', self.id)) for x in c.servers: if x.filter('Key', 'root'): x.filter('Key', 'root')[0].value = os.path.join(self.path, 'public') nginx.dumpf(c, os.path.join('/etc/nginx/sites-available', self.id)) uid, gid = users.get_system("http").uid, groups.get_system("http").gid for r, d, f in os.walk(os.path.join(self.path, 'app')): for x in d: os.chmod(os.path.join(r, x), 0755) os.chown(os.path.join(r, x), uid, gid) for x in f: os.chmod(os.path.join(r, x), 0644) os.chown(os.path.join(r, x), uid, gid) if os.path.exists(os.path.join(self.path, 'app/storage/setup')): os.unlink(os.path.join(self.path, 'app/storage/setup'))
def update(self, pkg, ver): # General update procedure shell('unzip -o -d {0} {1}'.format(self.path, pkg)) uid = users.get_system("ghost").uid for r, d, f in os.walk(self.path): for x in d: os.chown(os.path.join(r, x), uid, -1) for x in f: os.chown(os.path.join(r, x), uid, -1) nodejs.install_from_package(self.path, 'production', { 'sqlite': '/usr/bin/sqlite3', 'python': '/usr/bin/python2' }) services.get(self.id).restart()
def update_site(self, pkg, ver): cwd = os.getcwd() os.chdir(self.path) s = shell("bin/gpm selfupgrade -f") if s["code"] != 0: logger.error( "Webs", "Grav failed to run selfupgrade. Error: {0}".format( s["stderr"].decode())) s = shell("bin/gpm update -f") if s["code"] != 0: logger.error( "Webs", "Grav failed to run plugin update. Error: {0}".format( s["stderr"].decode())) os.chdir(cwd)
def add_rule(opt, protocol, port, ranges=[]): # Add rule for this port # If range is not provided, assume "0.0.0.0" cmd = "iptables -I arkos-apps {src} -p {ptc} -m {ptc} --dport {prt} -j {opt}" src = "" for x in [r for r in ranges if r not in ["", "anywhere", "0.0.0.0"]]: src = "-s " if not src else (src + ",") ip, cidr = x.split("/") mask = cidr_to_netmask(int(cidr)) src += ip + "/" + mask s = shell(cmd.format(src=src, ptc=protocol, prt=port, opt=opt)) if s["code"] != 0 and "No chain/target/match by that name" in s["stderr"]: # Create chain if not exists shell("iptables -N arkos-apps") shell(cmd.format(src=src, ptc=protocol, prt=port, opt=opt))
def make_json_error(err): """Prepare a standardized error report.""" if hasattr(err, "description"): message = err.description else: message = str(err) if (isinstance(err, HTTPException) and err.code == 500)\ or not isinstance(err, HTTPException): pyver = [str(x) for x in platform.python_version_tuple()] apps = arkos_storage.applications.values() apps = [x.id for x in apps if x.installed] stacktrace = traceback.format_exc() report = "arkOS {0} Crash Report\n".format(version) report += "--------------------\n\n" report += "Running in {0}\n".format(config.get("enviro", "run")) report += "System: {0}\n".format(shell("uname -a")["stdout"].decode()) report += "Platform: {0} {1}\n".format(config.get("enviro", "arch"), config.get("enviro", "board")) report += "Python version {0}\n".format('.'.join(pyver)) report += "Config path: {0}\n\n".format(config.filename) report += "Loaded applicatons: \n{0}\n\n".format("\n".join(apps)) report += "Request: {0} {1}\n\n".format(request.method, request.path) report += stacktrace response = jsonify(errors={"msg": message, "stack": stacktrace, "report": report, "version": version, "arch": config.get("enviro", "arch")}) logger.critical("Unknown", stacktrace) else: response = jsonify(errors={"msg": message}) response.status_code = err.code if isinstance(err, HTTPException) else 500 return add_cors_to_response(response)
def create(self, mount=False): vdisk_dir = config.get("filesystems", "vdisk_dir") if not os.path.exists(os.path.join(config.get("filesystems", "vdisk_dir"))): os.mkdir(os.path.join(config.get("filesystems", "vdisk_dir"))) self.path = str(os.path.join(vdisk_dir, self.id+".img")) if os.path.exists(self.path): raise Exception("This virtual disk already exists") signals.emit("filesystems", "pre_add", self) # Create an empty file matching disk size with open(self.path, "wb") as f: written = 0 with file("/dev/zero", "r") as zero: while self.size > written: written += 1024 f.write(zero.read(1024)) # Get a free loopback device and mount loop = losetup.find_unused_loop_device() loop.mount(str(self.path), offset=1048576) # Make a filesystem s = shell("mkfs.ext4 %s" % loop.device) if s["code"] != 0: raise Exception("Failed to format loop device: %s" % s["stderr"]) loop.unmount() signals.emit("filesystems", "pre_add", self) if mount: self.mount()
def install(*mods, **kwargs): """ Install a set of NPM packages. Include ``as_global`` in kwargs to install package as global. :param *mods: Packages to install :param **kwargs: Extra keyword arguments to pass to NPM """ as_global = "-g " if kwargs.get("as_global") else "" mods = " ".join(x for x in mods) opt_str = "" if kwargs.get("opts"): opt_str += " --" for k, v in kwargs.get("opts", {}): opt_str += " --".join(k + v if v[0] == '=' else k + " " + v) cwd = os.getcwd() if "install_path" in kwargs: os.chdir(kwargs["install_path"]) s = shell("npm install {0}{1}{2}".format( as_global, mods, )) os.chdir(cwd) if s["code"] != 0: errmsg = "NPM install of {0} failed.".format(mods) logmsg = "NPM install failure details:\n{0}" logger.error("NodeJS", logmsg.format(s["stderr"].decode())) raise errors.OperationFailedError(errmsg)
def encrypt(self, passwd, cipher="", keysize=0, mount=False): cipher = cipher or config.get("filesystems", "cipher") or "aes-xts-plain64" keysize = keysize or config.get("filesystems", "keysize") or 256 os.rename(self.path, os.path.join(config.get("filesystems", "vdisk_dir"), self.id+".crypt")) self.path = os.path.join(config.get("filesystems", "vdisk_dir"), self.id+".crypt") # Find an open loopback device and mount loop = losetup.find_unused_loop_device() loop.mount(str(self.path), offset=1048576) # Encrypt the file inside the loopback and mount s = crypto.luks_format(loop.device, passwd, cipher, int(keysize)) if s != 0: loop.unmount() os.rename(self.path, os.path.join(config.get("filesystems", "vdisk_dir"), self.id+".img")) raise Exception("Failed to encrypt %s with errno %s"%(self.id, str(s))) s = crypto.luks_open(loop.device, self.id, passwd) if s != 0: loop.unmount() raise Exception("Failed to decrypt %s with errno %s"%(self.id, str(s))) # Create a filesystem inside the encrypted device s = shell("mkfs.ext4 /dev/mapper/%s" % self.id) crypto.luks_close(self.id) loop.unmount() if s["code"] != 0: raise Exception("Failed to format loop device: %s" % s["stderr"]) self.crypt = True if mount: self.mount(passwd)
def get_connections(id=None, iface=None): """ Get list of network connections. :param str id: Filter by network connection name :param str iface: Filter by network interface name :returns: Connection(s) :rtype: Connection or list thereof """ conns = [] netctl = shell("netctl list") for line in netctl["stdout"].split(b"\n"): if not line.split(): continue svc = "/etc/systemd/system/multi-user.target.wants/netctl@{0}.service" enabled = os.path.exists(svc.format(line[2:])) c = Connection(line[2:].decode(), line.startswith(b"*"), enabled) with open(os.path.join("/etc/netctl", c.id), "r") as f: data = f.readlines() for x in data: if x.startswith("#") or not x.strip(): continue parse = x.split("=") to_trans = dict.fromkeys(map(ord, "()\"\"\n"), None) c.config[parse[0].lower()] = parse[1].translate(to_trans) if id == c.id: return c if not iface or c.config.get("interface") == iface: conns.append(c) return conns if not id else None
def install_updates(message=DefaultMessage()): updates = storage.updates.get("updates") if not updates: return signals.emit("updates", "pre_install") amount = len(updates) responses, ids = [], [] for z in enumerate(updates): message.update("info", "%s of %s..." % (z[0]+1, amount), head="Installing updates") for x in sorted(z[1]["tasks"], key=lambda y: y["step"]): getout = False if x["unit"] == "shell": s = shell(x["order"], stdin=x.get("data", None)) if s["code"] != 0: responses.append((x["step"], s["stderr"])) getout = True break elif x["unit"] == "fetch": try: download(x["order"], x["data"], True) except Exception, e: code = 1 if hasattr(e, "code"): code = e.code responses.append((x["step"], str(code))) getout = True break else: ids.append(z[1]["id"]) config.set("updates", "current_update", z[1]["id"]) config.save() continue message.complete("error", "Installation of update %s failed. See logs for details." % str(z[1]["id"])) print responses break
def get_log(self): if self.stype == "supervisor": supervisor_ping() s = conns.Supervisor.tailProcessStdoutLog(self.name) else: s = shell("systemctl --no-ask-password status {}.service".format(self.name))["stdout"] return s
def add_share(self): """Add a share.""" with open("/etc/exports", "r") as f: data = f.readlines() if any([shlex.split(x)[0] == self.path for x in data]): raise errors.InvalidConfigError( "Share already present with this path" ) if not os.path.exists(self.path): os.makedirs(self.path) sstr = '"' + self.path + '" ' sstr += '*(' + ("ro" if self.readonly else "rw") + ',sync)' with open("/etc/exports", "w") as f: f.writelines(data) f.write(sstr + '\n') shell("exportfs -arv")
def is_installed(name): # Returns a list of installed Python packages. s = shell("pip2 freeze") for x in s["stdout"].split("\n"): if name.lower() in x.split("==")[0].lower(): return True return False
def post_install(self, extra_vars, dbpasswd=""): # Make sure the webapps config points to # the _site directory and generate it. c = nginx.loadf(os.path.join('/etc/nginx/sites-available', self.id)) for x in c.servers: if x.filter('Key', 'root'): x.filter('Key', 'root')[0].value = \ os.path.join(self.path, '_site') nginx.dumpf(c, os.path.join('/etc/nginx/sites-available', self.id)) s = shell('jekyll build --source {0} --destination {1}'.format( self.path, os.path.join(self.path, '_site'))) if s["code"] != 0: raise errors.OperationFailedError( 'Jekyll failed to build: {0}'.format(str(s["stderr"]))) uid, gid = users.get_system("http").uid, groups.get_system("http").gid for r, d, f in os.walk(self.path): for x in d: os.chmod(os.path.join(r, x), 0o755) os.chown(os.path.join(r, x), uid, gid) for x in f: os.chmod(os.path.join(r, x), 0o644) os.chown(os.path.join(r, x), uid, gid) # Return an explicatory message. return 'Jekyll has been setup, with a sample site at {0}. '\ 'Modify these files as you like. To learn how to use Jekyll, '\ 'visit http://jekyllrb.com/docs/usage. After making changes, '\ 'click the site icon to edit, then "Regenerate Site" '\ 'to bring your changes live.'.format(self.path)
def disable(self): """Disable connection on boot.""" s = shell("netctl disable {0}".format(self.id)) if s["code"] == 0: self.enabled = False else: raise errors.OperationFailedError("Network disable failed")
def post_install(self, extra_vars, dbpasswd=""): secret_key = random_string() dbengine = 'mysql' \ if self.app.selected_dbengine == 'db-mariadb' \ else 'sqlite' # Write a standard Wallabag config file config_file = os.path.join(self.path, 'app/config/parameters.yml') with open(config_file + ".dist", 'r') as f: ic = f.readlines() with open(config_file, 'w') as f: for l in ic: if "database_driver: " in l: pdo = "pdo_mysql" if dbengine == "mysql" else "pdo_sqlite" l = " database_driver: {0}\n".format(pdo) elif "database_path: " in l and dbengine == 'sqlite': l = " database_path: {0}\n".format(self.db.path) elif "database_name: " in l and dbengine == 'mysql': l = " database_name: {0}\n".format(self.db.id) elif "database_user: "******" database_user: {0}\n".format(self.db.id) elif "database_password: "******"{0}"\n'.format(dbpasswd) elif "secret: " in l: l = " secret: {0}\n".format(secret_key) f.write(l) # Make sure that the correct PHP settings are enabled php.enable_mod('sqlite3', 'bcmath', 'pdo_mysql' if dbengine == 'mysql' else 'pdo_sqlite', 'zip', 'tidy') php.open_basedir('add', '/usr/bin/php') uid, gid = users.get_system("http").uid, groups.get_system("http").gid # Set up the database then delete the install folder if dbengine == 'sqlite3': php.open_basedir('add', '/var/lib/sqlite3') cwd = os.getcwd() os.chdir(self.path) s = shell("php bin/console wallabag:install --env=prod -n") if s["code"] != 0: logger.error("Websites", s["stderr"].decode()) raise errors.OperationFailedError( "Failed to populate database. See logs for more info") os.chdir(cwd) if dbengine == 'sqlite3': os.chown("/var/lib/sqlite3/{0}.db".format(self.db.id), -1, gid) os.chmod("/var/lib/sqlite3/{0}.db".format(self.db.id), 0o660) # Finally, make sure that permissions are set so that Wallabag # can make adjustments and save plugins when need be. for r, d, f in os.walk(self.path): for x in d: os.chown(os.path.join(r, x), uid, gid) for x in f: os.chown(os.path.join(r, x), uid, gid)
def disconnect(self): signals.emit("networks", "pre_disconnect", self) s = shell("netctl stop %s" % self.id) if s["code"] == 0: self.connected = False signals.emit("networks", "post_disconnect", self) else: raise Exception("Network disconnection failed")
def _init_samba_for_ldap(self): shell("smbpasswd -w {0}".format(secrets.get("ldap"))) config = configparser.ConfigParser() config.read(["/etc/samba/smb.conf"]) config.set("global", "passdb backend", 'ldapsam:"ldap://localhost/"') config.set("global", "ldap ssl", "off") config.set("global", "ldap passwd sync", "no") config.set("global", "ldap suffix", "dc=arkos-servers,dc=org") config.set("global", "ldap user suffix", "ou=users") config.set("global", "ldap group suffix", "ou=groups") config.set( "global", "ldap admin dn", "cn=admin,dc=arkos-servers,dc=org" ) if config.has_section("homes"): config.remove_section("homes") with open("/etc/samba/smb.conf", "w") as f: config.write(f)
def nslcd(): """Initialize distribution PAM integration of OpenLDAP.""" patchfiles = [ ("/etc/pam.d/system-auth", "001-add-ldap-to-system-auth.patch"), ("/etc/pam.d/su", "002-add-ldap-to-su.patch"), ("/etc/pam.d/su-l", "003-add-ldap-to-su-l.patch"), ("/etc/pam.d/passwd", "004-add-ldap-to-passwd.patch"), ("/etc/pam.d/system-login", "005-add-ldap-to-system-login.patch"), ("/etc/nsswitch.conf", "006-add-ldap-to-nsswitch.patch"), ("/etc/nslcd.conf", "007-add-ldap-to-nslcd.patch") ] for x in patchfiles: if not os.path.exists(os.path.join("/usr/share/arkos/nslcd", x[1])): raise CLIException( "Patch files could not be found. Your installation may " "be corrupted. Please reinstall the `arkos-configs` package.") logger.debug('ctl:init:nslcd', 'Stopping daemon: nslcd') s = shell("systemctl stop nslcd") if s["code"] != 0: raise click.ClickException(s["stderr"].decode()) logger.info('ctl:init:nslcd', 'Patching system files') for x in patchfiles: shell("patch -N {0} {1}".format( x[0], os.path.join("/usr/share/arkos/nslcd", x[1]))) logger.debug('ctl:init:nslcd', 'Starting daemon: nslcd') shell("systemctl enable nslcd") shell("systemctl start nslcd") logger.success('ctl:init:nslcd', 'Complete')
def get_log(self): """Get supervisor service logs.""" if self.stype == "supervisor": supervisor_ping() s = conns.Supervisor.tailProcessStdoutLog(self.name) else: s = shell("systemctl --no-ask-password status {0}".format( self.sfname))["stdout"] return s
def disconnect(self): """Disconnect from network.""" signals.emit("networks", "pre_disconnect", self) s = shell("netctl stop {0}".format(self.id)) if s["code"] == 0: self.connected = False signals.emit("networks", "post_disconnect", self) else: raise errors.OperationFailedError("Network disconnection failed")
def install_from_package(path, stat="production", opts={}): # Installs a set of NPM package dependencies from an NPM package.json. cwd = os.getcwd() os.chdir(path) s = shell("npm install %s%s" % (" --"+stat if stat else "", " --"+" --".join(x+"="+opts[x] for x in opts) if opts else "")) os.chdir(cwd) if s["code"] != 0: logger.error("NPM install of %s failed; log output follows:\n%s"%(path,s["stderr"])) raise Exception("NPM install failed, check logs for info")
def show_version(): """Show version and diagnostic details""" click.echo(shell("uname -a")["stdout"].decode().rstrip("\n")) click.echo( click.style(" * arkOS server version: ", fg="yellow") + config.get("enviro", "version", "Unknown")) click.echo( click.style(" * Arch / Board: ", fg="yellow") + config.get("enviro", "arch", "Unknown") + " / " + config.get("enviro", "board", "Unknown"))
def add_db(self): if re.search('\.|-|`|\\\\|\/|[ ]', self.id): raise errors.InvalidConfigError( 'Name must not contain spaces, dots, dashes or other ' 'special characters') self.manager.chkpath() status = shell("sqlite3 {0} \"ATTACH '{1}' AS {2};\"".format( self.path, self.path, self.id)) if status["code"] >= 1: raise errors.OperationFailedError(status["stderr"])
def connect(self): signals.emit("networks", "pre_connect", self) for x in get_connections(iface=self.config.get("interface")): x.disconnect() s = shell("netctl start %s" % self.id) if s["code"] == 0: self.connected = True signals.emit("networks", "post_connect", self) else: raise Exception("Network connection failed")
def regenerate_firewall(data, range=[]): # Regenerate our chain. # If local ranges are not provided, get them. signals.emit("security", "pre_fw_regen") flush_chain("arkos-apps") default_range = range or network.get_active_ranges() # For each policy in the system, add a rule for x in data: range = getattr(x, "allowed_ranges", default_range) for port in x.ports: if x.policy == 2: add_rule("ACCEPT", port[0], port[1], ["anywhere"]) elif x.policy == 1: add_rule("ACCEPT", port[0], port[1], range) else: add_rule("REJECT", port[0], port[1]) shell("iptables -A arkos-apps -j RETURN") save_rules() signals.emit("security", "post_fw_regen")
def connect(self, user='******', passwd='', db=None): passwd = passwd or secrets.get("mysql") if not os.path.exists("/var/lib/mysql/mysql"): shell("mysql_install_db --user=mysql --basedir=/usr --datadir=/var/lib/mysql") try: conns.MariaDB.ping() self.state = True return except: pass try: if not passwd: passwd = self.change_admin_passwd() conns.MariaDB = MySQLdb.connect('localhost', user, passwd, db or "") self.state = True self.connection = conns.MariaDB except: self.state = False raise ConnectionError("MariaDB")
def pre_backup(self): """Reimplement.""" s = shell("slapcat -n 1") if s["code"] != 0: emsg = ("Could not backup LDAP database. " "Please check logs for errors.") logger.error("Backup", s["stderr"].decode()) raise errors.OperationFailedError(emsg) with open("/tmp/ldap.ldif", "wb") as f: f.write(s["stdout"])
def add_db(self): if re.search('\.|-|`|\\\\|\/|[ ]', self.id): raise Exception( 'Name must not contain spaces, dots, dashes or other special characters' ) self.manager.chkpath() path = '/var/lib/sqlite3/%s.db' % self.id status = shell('sqlite3 %s "ATTACH \'%s\' AS %s;"' % (path, path, self.id)) if status["code"] >= 1: raise Exception(status["stderr"])
def is_installed(name, as_global=True): """ Return whether an NPM package is installed. :param str name: NPM package name :param bool as_global: Check global NPM instead of local """ s = shell("npm list -p {0}{1}".format("-g " if as_global else "", name)) if name in s['stdout']: return True return False
def install(*mods, **kwargs): # Installs a set of NPM packages. as_global = kwargs.get("as_global", False) cwd = os.getcwd() if "install_path" in kwargs: os.chdir(kwargs["install_path"]) s = shell("npm install %s%s%s" % ("-g " if as_global else "", " ".join(x for x in mods), (" --"+" --".join(k+v if v[0]=='=' else k+" "+v for k,v in kwargs["opts"].items()) if kwargs.has_key("opts") else ""))) os.chdir(cwd) if s["code"] != 0: logger.error("NPM install of %s failed; log output follows:\n%s"%(" ".join(x for x in mods),s["stderr"])) raise Exception("NPM install failed, check logs for info")
def post_restore(self): if not os.path.exists("/tmp/ldap.ldif"): raise Exception("Could not restore LDAP database. Please check logs for errors.") with open("/tmp/ldap.ldif", "r") as f: ldif = f.read() s = shell('ldapadd -D "cn=admin,dc=arkos-servers,dc=org" -w %s' % secrets.get("ldap"), stdin=ldif) if os.path.exists("/tmp/ldap.ldif"): os.unlink("/tmp/ldap.ldif") if s["code"] != 0: raise Exception("Could not restore LDAP database. Please check logs for errors.")
def connect(self): """Connect to network.""" signals.emit("networks", "pre_connect", self) for x in get_connections(iface=self.config.get("interface")): x.disconnect() s = shell("netctl start {0}".format(self.id)) if s["code"] == 0: self.connected = True signals.emit("networks", "post_connect", self) else: raise errors.OperationFailedError("Network connection failed")
def nginx(): """Initialize default nginx configuration.""" if not os.path.exists("/usr/share/arkos/nginx.conf"): raise CLIException( "Template files could not be found. Your installation may " "be corrupted. Please reinstall the `arkos-configs` package.") logger.info('ctl:init:nginx', 'Copying files') if not os.path.exists("/srv/http/webapps"): os.makedirs("/srv/http/webapps") if not os.path.exists("/etc/nginx/sites-available"): os.makedirs("/etc/nginx/sites-available") if not os.path.exists("/etc/nginx/sites-enabled"): os.makedirs("/etc/nginx/sites-enabled") shutil.copy("/usr/share/arkos/nginx.conf", "/etc/nginx/nginx.conf") logger.debug('ctl:init:nginx', 'Restarting daemon: nginx') shell("systemctl enable nginx") shell("systemctl restart nginx") logger.success('ctl:init:nginx', 'Completed')
def connect(self, user='******', passwd='', db=None): passwd = passwd or secrets.get("mysql") if not os.path.exists("/var/lib/mysql/mysql"): shell("mysql_install_db --user=mysql --basedir=/usr " "--datadir=/var/lib/mysql") try: conns.MariaDB.ping() self.state = True return except: pass try: if not passwd: passwd = self.change_admin_passwd() conns.MariaDB = MySQLdb.connect('localhost', user, passwd, db or "") self.state = True self.connection = conns.MariaDB except: self.state = False raise errors.ConnectionError("MariaDB")
def remove(*mods): """ Remove a set of Python packages from the system. :param *mods: packages to remove """ s = shell("pip uninstall {0}".format(mods)) if s["code"] != 0: errmsg = "PyPI uninstall of {0} failed.".format(mods) logmsg = "PyPI uninstall failure details:\n{0}" logger.error("Python", logmsg.format(s["stderr"].decode())) raise errors.OperationFailedError(errmsg)
def get_installed(py2=False): """ Get all installed Python packages. Returns in format `{"id": "package_name", "version": "1.0.0"}`. :param bool py2: Check Python 2.x packages instead of 3.x """ s = shell("pip{0} freeze".format("2" if py2 else "")) return [{ "id": x.split(b"==")[0].decode(), "version": x.split(b"==")[1].decode() } for x in s["stdout"].split(b"\n") if x.split() and b"==" in x]
def generate_certificate( id, domain, country, state="", locale="", email="", keytype="RSA", keylength=2048, message=DefaultMessage()): signals.emit("certificates", "pre_add", id) # Check to see that we have a CA ready; if not, generate one basehost = ".".join(domain.split(".")[-2:]) ca = get_authorities(id=basehost) if not ca: message.update("info", "Generating certificate authority...") ca = generate_authority(basehost) with open(ca.cert_path, "r") as f: ca_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read()) with open(ca.key_path, "r") as f: ca_key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, f.read()) # Check to see that we have DH params, if not then do that too if not os.path.exists("/etc/arkos/ssl/dh_params.pem"): message.update("info", "Generating Diffie-Hellman parameters...") s = shell("openssl dhparam 2048 -out /etc/arkos/ssl/dh_params.pem") if s["code"] != 0: raise Exception("Failed to generate Diffie-Hellman parameters") os.chown("/etc/arkos/ssl/dh_params.pem", -1, gid) os.chmod("/etc/arkos/ssl/dh_params.pem", 0750) # Generate private key and create X509 certificate, then set options message.update("info", "Generating certificate...") kt = OpenSSL.crypto.TYPE_DSA if keytype == "DSA" else OpenSSL.crypto.TYPE_RSA try: key = OpenSSL.crypto.PKey() key.generate_key(kt, keylength) crt = OpenSSL.crypto.X509() crt.set_version(3) crt.get_subject().C = country crt.get_subject().CN = domain if state: crt.get_subject().ST = state if locale: crt.get_subject().L = locale if email: crt.get_subject().emailAddress = email crt.get_subject().O = "arkOS Servers" crt.set_serial_number(int(systemtime.get_serial_time())) crt.gmtime_adj_notBefore(0) crt.gmtime_adj_notAfter(2*365*24*60*60) crt.set_issuer(ca_cert.get_subject()) crt.set_pubkey(key) crt.sign(ca_key, "sha256") except Exception, e: raise Exception("Error generating self-signed certificate: "+str(e))
def install_composer(): # Installs Composer to the system. cwd = os.getcwd() os.chdir("/root") os.environ["COMPOSER_HOME"] = "/root" enable_mod("phar") open_basedir("add", "/root") installer = urllib2.urlopen("https://getcomposer.org/installer").read() s = shell("php", stdin=installer) os.chdir(cwd) if s["code"] != 0: raise Exception("Composer download/config failed. Error: %s"%str(s["stderr"])) os.rename("/root/composer.phar", "/usr/local/bin/composer") os.chmod("/usr/local/bin/composer", 755) open_basedir("add", "/usr/local/bin")
def regenerate(self): path = self.path if not path.endswith("_site"): path = os.path.join(self.path, "_site") s = shell('jekyll build --source '+self.path.split('/_site')[0]+' --destination '+path) if s["code"] != 0: raise Exception('Jekyll failed to build: %s'%str(s["stderr"])) uid, gid = users.get_system("http").uid, groups.get_system("http").gid for r, d, f in os.walk(self.path): for x in d: os.chmod(os.path.join(r, x), 0755) os.chown(os.path.join(r, x), uid, gid) for x in f: os.chmod(os.path.join(r, x), 0644) os.chown(os.path.join(r, x), uid, gid)
def update(self, pkg, ver): # General update procedure shell('tar xzf %s -C %s --strip 1' % (pkg, self.path)) for x in os.listdir(os.path.join(self.path, 'cache')): if os.path.isdir(os.path.join(self.path, 'cache', x)): shutil.rmtree(os.path.join(self.path, 'cache', x)) else: os.unlink(os.path.join(self.path, 'cache', x)) shutil.rmtree(os.path.join(self.path, 'install')) shell('chmod -R 755 '+os.path.join(self.path, 'assets/')+' ' +os.path.join(self.path, 'cache/')+' ' +os.path.join(self.path, 'db/')) shell('chown -R http:http '+self.path)
def get_connections(id=None, iface=None): conns = [] netctl = shell("netctl list") for line in netctl["stdout"].split("\n"): if not line.split(): continue enabled = os.path.exists("/etc/systemd/system/multi-user.target.wants/netctl@%s.service" % line[2:]) c = Connection(id=line[2:], connected=line.startswith("*"), enabled=enabled) with open(os.path.join("/etc/netctl", c.id), "r") as f: data = f.readlines() for x in data: if x.startswith("#") or not x.strip(): continue parse = x.split("=") c.config[parse[0].lower()] = parse[1].translate(None, "()\"\"\n") if id == c.id: return c if not iface or c.config.get("interface") == iface: conns.append(c) return conns if not id else None
def post_install(self, vars, dbpasswd=""): # Make sure the webapps config points to the _site directory and generate it. c = nginx.loadf(os.path.join('/etc/nginx/sites-available', self.id)) for x in c.servers: if x.filter('Key', 'root'): x.filter('Key', 'root')[0].value = os.path.join(self.path, '_site') nginx.dumpf(c, os.path.join('/etc/nginx/sites-available', self.id)) s = shell('jekyll build --source '+self.path+' --destination '+os.path.join(self.path, '_site')) if s["code"] != 0: raise Exception('Jekyll failed to build: %s'%str(s["stderr"])) uid, gid = users.get_system("http").uid, groups.get_system("http").gid for r, d, f in os.walk(self.path): for x in d: os.chmod(os.path.join(r, x), 0755) os.chown(os.path.join(r, x), uid, gid) for x in f: os.chmod(os.path.join(r, x), 0644) os.chown(os.path.join(r, x), uid, gid) # Return an explicatory message. return 'Jekyll has been setup, with a sample site at '+self.path+'. Modify these files as you like. To learn how to use Jekyll, visit http://jekyllrb.com/docs/usage. After making changes, click the Edit button for the site, then "Regenerate Site" to bring your changes live.'
def genesis_init(state): path = "" apps = applications.get() if config.get("enviro", "run") == "vagrant": path = '/home/vagrant/genesis' elif config.get("enviro", "run") == "dev": sdir = os.path.dirname(os.path.realpath(__file__)) path = os.path.abspath(os.path.join(sdir, '../../genesis')) elif os.path.exists('/var/lib/arkos/genesis'): path = '/var/lib/arkos/genesis' if not os.path.exists(path): return backend.add_url_rule('/', defaults={'path': None}, view_func=genesis, methods=['GET',]) backend.add_url_rule('/<path:path>', view_func=genesis, methods=['GET',]) for x in os.listdir(os.path.join(path, 'lib')): if os.path.islink(os.path.join(path, 'lib', x)): os.unlink(os.path.join(path, 'lib', x)) libpaths = [] for x in apps: genpath = "/var/lib/arkos/applications/%s/genesis" % x.id if os.path.exists(genpath): libpaths.append("lib/%s"%x.id) os.symlink(genpath, os.path.join(path, 'lib', x.id)) if libpaths: with open(os.path.join(path, 'package.json'), 'r') as f: data = json.loads(f.read()) data["ember-addon"] = {"paths": libpaths} with open(os.path.join(path, 'package.json'), 'w') as f: f.write(json.dumps(data, sort_keys=True, indent=2, separators=(',', ': '))) mydir = os.getcwd() os.chdir(path) s = shell("ember build") os.chdir(mydir) if s["code"] != 0: raise Exception("Genesis rebuild process failed")
def disable(self): s = shell("netctl disable %s" % self.id) if s["code"] == 0: self.enabled = False else: raise Exception("Network disable failed")
def enable(self): s = shell("netctl enable %s" % self.id) if s["code"] == 0: self.enabled = True else: raise Exception("Network enable failed")
def disable(self): shell("systemctl disable netctl-auto@%s.service" % self.id)
def bring_down(self): shell("ip link set dev %s down" % self.id)