1212from ast import literal_eval
1313from configparser import ConfigParser ,NoOptionError ,NoSectionError
1414from filecmp import cmp
15- from os .path import expanduser
1615from re import sub
1716from shutil import copy
1817from tempfile import TemporaryDirectory
19- from urllib .request import urlopen
2018from urllib .error import URLError ,HTTPError
19+ from urllib .request import urlopen
2120
2221# Directories
2322# All snapshots share one /var
3534# /var/lib/ash(/fstree) : ash files, stores fstree, symlink to /.snapshots/ash
3635# Failed prompts start with "F: "
3736
38- distro = subprocess .check_output (['/usr/bin/detect_os.sh' ,'id' ]).decode ('utf-8' ).replace ('"' ,"" ).strip ()
39- distro_name = subprocess .check_output (['/usr/bin/detect_os.sh' ,'name' ]).decode ('utf-8' ).strip ()
40- GRUB = subprocess .check_output ("ls /boot | grep grub" ,encoding = 'utf-8' ,shell = True ).strip ()
4137DEBUG = " >/dev/null 2>&1" # options: "", " >/dev/null 2>&1"
4238URL = "https://raw.githubusercontent.com/ashos/ashos/main/src"
43- USERNAME = os .getenv ("SUDO_USER" )or os .getenv ("USER" )
44- HOME = os .path .expanduser ('~' + USERNAME )# type: ignore
4539
4640# ------------------------------ CORE FUNCTIONS ------------------------------ #
4741
@@ -88,7 +82,6 @@ def ash_chroot_mounts(i, CHR=""):
8882
8983# Update ash itself
9084def ash_update (dbg ):
91- dist = distro .split ("_" )[0 ]# Remove '_ashos"
9285mode = stat .S_IXUSR | stat .S_IXGRP | stat .S_IXOTH
9386with TemporaryDirectory (dir = "/.snapshots/tmp" ,prefix = "ashpk." )as tmpdir :
9487try :
@@ -425,9 +418,53 @@ def get_current_snapshot():
425418with open ("/usr/share/ash/snap" ,"r" )as csnap :
426419return int (csnap .read ().rstrip ())
427420
428- # This function returns either empty string or underscore plus name of distro if it was appended to sub-volume names to distinguish
421+ def get_distro_id (path_prepend = "" ):
422+ distro_id = None
423+ while distro_id == None :
424+ if os .path .exists (f"{ path_prepend } /etc/lsb-release" ):
425+ with open (f"{ path_prepend } /etc/lsb-release" ,"r" )as temp :
426+ for line in temp .readlines ():
427+ if line .startswith ("DISTRIB_ID=" ):
428+ distro_id = line .split ('=' )[1 ].lower ().strip ()
429+ break
430+ if os .path .exists (f"{ path_prepend } /etc/os-release" ):
431+ with open (f"{ path_prepend } /etc/os-release" ,"r" )as temp :
432+ for line in temp .readlines ():
433+ if line .startswith ("ID=" ):
434+ distro_id = line .split ('=' )[1 ].lower ().strip ()
435+ break
436+ # otherwise loop through all files in /etc and check for "-release"
437+ for etcf in os .listdir ("/etc" ):# depth=1, hopefully just 1 file matches
438+ if etcf .endswith ("-release" )and etcf not in ("os-release" ,"lsb-release" ):
439+ distro_id = etcf .split ('-' )[0 ]
440+ break
441+ return distro_id
442+
443+ def get_distro_name (path_prepend = "" ):
444+ distro_name = None
445+ while distro_name == None :
446+ if os .path .exists (f"{ path_prepend } /etc/lsb-release" ):
447+ with open (f"{ path_prepend } /etc/lsb-release" ,"r" )as temp :
448+ for line in temp .readlines ():
449+ if line .startswith ("DISTRIB_DESCRIPTION=" ):
450+ distro_name = line .split ('=' )[1 ].strip ()
451+ break
452+ if os .path .exists (f"{ path_prepend } /etc/os-release" ):
453+ with open (f"{ path_prepend } /etc/os-release" ,"r" )as temp :
454+ for line in temp .readlines ():
455+ if line .startswith ("NAME=" ):
456+ distro_name = line .split ('=' )[1 ].strip ()
457+ break
458+ # Last resort loop through all files in /etc and check for "-release"
459+ for etcf in os .listdir ("/etc" ):
460+ if etcf .endswith ("-release" )and etcf not in ("os-release" ,"lsb-release" ):
461+ distro_name = etcf .split ('-' )[0 ].capitalize ()
462+ break
463+ return distro_name
464+
465+ # This function returns either empty string or underscore plus name of distribution if it was appended to sub-volume names to distinguish
429466def get_distro_suffix ():
430- if "ashos" in distro :
467+ if distro and "ashos" in distro :
431468return f'_{ distro .replace ("_ashos" ,"" )} '
432469else :
433470sys .exit (1 )### REVIEW before: return ""
@@ -567,7 +604,6 @@ def install_live(pkg, snap=get_current_snapshot()):
567604
568605# Install a profile from a text file
569606def install_profile (prof ,snap ,force = False ,secondary = False ,section_only = None ):
570- dist = distro .split ("_" )[0 ]# Remove '_ashos"
571607if not os .path .exists (f"/.snapshots/rootfs/snapshot-{ snap } " ):
572608print (f"F: Cannot install as snapshot{ snap } doesn't exist." )
573609elif os .path .exists (f"/.snapshots/rootfs/snapshot-chr{ snap } " ):# Make sure snapshot is not in use by another ash process
@@ -610,7 +646,6 @@ def install_profile(prof, snap, force=False, secondary=False, section_only=None)
610646
611647# Install profile in live snapshot
612648def install_profile_live (prof ,snap ,force ):
613- dist = distro .split ("_" )[0 ]# Remove '_ashos"
614649tmp = get_tmp ()
615650ash_chroot_mounts (tmp )
616651print (f"Updating the system before installing profile{ prof } ." )
@@ -651,7 +686,7 @@ def is_pkg_installed(pkg, snap):
651686else :
652687return None
653688
654- # List sub-volumes for the booteddistro only
689+ # List sub-volumes for the booteddistribution only
655690def list_subvolumes ():
656691os .system (f"btrfs sub list / | grep -i{ get_distro_suffix ()} | sort -f -k 9" )
657692
@@ -920,7 +955,7 @@ def snapshot_config_edit(snap, skip_prep=False, skip_post=False):
920955
921956# Get per-snapshot configuration options from /etc/ash.conf
922957def snapshot_config_get (snap ):
923- options = {"aur" :"False" ,"mutable_dirs" :"" ,"mutable_dirs_shared" :"" }# defaults here ### REVIEW This is notdistro- generic
958+ options = {"aur" :"False" ,"mutable_dirs" :"" ,"mutable_dirs_shared" :"" }# defaults here ### REVIEW This is not generic
924959if not os .path .exists (f"/.snapshots/etc/etc-{ snap } /ash.conf" ):
925960return options
926961with open (f"/.snapshots/etc/etc-{ snap } /ash.conf" ,"r" )as optfile :
@@ -943,7 +978,7 @@ def snapshot_unlock(snap):
943978def switch_distro ():
944979while True :
945980map_tmp = subprocess .check_output ("cat /boot/efi/EFI/map.txt | awk 'BEGIN { FS = " '"' " === " '"' " } ; { print $1 }'" ,shell = True ).decode ('utf-8' ).strip ()
946- print ("Type the name of adistro to switch to: (type 'list' to list them, 'q' to quit)" )
981+ print ("Type the name of adistribution to switch to: (type 'list' to list them, 'q' to quit)" )
947982next_distro = input ("> " )
948983if next_distro == "q" :
949984break
@@ -966,7 +1001,7 @@ def switch_distro():
9661001#break ### REVIEW
9671002break
9681003else :
969- print ("Invaliddistro !" )
1004+ print ("Invaliddistribution !" )
9701005continue
9711006
9721007# Switch between /tmp deployments
@@ -1042,7 +1077,7 @@ def tmp_delete(secondary=False):
10421077# Print out tree with descriptions
10431078def tree_print (tree ):
10441079csnap = get_current_snapshot ()
1045- for pre ,fill ,node in RenderTree (tree ,style = AsciiStyle ()):# simpler tree style
1080+ for pre ,fill ,node in RenderTree (tree ,style = AsciiStyle ()):# simpler tree style # type: ignore
10461081if os .path .isfile (f"/.snapshots/ash/snapshots/{ node .name } -desc" ):
10471082with open (f"/.snapshots/ash/snapshots/{ node .name } -desc" ,"r" )as descfile :
10481083desc = descfile .readline ()
@@ -1120,7 +1155,7 @@ def tree_sync(tree, tname, force_offline, live):
11201155post_transactions (snap_to )### IMPORTANT REVIEW 2023 - Moved here from the line immediately after first tree_sync_helper
11211156print (f"Tree{ tname } synced." )
11221157
1123- # Sync tree helper function ### REVIEW might need to put it indistro -specific ashpk.py
1158+ # Sync tree helper function ### REVIEW might need to put it indistribution -specific ashpk.py
11241159def tree_sync_helper (CHR ,s_f ,s_t ):
11251160os .system ("mkdir -p /.snapshots/tmp-db/local/" )### REVIEW Still resembling Arch pacman folder structure!
11261161os .system ("rm -rf /.snapshots/tmp-db/local/*" )### REVIEW
@@ -1216,7 +1251,7 @@ def upgrade(snap, baseup=False):
12161251elif snap == 0 and not baseup :
12171252print ("F: Changing base snapshot is not allowed." )
12181253else :
1219- # prepare(snap) ### REVIEW Moved to adistro -specific function as it needs to go after setup_aur_if_enabled()
1254+ # prepare(snap) ### REVIEW Moved to adistribution -specific function as it needs to go after setup_aur_if_enabled()
12201255# Default upgrade behaviour is now "safe" update, meaning failed updates get fully discarded
12211256excode = upgrade_helper (snap )
12221257if excode :
@@ -1263,10 +1298,28 @@ def yes_no(msg):
12631298continue
12641299return e
12651300
1301+ # Define variable
1302+ distro = get_distro_id ()
1303+ distro_name = get_distro_name ()
1304+ if distro :
1305+ dist = distro .split ("_" )[0 ]# Remove '_ashos"
1306+ else :
1307+ sys .exit ("F: Operating system could not be detected!" )
1308+ GRUB = subprocess .check_output ("ls /boot | grep grub" ,encoding = 'utf-8' ,shell = True ).strip ()
1309+ USERNAME = os .getenv ("SUDO_USER" )or os .getenv ("USER" )
1310+ HOME = os .path .expanduser ('~' + USERNAME )# type: ignore
1311+
12661312# Main function
12671313def main ():
1268- if os .geteuid ()!= 0 :# TODO 2023 exception: Make 'ash tree' run without root permissions
1314+ # TODO 2023 exception: Make 'ash tree' run without root permissions
1315+ if sys .platform .startswith ("linux" )and os .geteuid ()!= 0 :
12691316exit ("sudo/doas is required to run ash!" )
1317+ elif sys .platform .startswith ("windows" ):
1318+ print ("TODO" )
1319+ elif sys .platform .startswith ("darwin" ):
1320+ print ("TODO" )
1321+ elif sys .platform .startswith ("cosmo" ):
1322+ print ("TODO" )
12701323else :
12711324importer = DictImporter ()# Dict importer
12721325# isChroot = chroot_check() ### TODO
@@ -1405,7 +1458,7 @@ def main():
14051458editconf_par .add_argument ("--snap" ,"--snapshot" ,"-s" ,type = int ,required = False ,default = get_current_snapshot (),help = "snapshot number" )
14061459editconf_par .set_defaults (func = snapshot_config_edit )
14071460# Snapshot diff
1408- diff_par = subparsers .add_parser ("diff" ,aliases = [' dif' ],allow_abbrev = True ,help = 'Show package diff between snapshots' )
1461+ diff_par = subparsers .add_parser ("diff" ,aliases = [" dif" ],allow_abbrev = True ,help = 'Show package diff between snapshots' )
14091462diff_par .add_argument ("snap1" ,type = int ,help = "source snapshot" )
14101463diff_par .add_argument ("snap2" ,type = int ,help = "target snapshot" )
14111464diff_par .set_defaults (func = snapshot_diff )
@@ -1414,7 +1467,7 @@ def main():
14141467unl_par .add_argument ("--snap" ,"--snapshot" ,"-s" ,type = int ,required = False ,default = get_current_snapshot (),help = "snapshot number" )
14151468unl_par .set_defaults (func = snapshot_unlock )
14161469# Switch distros
1417- switch_par = subparsers .add_parser ("dist" ,aliases = ["distro" , "distros" ],allow_abbrev = True ,help = "Switch to anotherdistro " )
1470+ switch_par = subparsers .add_parser ("dist" ,aliases = ["distro" ],allow_abbrev = True ,help = "Switch to anotherdistribution " )
14181471switch_par .set_defaults (func = switch_distro )
14191472# Switch to Windows (semi plausible deniability)
14201473hide_par = subparsers .add_parser ("hide" ,allow_abbrev = False ,help = "Hide AshOS and switch to Windows" )