#!/usr/bin/python3 import animation import csv import os import shutil import sqlite3 import subprocess import sys import urllib3 import io from dateutil import parser redcore_portage_config_path = '/opt/redcore-build' remotePkgsDB = '/var/lib/sisyphus/csv/remotePackagesPre.csv' remoteDscsDB = '/var/lib/sisyphus/csv/remoteDescriptionsPre.csv' localPkgsDB = '/var/lib/sisyphus/csv/localPackagesPre.csv' sisyphusDB = '/var/lib/sisyphus/db/sisyphus.db' mirrorCfg = '/etc/sisyphus/mirrors.conf' # only run as root (CLI + GUI frontend) def checkRoot(): if not os.getuid() == 0: sys.exit("\nYou need root permissions to do this, exiting!\n") # only run in binary mode (binmode) or hybrid mode (mixedmode) (CLI + GUI frontend) def checkSystemMode(): portage_binmode_make_conf = '/opt/redcore-build/conf/intel/portage/make.conf.amd64-binmode' portage_mixedmode_make_conf = '/opt/redcore-build/conf/intel/portage/make.conf.amd64-mixedmode' portage_make_conf_symlink = '/etc/portage/make.conf' if not os.path.islink(portage_make_conf_symlink): print("\nmake.conf is not a symlink, refusing to run!\n") sys.exit(1) else: if os.path.realpath(portage_make_conf_symlink) == portage_binmode_make_conf: pass elif os.path.realpath(portage_make_conf_symlink) == portage_mixedmode_make_conf: pass else: print("\nThe system is not set to binmode or mixedmode, refusing to run!\n") sys.exit(1) # get current mirror information, so we know where we download from (CLI + GUI frontend) def getRemotePkgsURL(): remotePkgsURL = [] portageExec = subprocess.Popen(['emerge', '--info', '--verbose'], stdout=subprocess.PIPE) for portageOutput in io.TextIOWrapper(portageExec.stdout, encoding="utf-8"): if "PORTAGE_BINHOST" in portageOutput.rstrip(): remotePkgsURL = str(portageOutput.rstrip().split("=")[1].strip('\"').replace('packages', 'csv') + 'remotePackagesPre.csv') return remotePkgsURL def getRemoteDscsURL(): remoteDscsURL = [] portageExec = subprocess.Popen(['emerge', '--info', '--verbose'], stdout=subprocess.PIPE) for portageOutput in io.TextIOWrapper(portageExec.stdout, encoding="utf-8"): if "PORTAGE_BINHOST" in portageOutput.rstrip(): remoteDscsURL = str(portageOutput.rstrip().split("=")[1].strip('\"').replace('packages', 'csv') + 'remoteDescriptionsPre.csv') return remoteDscsURL # download remote CSV's to be imported into the database (CLI + GUI frontend) def fetchRemoteDatabase(): remotePkgsURL = getRemotePkgsURL() remoteDscsURL = getRemoteDscsURL() http = urllib3.PoolManager() with http.request('GET', remotePkgsURL, preload_content=False) as tmp_buffer, open(remotePkgsDB, 'wb') as output_file: shutil.copyfileobj(tmp_buffer, output_file) with http.request('GET', remoteDscsURL, preload_content=False) as tmp_buffer, open(remoteDscsDB, 'wb') as output_file: shutil.copyfileobj(tmp_buffer, output_file) # generate local CSV's to be imported into the database (CLI + GUI frontend) def makeLocalDatabase(): subprocess.check_call(['/usr/share/sisyphus/helpers/make_local_csv']) # this is really hard to do in python, so we cheat with a bash helper script # download and import remote CSV's into the database (CLI + GUI frontend) def syncRemoteDatabase(): fetchRemoteDatabase() sisyphusdb = sqlite3.connect(sisyphusDB) sisyphusdb.cursor().execute('''drop table if exists remote_packages''') sisyphusdb.cursor().execute('''create table remote_packages (category TEXT,name TEXT,version TEXT,slot TEXT)''') with open(remotePkgsDB) as rmtCsv: for row in csv.reader(rmtCsv): sisyphusdb.cursor().execute("insert into remote_packages (category, name, version, slot) values (?, ?, ?, ?);", row) sisyphusdb.commit() sisyphusdb.close() sisyphusdb = sqlite3.connect(sisyphusDB) sisyphusdb.cursor().execute('''drop table if exists remote_descriptions''') sisyphusdb.cursor().execute('''create table remote_descriptions (category TEXT,name TEXT,description TEXT)''') with open(remoteDscsDB) as rmtCsv: for row in csv.reader(rmtCsv): sisyphusdb.cursor().execute("insert into remote_descriptions (category, name, description) values (?, ?, ?);", row) sisyphusdb.commit() sisyphusdb.close() # generate and import local CSV's into the database (CLI + GUI frontend) def syncLocalDatabase(): makeLocalDatabase() sisyphusdb = sqlite3.connect(sisyphusDB) sisyphusdb.cursor().execute('''drop table if exists local_packages''') sisyphusdb.cursor().execute('''create table local_packages (category TEXT,name TEXT,version TEXT,slot TEXT)''') with open(localPkgsDB) as lclCsv: for row in csv.reader(lclCsv): sisyphusdb.cursor().execute("insert into local_packages (category, name, version, slot) values (?, ?, ?, ?);", row) sisyphusdb.commit() sisyphusdb.close() # sync portage tree (CLI + GUI frontend) def syncPortageTree(): subprocess.call(['emerge', '--sync', '--quiet']) # sync portage configuration files (CLI + GUI frontend) def syncPortageCfg(): os.chdir(redcore_portage_config_path) subprocess.call(['git', 'pull', '--quiet']) # check remote timestamps...if newer than local timestamps, sync everything (CLI + GUI frontend) @animation.wait('syncing remote database') def syncAll(): checkRoot() remotePkgsURL = getRemotePkgsURL() remoteDscsURL = getRemoteDscsURL() http = urllib3.PoolManager() reqRemotePkgsTS = http.request('HEAD',remotePkgsURL) remotePkgsTS = int(parser.parse(reqRemotePkgsTS.headers['last-modified']).strftime("%s")) localPkgsTS = int(os.path.getctime(remotePkgsDB)) reqRemoteDscsTS = http.request('HEAD',remoteDscsURL) remoteDscsTS = int(parser.parse(reqRemoteDscsTS.headers['last-modified']).strftime("%s")) localDscsTS = int(os.path.getctime(remoteDscsDB)) if remotePkgsTs < localPkgsTs: pass elif remoteDscsTs < localDscsTs: pass else: syncPortageTree() syncPortageCfg() syncRemoteDatabase() # regenerate local CSV's and import them into the database (CLI frontend) # if something is installed with portage directly using emerge, sisyphus won't be aware of it # this will parse local portage database and import the changes into sisyphus database @animation.wait('syncing local database') def startSyncSPM(): syncLocalDatabase() # sync portage tree and portage configuration files (CLI frontend) @animation.wait('syncing portage') def startSync(): syncPortageTree() syncPortageCfg() # regenerate sisyphus database (CLI frontend) # if for some reason sisyphus database gets corrupted or deleted, we can still regenerate it from portage database # this will fetch remote information from mirrors, parse local portage database and regenerate sisyphus database @animation.wait('resurrecting database') def rescueDB(): if os.path.exists(remotePkgsDB): os.remove(remotePkgsDB) if os.path.exists(remoteDscsDB): os.remove(remoteDscsDB) if os.path.exists(localPkgsDB): os.remove(localPkgsDB) if os.path.exists(sisyphusDB): os.remove(sisyphusDB) syncRemoteDatabase() syncLocalDatabase() # call portage to install the package(s) (CLI frontend) def startInstall(pkgList): syncAll() portageExec = subprocess.Popen(['emerge', '-aq'] + pkgList) portageExec.communicate() syncLocalDatabase() # call portage to uninstall the package(s) (CLI frontend) def startUninstall(pkgList): portageExec = subprocess.Popen(['emerge', '--depclean', '-aq'] + pkgList) portageExec.communicate() syncLocalDatabase() # call portage to force-uninstall the package(s) (CLI frontend) def startUninstallForce(pkgList): portageExec = subprocess.Popen(['emerge', '--unmerge', '-aq'] + pkgList) portageExec.communicate() syncLocalDatabase() # call portage to remove orphan package(s) (CLI frontend) def removeOrphans(): portageExec = subprocess.Popen(['emerge', '--depclean', '-aq']) portageExec.communicate() syncLocalDatabase() # call portage to perform a system upgrade (CLI frontend) def startUpgrade(): syncAll() portageExec = subprocess.Popen(['emerge', '-uDaNq', '--backtrack=100', '--with-bdeps=y', '@world']) portageExec.communicate() syncLocalDatabase() # call portage to search for package(s) (CLI frontend) def startSearch(pkgList): subprocess.check_call(['emerge', '--search'] + pkgList) # check remote timestamps...if newer than local timestamps, sync everything (CLI + GUI frontend) def startUpdate(): syncAll() # display information about installed core packages and portage configuration (CLI frontend) def sysInfo(): subprocess.check_call(['emerge', '--info']) # kill background portage process if sisyphus dies (CLI + GUI frontend) def portageKill(portageCmd): portageCmd.terminate() # get a list of mirrors (GUI frontend) def getMirrors(): mirrorList = [] with open(mirrorCfg) as mirrorFile: for line in mirrorFile.readlines(): if 'PORTAGE_BINHOST=' in line: url = line.split("=")[1].replace('"', '').rstrip() mirror = {'isActive':True,'Url':url} if line.startswith('#'): mirror['isActive'] = False mirrorList.append(mirror) mirrorFile.close() return mirrorList # set the active mirror (GUI frontend) def setActiveMirror(mirrorList): with open(mirrorCfg, 'w+') as mirrorFile: mirrorFile.write("#######################################################\n") mirrorFile.write("# Support for multiple mirrors is somewhat incomplete #\n") mirrorFile.write("# Uncomment only one mirror from the list bellow #\n") mirrorFile.write("#######################################################\n") mirrorFile.write("\n") for line in mirrorList : mirror = 'PORTAGE_BINHOST=' + '"' + line['Url'] + '"' if not line['isActive']: mirror = '# ' + mirror mirrorFile.write(mirror + "\n") mirrorFile.write("\n") # get a list of mirrors (CLI frontend) def listRepo(): mirrorList = getMirrors() for i, line in enumerate(mirrorList): if line['isActive']: print(i+1,'*',line['Url']) else: print(i+1,' ',line['Url']) # set the active mirror (CLI frontend) def setRepo(mirror): mirror = int(mirror[0]) mirrorList = getMirrors() if mirror not in range(1,len(mirrorList)+1): print('mirror index is wrong, please check with "sisyphus mirror list"') else: for i in range(0,len(mirrorList)): indx = i+1 if indx == mirror : mirrorList[i]['isActive'] = True else: mirrorList[i]['isActive'] = False setActiveMirror(mirrorList) # display help menu (CLI frontend) def showHelp(): print("\nUsage : sisyphus command [package(s)] || [file(s)]\n") print("Sisyphus is a simple python wrapper around portage, gentoolkit, and portage-utils that provides") print("an apt-get/yum-alike interface to these commands, to assist newcomer people transitioning from") print("Debian/RedHat-based systems to Gentoo.\n") print("Commands :\n") print("install - Install new packages") print("uninstall - Uninstall packages *safely* (INFO : If reverse deps are found, package(s) will NOT be uninstalled)") print("force-uninstall - Uninstall packages *unsafely* (WARNING : This option will ignore reverse deps, which may break your system)") print("remove-orphans - Uninstall packages that are no longer needed") print("update - Update the Portage tree, Overlay(s), Portage config files && Sisyphus database remote_packages table") print("upgrade - Upgrade the system") print("search - Search for packages") print("spmsync - Sync Sisyphus database with Portage database (if you install something with Portage, not Sisyphus)") print("rescue - Rescue Sisyphus database if lost or corrupted") print("mirror list - List available mirrors (the active one is marked with *)") print("mirror set INDEX - Switch the repository to the selected mirror") print("sysinfo - Display information about installed core packages and portage configuration")