class ProjectManager: def __init__(self): add_exports( ( ('getitem', self.getitem), ('getbool', self.getbool), ('setitem', self.setitem), ('setbool', self.setbool), ('get_projects', self.list_projects), ('get_profiles', self.list_profiles), ('get_installation_dir', self.get_ipath), ('set_installation_dir', self.set_ipath), ('testlarchify', self.testlarchify), ('set_project', self.set_projectname), ('get_project', self.get_projectname), ('delete_project', self.delete_project), ('delete_profile', self.delete_profile), ('list_free_projects', self.list_free_projects), ('list_free_profiles', self.list_free_profiles), ('get_new_profile', self.get_new_profile), ('rename_profile', self.rename_profile), ('can_rename_profile', self.can_rename_profile), ('save_profile', self.save_profile), ('get_profile', self.get_profile), ('set_profile', self.set_profile), ('get_example_profiles', self.get_example_profiles), ('set_profile_browse_dir', self.set_profile_browse_dir), ('get_mediumlabel', self.get_mediumlabel), ('set_mediumlabel', self.set_mediumlabel), ('getisosavedir', self.getisosavedir), ('getisofile', self.getisofile), ('getbootisofile', self.getbootisofile), ('get_bootisolabel', self.get_bootisolabel), ('set_bootisolabel', self.set_bootisolabel), ('newUserinfo', self.newUserinfo), ('allusers', self.allusers), ('getuserinfo', self.getuserinfo), ('newuser', self.newuser), ('userset', self.userset), ('deluser', self.deluser), ('listskels', self.listskels), ('saveusers', self.saveusers)) ) def init(self): self.projects_base = os.path.join(os.environ['HOME'], CONFIG_DIR) self.profiles_dir = os.path.join(self.projects_base, 'myprofiles') # Ensure the presence of the larch default project folder dpf = '%s/p_%s' % (self.projects_base, PROJECT0) if not os.path.isdir(dpf): os.makedirs(dpf) # Ensure the presence of the profiles folder and the 'default' profile if not os.path.isdir(self.profiles_dir): os.mkdir(self.profiles_dir) self.default_profile_dir = os.path.join(self.profiles_dir, PROFILE0) if not os.path.isdir(self.default_profile_dir): call(['cp', '-a', base_dir + '/profiles/'+ PROFILE0, self.profiles_dir]) # The application configs self.aconfig_file = os.path.join(self.projects_base, APP_CONF) self.aconfig = self.getconfig(self.aconfig_file) # The project-specific configs self.set_projectname(self.appget('project')) def get_projectname(self): return (True, self.project_name) def set_projectname(self, name): self.project_dir = os.path.join(self.projects_base, 'p_' + name) plist = self.list_projects()[1] if name not in plist: os.mkdir(self.project_dir) self.pconfig_file = os.path.join(self.project_dir, PROJECT_CONF) self.pconfig = self.getconfig(self.pconfig_file) self.profile_name = self.get('profile') self.profile_path = os.path.join(self.profiles_dir, self.profile_name) self.appset('project', name) self.project_name = name return (True, None) def delete_project(self, name): # This should probably be run as root, in case the build directory # is inside it ... cross that bridge when we come to it! r = call(['rm', '-r', '--interactive=never', os.path.join(self.projects_base, 'p_' + name)]) return (True, r == 0) def delete_profile(self, name): r = call(['rm', '-r', '--interactive=never', os.path.join(self.profiles_dir, name)]) return (True, r == 0) def get_profile(self): return (True, self.profile_name) def set_profile(self, name): self.set('profile', name) self.profile_name = name self.profile_path = os.path.join(self.profiles_dir, self.profile_name) return (True, None) def rename_profile(self, name): os.rename(self.profile_path, os.path.join(self.profiles_dir, name)) self.set_profile(name) return (True, None) def get_new_profile(self, src, name): if not os.path.isfile(src + '/addedpacks'): return (True, False) dst = os.path.join(self.profiles_dir, name) call(['rm', '-rf', dst]) shutil.copytree(src, dst, symlinks=True) self.set_profile(name) return (True, True) def get_example_profiles(self): pd = base_dir + '/profiles' return (True, (pd, os.listdir(pd))) # location of working profiles # self.projects_base + '/myprofiles', # What about not allowing changes to the default profile? # That would mean also no renaming? # One would have to copy a profile into the project before going # any further ... # Is it right to share profiles between projects? (Probably) #+++++++++++++++++++++++++++++++++++++++++++++++ ### A very simple configuration file handler def getconfig(self, filepath): cfg = {} if os.path.isfile(filepath): fh = open(filepath) for line in fh: ls = line.split('=', 1) if len(ls) > 1: cfg[ls[0].strip()] = ls[1].strip() return cfg def saveconfig(self, filepath, config): fh = open(filepath, 'w') ci = config.items() ci.sort() for kv in ci: fh.write('%s = %s\n' % kv) fh.close() ### #----------------------------------------------- def list_projects(self): projects = [p[2:] for p in os.listdir(self.projects_base) if p.startswith('p_')] projects.sort() return (True, projects) def list_free_projects(self): """This returns a list of projects which are free for (e.g.) deletion. """ plist = self.list_projects()[1] plist.remove(PROJECT0) # this one is not 'free' if self.project_name in plist: plist.remove(self.project_name) return (True, plist) def list_profiles(self): profiles = [d for d in os.listdir(self.profiles_dir) if os.path.isfile(os.path.join(self.profiles_dir, d, 'addedpacks'))] profiles.sort() return (True, profiles) def list_free_profiles(self): """This returns a list of profiles which are not in use by any project. """ plist = self.list_profiles()[1] plist.remove(PROFILE0) # this one is not 'free' for project in self.list_projects()[1]: cfg = self.getconfig(os.path.join(self.projects_base, 'p_' + project, PROJECT_CONF)) p = cfg.get('profile') if p in plist: plist.remove(p) return (True, plist) def can_rename_profile(self): if self.profile_name == PROFILE0: return (True, False) for project in self.list_projects()[1]: if project != self.project_name: cfg = self.getconfig(os.path.join(self.projects_base, 'p_' + project, PROJECT_CONF)) if self.profile_name == cfg.get('profile'): return (True, False) return (True, True) def save_profile(self, path, force): if path[0] != '/': # cloning, only the profile name is passed path = os.path.join(self.profiles_dir, path) else: # copying, the destination will have the same name if os.path.basename(path) != self.profile_name: path = os.path.join(path, self.profile_name) if os.path.exists(path): if force: call(['rm', '-rf', path]) elif os.path.isfile(os.path.join(path, 'addedpacks')): # This is an existing profile return (True, False) else: # This location is otherwise in use return (True, None) shutil.copytree(self.profile_path, path, symlinks=True) return (True, True) def set_profile_browse_dir(self, path): if os.path.isfile(os.path.join(path, 'addedpacks')): # Don't set the profile browse path to a profile directory, # but rather tp the containing directory path = os.path.dirname(path) self.set('profile_browse_dir', path) def appget(self, item): """Read an entry in the application configuration. """ v = self.aconfig.get(item) if v: return v elif APP_DEFAULTS.has_key(item): return APP_DEFAULTS[item] debug("Unknown application configuration option: %s" % item) assert False def appset(self, item, value): """Set an entry in the application configuration. """ self.aconfig[item] = value.strip() self.saveconfig(self.aconfig_file, self.aconfig) def getitem(self, item): return (True, self.get(item)) def getbool(self, item): return (True, self.get(item) == 'Yes') def setitem(self, item, value): self.set(item, value) return (True, None) def setbool(self, item, on): self.set(item, 'Yes' if on else 'No') return (True, None) def get(self, item): """Read an entry in the project configuration. """ v = self.pconfig.get(item) if v: return v elif DEFAULTS.has_key(item): return DEFAULTS[item] debug("Unknown configuration option: %s" % item) assert False def set(self, item, value): """Set an entry in the project configuration. """ self.pconfig[item] = value.strip() self.saveconfig(self.pconfig_file, self.pconfig) # return True def get_ipath(self): ip = self.get('installation_dir') if not ip: ip = self.set_ipath('')[1] return (True, ip) def set_ipath(self, path): path = path.strip() if path: path = '/' + path.strip('/') else: path = os.environ['HOME'] + '/larch_build' self.set('installation_dir', path) return (True, path) def testlarchify(self): ipath = self.get_ipath()[1] path = ipath + CHROOT_DIR_MEDIUM bl = [] nosave = False ok = (os.path.isfile(path + '/larch/system.sqf') and os.path.isfile(ipath + SYSLINUXDIR + '/isolinux.bin')) return (True, (path, ok)) def newUserinfo(self): self.userInfo = Userinfo(self.profile_path) return (True, None) def allusers(self): return (True, self.userInfo.allusers()) def getuserinfo(self, user, fields): return (True, self.userInfo.userinfo(user, fields)) def newuser(self, user): return (True, self.userInfo.newuser(user)) def saveusers(self): return (True, self.userInfo.saveusers()) def userset(self, uname, field, text): self.userInfo.userset(uname, field, text) return (True, None) def deluser(self, user): return (True, self.userInfo.deluser(user)) def listskels(self): return (True, glob(self.profile_path + '/skel_*')) def get_mediumlabel(self): l = self.get('medium_label') return (True, l if l else LABEL) def set_mediumlabel(self, l): if len(l) > 16: l = l[:16] self.set('medium_label', l) return self.get_mediumlabel() def set_bootisolabel(self, l): if len(l) > 32: l = l[:32] self.set('bootisolabel', l) return self.get_bootisolabel() def getisosavedir(self): d = self.get('isosavedir') return (True, d if d else os.environ['HOME']) def getisofile(self): f = self.get('isofile') return (True, f if f else ISOFILE) def getbootisofile(self): f = self.get('bootisofile') return (True, f if f else BOOTISO) def get_bootisolabel(self): l = self.get('bootisolabel') return (True, l if l else BOOTISOLABEL)
def add_users(self, rootcmd): userinfo = Userinfo(self.profile_dir) userlist = [] for user in userinfo.allusers(): # Only include if the user does not yet exist if runcmd('bash -c "grep \"^%s\" %s/etc/passwd || echo -"' % (user, self.installation_dir))[1][0] != '-': comment("(WARNING): User '%s' exists already" % user) else: userlist.append(user) # Only continue if there are new users in the list if rootcmd: clist = [('root', rootcmd + ' %s')] else: if userlist == []: return True clist = [] # Save system files and replace them by the overlay versions savedir = self.larchify_dir + '/save_etc' runcmd('rm -rf %s' % savedir) runcmd('mkdir -p %s/default' % savedir) savelist = 'group,gshadow,passwd,shadow,login.defs,skel' runcmd('bash -c "cp -a %s/etc/{%s} %s"' % (self.installation0, savelist, savedir)) runcmd('cp -a %s/etc/default/useradd %s/default' % (self.installation0, savedir)) for f in ('group', 'gshadow', 'passwd', 'shadow', 'login.defs'): if os.path.isfile(self.overlay + '/etc/%s'): runcmd('cp %s/etc/%s %s/etc' % (self.overlay, f, self.installation0)) if os.path.isfile(self.overlay + '/etc/default/useradd'): runcmd('cp %s/etc/default/useradd %s/etc/default' % (self.overlay, self.installation0)) if os.path.isdir(self.overlay + '/etc/skel'): runcmd('cp -r %s/etc/skel %s/etc' % (self.overlay, self.installation0)) # Build the useradd command userdir0 = '/users' userdir = self.larchify_dir + userdir0 userdirs = [] runcmd('mkdir -p %s/home' % self.overlay) for u in userlist: cline = 'useradd -m' pgroup = userinfo.get(u, 'maingroup') if pgroup: cline += ' -g ' + pgroup uid = userinfo.get(u, 'uid') if uid: cline += ' -u ' + uid pw = userinfo.get(u, 'pw') if (pw == ''): # Passwordless login pwcrypt = '' else: # Normal MD5 password pwcrypt = encryptPW(pw) cline += " -p '%s'" % pwcrypt skeldir = userinfo.get(u, 'skel') if skeldir: # Custom home initialization directories in the profile # always start with 'skel_' skel = 'skel_' + skeldir if skel not in userdirs: userdirs.append(skel) cline += ' -k %s/%s' % (CHROOT_DIR_LARCHIFY + userdir0, skel) # Allow for expert tweaking cline += ' ' + userinfo.get(u, 'expert') # The user and the command to be run clist.append((u, cline + ' %s')) xgroups = userinfo.get(u, 'xgroups') if xgroups: xgl = [] for g in xgroups.split(','): clist.append((u, 'usermod -a -G %s %%s' % g)) if userdirs: # Copy custom 'skel' directories to build space runcmd('rm -rf %s' % userdir) runcmd('mkdir -p %s' % userdir) for ud in userdirs: runcmd('cp -r %s/%s %s/%s' % (self.profile_dir, ud, userdir, ud)) nfail = 0 ok = True for u, cmd in clist: if not chroot(self.installation0, cmd % u): nfail += 1 # Errors adding users to groups are not fatal: if not cmd.startswith('usermod -a -G'): ok = False if os.path.isdir('%s/home/%s' % (self.installation0, u)): runcmd('mv %s/home/%s %s/home' % (self.installation0, u, self.overlay)) if nfail > 0: errout(_("%d user account operation(s) failed") % nfail, 0) # Move changed /etc/{group,gshadow,passwd,shadow} to overlay runcmd('bash -c "mv %s/etc/{group,gshadow,passwd,shadow} %s/etc"' % (self.installation0, self.overlay)) # Restore system files in base installation runcmd('rm -rf %s/etc/skel' % self.installation0) runcmd('bash -c "cp -a %s/* %s/etc"' % (savedir, self.installation0)) return ok
class ProjectManager: def __init__(self): add_exports( (('getitem', self.getitem), ('getbool', self.getbool), ('setitem', self.setitem), ('setbool', self.setbool), ('get_projects', self.list_projects), ('get_profiles', self.list_profiles), ('get_installation_dir', self.get_ipath), ('set_installation_dir', self.set_ipath), ('testlarchify', self.testlarchify), ('set_project', self.set_projectname), ('get_project', self.get_projectname), ('delete_project', self.delete_project), ('delete_profile', self.delete_profile), ('list_free_projects', self.list_free_projects), ('list_free_profiles', self.list_free_profiles), ('get_new_profile', self.get_new_profile), ('rename_profile', self.rename_profile), ('can_rename_profile', self.can_rename_profile), ('save_profile', self.save_profile), ('get_profile', self.get_profile), ('set_profile', self.set_profile), ('get_example_profiles', self.get_example_profiles), ('set_profile_browse_dir', self.set_profile_browse_dir), ('get_mediumlabel', self.get_mediumlabel), ('set_mediumlabel', self.set_mediumlabel), ('getisosavedir', self.getisosavedir), ('getisofile', self.getisofile), ('getbootisofile', self.getbootisofile), ('get_bootisolabel', self.get_bootisolabel), ('set_bootisolabel', self.set_bootisolabel), ('newUserinfo', self.newUserinfo), ('allusers', self.allusers), ('getuserinfo', self.getuserinfo), ('newuser', self.newuser), ('userset', self.userset), ('deluser', self.deluser), ('listskels', self.listskels), ('saveusers', self.saveusers))) def init(self): self.projects_base = os.path.join(os.environ['HOME'], CONFIG_DIR) self.profiles_dir = os.path.join(self.projects_base, 'myprofiles') # Ensure the presence of the larch default project folder dpf = '%s/p_%s' % (self.projects_base, PROJECT0) if not os.path.isdir(dpf): os.makedirs(dpf) # Ensure the presence of the profiles folder and the 'default' profile if not os.path.isdir(self.profiles_dir): os.mkdir(self.profiles_dir) self.default_profile_dir = os.path.join(self.profiles_dir, PROFILE0) if not os.path.isdir(self.default_profile_dir): call([ 'cp', '-a', base_dir + '/profiles/' + PROFILE0, self.profiles_dir ]) # The application configs self.aconfig_file = os.path.join(self.projects_base, APP_CONF) self.aconfig = self.getconfig(self.aconfig_file) # The project-specific configs self.set_projectname(self.appget('project')) def get_projectname(self): return (True, self.project_name) def set_projectname(self, name): self.project_dir = os.path.join(self.projects_base, 'p_' + name) plist = self.list_projects()[1] if name not in plist: os.mkdir(self.project_dir) self.pconfig_file = os.path.join(self.project_dir, PROJECT_CONF) self.pconfig = self.getconfig(self.pconfig_file) self.profile_name = self.get('profile') self.profile_path = os.path.join(self.profiles_dir, self.profile_name) self.appset('project', name) self.project_name = name return (True, None) def delete_project(self, name): # This should probably be run as root, in case the build directory # is inside it ... cross that bridge when we come to it! r = call([ 'rm', '-r', '--interactive=never', os.path.join(self.projects_base, 'p_' + name) ]) return (True, r == 0) def delete_profile(self, name): r = call([ 'rm', '-r', '--interactive=never', os.path.join(self.profiles_dir, name) ]) return (True, r == 0) def get_profile(self): return (True, self.profile_name) def set_profile(self, name): self.set('profile', name) self.profile_name = name self.profile_path = os.path.join(self.profiles_dir, self.profile_name) return (True, None) def rename_profile(self, name): os.rename(self.profile_path, os.path.join(self.profiles_dir, name)) self.set_profile(name) return (True, None) def get_new_profile(self, src, name): if not os.path.isfile(src + '/addedpacks'): return (True, False) dst = os.path.join(self.profiles_dir, name) call(['rm', '-rf', dst]) shutil.copytree(src, dst, symlinks=True) self.set_profile(name) return (True, True) def get_example_profiles(self): pd = base_dir + '/profiles' return (True, (pd, os.listdir(pd))) # location of working profiles # self.projects_base + '/myprofiles', # What about not allowing changes to the default profile? # That would mean also no renaming? # One would have to copy a profile into the project before going # any further ... # Is it right to share profiles between projects? (Probably) #+++++++++++++++++++++++++++++++++++++++++++++++ ### A very simple configuration file handler def getconfig(self, filepath): cfg = {} if os.path.isfile(filepath): fh = open(filepath) for line in fh: ls = line.split('=', 1) if len(ls) > 1: cfg[ls[0].strip()] = ls[1].strip() return cfg def saveconfig(self, filepath, config): fh = open(filepath, 'w') ci = config.items() ci.sort() for kv in ci: fh.write('%s = %s\n' % kv) fh.close() ### #----------------------------------------------- def list_projects(self): projects = [ p[2:] for p in os.listdir(self.projects_base) if p.startswith('p_') ] projects.sort() return (True, projects) def list_free_projects(self): """This returns a list of projects which are free for (e.g.) deletion. """ plist = self.list_projects()[1] plist.remove(PROJECT0) # this one is not 'free' if self.project_name in plist: plist.remove(self.project_name) return (True, plist) def list_profiles(self): profiles = [ d for d in os.listdir(self.profiles_dir) if os.path.isfile(os.path.join(self.profiles_dir, d, 'addedpacks')) ] profiles.sort() return (True, profiles) def list_free_profiles(self): """This returns a list of profiles which are not in use by any project. """ plist = self.list_profiles()[1] plist.remove(PROFILE0) # this one is not 'free' for project in self.list_projects()[1]: cfg = self.getconfig( os.path.join(self.projects_base, 'p_' + project, PROJECT_CONF)) p = cfg.get('profile') if p in plist: plist.remove(p) return (True, plist) def can_rename_profile(self): if self.profile_name == PROFILE0: return (True, False) for project in self.list_projects()[1]: if project != self.project_name: cfg = self.getconfig( os.path.join(self.projects_base, 'p_' + project, PROJECT_CONF)) if self.profile_name == cfg.get('profile'): return (True, False) return (True, True) def save_profile(self, path, force): if path[0] != '/': # cloning, only the profile name is passed path = os.path.join(self.profiles_dir, path) else: # copying, the destination will have the same name if os.path.basename(path) != self.profile_name: path = os.path.join(path, self.profile_name) if os.path.exists(path): if force: call(['rm', '-rf', path]) elif os.path.isfile(os.path.join(path, 'addedpacks')): # This is an existing profile return (True, False) else: # This location is otherwise in use return (True, None) shutil.copytree(self.profile_path, path, symlinks=True) return (True, True) def set_profile_browse_dir(self, path): if os.path.isfile(os.path.join(path, 'addedpacks')): # Don't set the profile browse path to a profile directory, # but rather tp the containing directory path = os.path.dirname(path) self.set('profile_browse_dir', path) def appget(self, item): """Read an entry in the application configuration. """ v = self.aconfig.get(item) if v: return v elif APP_DEFAULTS.has_key(item): return APP_DEFAULTS[item] debug("Unknown application configuration option: %s" % item) assert False def appset(self, item, value): """Set an entry in the application configuration. """ self.aconfig[item] = value.strip() self.saveconfig(self.aconfig_file, self.aconfig) def getitem(self, item): return (True, self.get(item)) def getbool(self, item): return (True, self.get(item) == 'Yes') def setitem(self, item, value): self.set(item, value) return (True, None) def setbool(self, item, on): self.set(item, 'Yes' if on else 'No') return (True, None) def get(self, item): """Read an entry in the project configuration. """ v = self.pconfig.get(item) if v: return v elif DEFAULTS.has_key(item): return DEFAULTS[item] debug("Unknown configuration option: %s" % item) assert False def set(self, item, value): """Set an entry in the project configuration. """ self.pconfig[item] = value.strip() self.saveconfig(self.pconfig_file, self.pconfig) # return True def get_ipath(self): ip = self.get('installation_dir') if not ip: ip = self.set_ipath('')[1] return (True, ip) def set_ipath(self, path): path = path.strip() if path: path = '/' + path.strip('/') else: path = os.environ['HOME'] + '/larch_build' self.set('installation_dir', path) return (True, path) def testlarchify(self): ipath = self.get_ipath()[1] path = ipath + CHROOT_DIR_MEDIUM bl = [] nosave = False ok = (os.path.isfile(path + '/larch/system.sqf') and os.path.isfile(ipath + SYSLINUXDIR + '/isolinux.bin')) return (True, (path, ok)) def newUserinfo(self): self.userInfo = Userinfo(self.profile_path) return (True, None) def allusers(self): return (True, self.userInfo.allusers()) def getuserinfo(self, user, fields): return (True, self.userInfo.userinfo(user, fields)) def newuser(self, user): return (True, self.userInfo.newuser(user)) def saveusers(self): return (True, self.userInfo.saveusers()) def userset(self, uname, field, text): self.userInfo.userset(uname, field, text) return (True, None) def deluser(self, user): return (True, self.userInfo.deluser(user)) def listskels(self): return (True, glob(self.profile_path + '/skel_*')) def get_mediumlabel(self): l = self.get('medium_label') return (True, l if l else LABEL) def set_mediumlabel(self, l): if len(l) > 16: l = l[:16] self.set('medium_label', l) return self.get_mediumlabel() def set_bootisolabel(self, l): if len(l) > 32: l = l[:32] self.set('bootisolabel', l) return self.get_bootisolabel() def getisosavedir(self): d = self.get('isosavedir') return (True, d if d else os.environ['HOME']) def getisofile(self): f = self.get('isofile') return (True, f if f else ISOFILE) def getbootisofile(self): f = self.get('bootisofile') return (True, f if f else BOOTISO) def get_bootisolabel(self): l = self.get('bootisolabel') return (True, l if l else BOOTISOLABEL)