diff --git a/README.md b/README.md index e025fd5..8cf6a1d 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,16 @@ This extension requires Python3.8 and above to be installed on your system. ## AccessOwnership This extension modifies access permissions & ownership on downloaded files.
Supports multiple categories and to configure access permissions & ownership separately for each category.
-Built in testfunction to test entered values. +Built in function to test entered values.
+Built in function to get UID and GID for NZBGet instance. + ## OS Support (so far) -Linux / Debian12 +Linux / Debian13
+MacOS / 14
+Docker ## Installation -For manual installation, unpack 'Extension-AccessOwnership-1.4.zip' into NZBget's scripts folder.
+For manual installation, unpack 'Extension-AccessOwnership-1.6.zip' into NZBget's scripts folder.
Follow instructions here:
https://nzbget.com/documentation/extension-scripts diff --git a/main.py b/main.py index cc18d2c..68ec7b9 100644 --- a/main.py +++ b/main.py @@ -9,112 +9,155 @@ import grp # Exit codes used by NZBGet -POSTPROCESS_SUCCESS=93 -POSTPROCESS_ERROR=94 +SCRIPT_SUCCESS=93 +SCRIPT_ERROR=94 # Debug messages from Extension -#check_OS= -check_SETTINGS="Check instructions in the extension settings" +CHECK_SETTINGS="Check instructions in the extension settings" # Check if all required script config options are present in config file -required_options = ('NZBPO_DESTDIR', 'NZBPO_ACCESS', 'NZBPO_OWNER', 'NZBPO_GROUP') +required_options = ('NZBPO_DESTDIR', 'NZBPO_ACCESS', 'NZBPO_OWNER', 'NZBPO_GROUP', 'NZBPO_OVERRIDEID') for optname in required_options: - if (not optname in os.environ): - print('[ERROR] Option %s is missing in configuration file. Please check script settings' % optname[6:]) - sys.exit(POSTPROCESS_ERROR) + if (optname not in os.environ): + print(f"[ERROR] Option {optname[6:]} is missing in configuration file. Please check script settings") + sys.exit(SCRIPT_ERROR) # Check if the script is executed from settings page with a custom command command = os.environ.get("NZBCP_COMMAND") +detect_mode = command == "Detect" test_mode = command == "Test" -if command != None and not test_mode: +if command is not None and not any((detect_mode,test_mode)): print('[ERROR] Invalid command ' + command) - sys.exit(COMMAND_ERROR) + sys.exit(SCRIPT_ERROR) + +# Override and debugging +overrideid = os.environ['NZBPO_OVERRIDEID']; + +# Detecting users Owner[UID] and Group[GID], executed from settings page +if detect_mode: + try: + if pwd.getpwuid(os.getuid())[2] is not None: + print("User UID is detected:",[pwd.getpwuid(os.getuid())[2]],) + else: + print("Cannot detect logged in users UID") + check = SCRIPT_ERROR + + if pwd.getpwuid(os.getuid())[3] is not None: + print("Group GID is detected:",[pwd.getpwuid(os.getuid())[3]],) + else: + print("Cannot detect logged in users GID") + check = SCRIPT_ERROR + + try: + check + except NameError: + sys.exit(SCRIPT_SUCCESS) + else: + sys.exit(check) + except KeyError: + print("The requested key was not found.") # Counters for added Categories -countCategory = 1 +countcategory = 1 for i in range(1, 100): if os.environ.get("NZBOP_Category" + str(i) + ".Name") is not None: - countCategory +=1 -countCategoryExt = 1 + countcategory +=1 +countcategoryext = 1 for i in range(1, 100): if os.environ.get("NZBPO_CategoryExt" + str(i) + ".Name") is not None: - countCategoryExt +=1 - + countcategoryext +=1 +# Testing the validity of settings, executed from settings page if test_mode: if not os.environ.get("NZBPO_DestDir") == os.environ.get("NZBOP_DestDir"): - print("Default Category: Invalid Path:",[os.environ.get("NZBPO_DestDir")],check_SETTINGS) - check = POSTPROCESS_ERROR - if not re.match('^[0-7]{3}+$', os.environ.get("NZBPO_Access")): - print("Default Category: Invalid Access[mask]:",[os.environ.get("NZBPO_Access")],check_SETTINGS) - check = POSTPROCESS_ERROR - - if not re.match('^[0-9]*$', os.environ.get("NZBPO_Owner")): - print("Default Category: Invalid Owner[UID]:",[os.environ.get("NZBPO_Owner")],check_SETTINGS) - check = POSTPROCESS_ERROR + print("Default Category: Invalid Path:",[os.environ.get("NZBPO_DestDir")],CHECK_SETTINGS) + check = SCRIPT_ERROR + if not re.match('^[0-7]{3}$', os.environ.get("NZBPO_Access")): + print("Default Category: Invalid Access[mask]:",[os.environ.get("NZBPO_Access")],CHECK_SETTINGS) + check = SCRIPT_ERROR + + if not re.match('^[0-9]{1,}$', os.environ.get("NZBPO_Owner")): + print("Default Category: Invalid Owner[UID]:",[os.environ.get("NZBPO_Owner")],CHECK_SETTINGS) + check = SCRIPT_ERROR else: try: pwd.getpwuid(int(os.environ.get("NZBPO_Owner"))) except KeyError: - print("Default Category: Invalid Owner[UID]:",[os.environ.get("NZBPO_Owner")],"User not in system.",check_SETTINGS) - check = POSTPROCESS_ERROR + if overrideid == "yes": + print("Default Category: Invalid Owner[UID]:",[os.environ.get("NZBPO_Owner")],"<- ENABLED!!") + else: + print("Default Category: Invalid Owner[UID]:",[os.environ.get("NZBPO_Owner")],"User not in system.",CHECK_SETTINGS) + check = SCRIPT_ERROR - if not re.match('^[0-9]*$', os.environ.get("NZBPO_Group")): - print("Default Category: Invalid Group[GID]:",[os.environ.get("NZBPO_Group")],check_SETTINGS) - check = POSTPROCESS_ERROR + if not re.match('^[0-9]{1,}$', os.environ.get("NZBPO_Group")): + print("Default Category: Invalid Group[GID]:",[os.environ.get("NZBPO_Group")],CHECK_SETTINGS) + check = SCRIPT_ERROR else: try: grp.getgrgid(int(os.environ.get("NZBPO_Group"))) except KeyError: - print("Default Category: Invalid Group[UID]: Group[UID]",[os.environ.get("NZBPO_Group")],"Group not in system.",check_SETTINGS) - check = POSTPROCESS_ERROR + if overrideid == "yes": + print("Default Category: Invalid Group[GID]:",[os.environ.get("NZBPO_Group")],"<- ENABLED!!") + else: + print("Default Category: Invalid Group[GID]:",[os.environ.get("NZBPO_Group")],"Group not in system.",CHECK_SETTINGS) + check = SCRIPT_ERROR - for i in range(1, countCategoryExt): + for i in range(1, countcategoryext): + if os.environ.get("NZBPO_CategoryExt" + str(i) + ".Name") is not None: catextname = os.environ["NZBPO_CategoryExt" + str(i) + ".Name"]; catextdestdir = os.environ["NZBPO_CategoryExt" + str(i) + ".DestDir"]; catextaccess = os.environ["NZBPO_CategoryExt" + str(i) + ".Access"]; catextowner = os.environ["NZBPO_CategoryExt" + str(i) + ".Owner"]; catextgroup = os.environ["NZBPO_CategoryExt" + str(i) + ".Group"]; - if not re.match('^[0-7]{3}+$', catextaccess): - print(catextname,"Category: Invalid Access[mask]:",[catextaccess],check_SETTINGS) - check = POSTPROCESS_ERROR - if not re.match('^[0-9]*$', catextowner): - print(catextname,"Category: Invalid Owner[UID]:",[catextowner],check_SETTINGS) - check = POSTPROCESS_ERROR + if not re.match('^[0-7]{3}$', catextaccess): + print(catextname,"Category: Invalid Access[mask]:",[catextaccess],CHECK_SETTINGS) + check = SCRIPT_ERROR + + if not re.match('^[0-9]{1,}$', catextowner): + print(catextname,"Category: Invalid Owner[UID]:",[catextowner],CHECK_SETTINGS) + check = SCRIPT_ERROR else: try: pwd.getpwuid(int(catextowner)) except KeyError: - print(catextname,"Category: Invalid Owner[UID]:",[catextowner],"User not in system.",check_SETTINGS) - check = POSTPROCESS_ERROR - - if not re.match('^[0-9]*$', catextgroup): - print(catextname,"Category: Invalid Group[UID]:",[catextgroup],check_SETTINGS) - check = POSTPROCESS_ERROR + if overrideid == "yes": + print(catextname,"Category: Invalid Owner[UID]:",[catextowner],"User not in system.","<- ENABLED!!") + else: + print(catextname,"Category: Invalid Owner[UID]:",[catextowner],"User not in system.",CHECK_SETTINGS) + check = SCRIPT_ERROR + + if not re.match('^[0-9]{1,}$', catextgroup): + print(catextname,"Category: Invalid Group[UID]:",[catextgroup],CHECK_SETTINGS) + check = SCRIPT_ERROR else: try: grp.getgrgid(int(catextgroup)) except KeyError: - print(catextname,"Category: Invalid Group[UID]:",[catextgroup],"Group not in system.",check_SETTINGS) - check = POSTPROCESS_ERROR - - countCategoryName = 1 - for i in range(1, countCategory): - if catextname == os.environ.get("NZBOP_Category" + str(i) + ".Name") is not None: + if overrideid == "yes": + print(catextname,"Category: Invalid Group[UID]:",[catextgroup],"Group not in system.","<- ENABLED!!") + else: + print(catextname,"Category: Invalid Group[UID]:",[catextgroup],"Group not in system.",CHECK_SETTINGS) + check = SCRIPT_ERROR + + for i in range(1, countcategory): + if catextname == os.environ.get("NZBOP_Category" + str(i) + ".Name"): if not catextdestdir == os.environ.get("NZBOP_Category" + str(i) + ".DestDir"): - print(catextname,"Category: Invalid Path:",[catextdestdir],check_SETTINGS) - check = POSTPROCESS_ERROR - else: - countCategoryName +=1 - if countCategoryName >= countCategoryExt: - print(catextname,"Category: Invalid CategoryExt Name:",[catextname],"Name not same as in CATEGORIES",check_SETTINGS) - check = POSTPROCESS_ERROR + print(catextname,"Category: Invalid Path:",[catextdestdir],CHECK_SETTINGS) + check = SCRIPT_ERROR + + catnames = [] + for i in range(1, countcategory): + catname = os.environ.get("NZBOP_Category" + str(i) + ".Name") + catnames.append(catname) + if catextname not in catnames: + print(catextname,"Category: Invalid CategoryExt Name:",[catextname],"Name not same as in CATEGORIES.",CHECK_SETTINGS) + check = SCRIPT_ERROR try: check except NameError: - sys.exit(POSTPROCESS_SUCCESS) + sys.exit(SCRIPT_SUCCESS) else: sys.exit(check) @@ -127,7 +170,7 @@ catextaccess = os.environ.get("NZBPO_CATEGORYEXT" + str(i) + "_ACCESS") catextowner = os.environ.get("NZBPO_CATEGORYEXT" + str(i) + "_OWNER") catextgroup = os.environ.get("NZBPO_CATEGORYEXT" + str(i) + "_GROUP") - if catextname == None or catextdestdir == None: + if catextname is None or catextdestdir is None: break categories.append({catextname, catextdestdir, catextaccess, catextowner, catextgroup}) @@ -141,40 +184,45 @@ group = os.environ['NZBPO_GROUP']; -# If download is an added category, set the related destination dir +# If download is an added category, read in the related values and dir if not category == "": - for i in range(1, countCategoryExt): - if category == os.environ.get("NZBPO_CategoryExt" + str(i) + ".Name"): - destdir = os.environ["NZBPO_CategoryExt" + str(i) + ".DestDir"]; - access = os.environ["NZBPO_CategoryExt" + str(i) + ".Access"]; - owner = os.environ["NZBPO_CategoryExt" + str(i) + ".Owner"]; - group = os.environ["NZBPO_CategoryExt" + str(i) + ".Group"]; - + for i in range(1, countcategoryext): + if category == os.environ.get("NZBPO_CategoryExt" + str(i) + ".Name"): + destdir = os.environ["NZBPO_CategoryExt" + str(i) + ".DestDir"]; + access = os.environ["NZBPO_CategoryExt" + str(i) + ".Access"]; + owner = os.environ["NZBPO_CategoryExt" + str(i) + ".Owner"]; + group = os.environ["NZBPO_CategoryExt" + str(i) + ".Group"]; # Check if input values are valid -if not re.match('^[0-7]{3}+$', access): - print(category,"Category: Invalid Access[mask]:",[access],check_SETTINGS) - sys.exit(POSTPROCESS_ERROR) +if not re.match('^[0-7]{3}$', access): + print(category,"Category: Invalid Access[mask]:",[access],CHECK_SETTINGS) + sys.exit(SCRIPT_ERROR) -if not re.match('^[0-9]*$', owner): - print(category,"Category: Invalid Owner[UID]:",[owner],check_SETTINGS) - sys.exit(POSTPROCESS_ERROR) +if not re.match('^[0-9]{1,}$', owner): + print(category,"Category: Invalid Owner[UID]:",[owner],CHECK_SETTINGS) + sys.exit(SCRIPT_ERROR) else: try: pwd.getpwuid(int(owner)) except KeyError: - print(category,"Category: Invalid Owner[UID]:",[owner],"User not in system.",check_SETTINGS) - sys.exit(POSTPROCESS_ERROR) - -if not re.match('^[0-9]*$', group): - print(category,"Category: Invalid Group[UID]:",[group],check_SETTINGS) - sys.exit(POSTPROCESS_ERROR) + if overrideid == "yes": + print(category,"Category: Invalid Owner[UID]:",[owner],"User not in system.","<- ENABLED!!") + else: + print(category,"Category: Invalid Owner[UID]:",[owner],"User not in system.",CHECK_SETTINGS) + sys.exit(SCRIPT_ERROR) + +if not re.match('^[0-9]{1,}$', group): + print(category,"Category: Invalid Group[UID]:",[group],CHECK_SETTINGS) + sys.exit(SCRIPT_ERROR) else: try: grp.getgrgid(int(group)) except KeyError: - print(category,"Category: Invalid Group[UID]:",[group],"Group not in system.",check_SETTINGS) - sys.exit(POSTPROCESS_ERROR) + if overrideid == "yes": + print(category,"Category: Invalid Group[UID]:",[group],"Group not in system.","<- ENABLED!!") + else: + print(category,"Category: Invalid Group[UID]:",[group],"Group not in system.",CHECK_SETTINGS) + sys.exit(SCRIPT_ERROR) # Apply chmod & chown to files in destination dir @@ -193,4 +241,4 @@ for f in files : os.chown(os.path.join(root,f), int(owner), int(group)) -sys.exit(POSTPROCESS_SUCCESS) +sys.exit(SCRIPT_SUCCESS) diff --git a/manifest.json b/manifest.json index 98e6884..68d4ce1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,30 +1,44 @@ { "main": "main.py", "name": "AccessOwnership", - "homepage": "https://github.com/nzbgetcom/Extension-AccessOwnership", + "homepage": "https://github.com/Lennong/Extension-AccessOwnership", "kind": "POST-PROCESSING", "displayName": "AccessOwnership", - "version": "1.4", + "version": "1.6", "nzbgetMinVersion": "24", "author": "Lennong", "license": "GNU", - "about": "Sets access permissions & ownership to downloaded files.", + "about": "Sets access permissions & ownership to downloaded files", "queueEvents": "", "requirements": [ "This extension is compatible with NZBGet v24+.", - "This extension requires Python3.8 and up to be installed on your system." + "This extension requires Python3.8 and up to be installed on your system.", + "This extension is supported on [Linux, MacOS, Docker].", + "", + "START INITIAL CONFIGURATION OF THIS EXTENSION BY SAVE AND RELOAD ONE TIME!" ], "description": [ "This script modifies access permissions & ownership on downloaded files." ], "options": [ + { + "name": "OverrideID", + "displayName": "Override UID and GID", + "value": "no", + "description": [ + "To override UID or GID check and allow non-system present UID and GID to be set on downloaded files", + "", + "NOTE: WARNING! Only use this if you know what you are doing!" + ], + "select": ["no", "yes"] + }, { "name": "DestDir", "displayName": "DestDir", - "value": "", + "value": "${DestDir}", "description": [ "Destination directory for downloads.", - "Set the same 'DestDir' value as set under the 'PATHS' settings.", + "Keep as is or manually set the same 'DestDir' value as set under the 'PATHS' settings.", "", "NOTE: Enable this extension in the 'Extensions' setting under 'EXTENSION SCRIPTS' after this extension is configured and saved." ], @@ -44,22 +58,24 @@ { "name": "Owner", "displayName": "Owner", - "value": 1000, + "value": "", "description": [ - "Sets Owner (uid) to downloaded files. Use positive integers only, starting with '0' and up.", + "Sets Owner [UID] to downloaded files. Use positive integers only, starting with '0' and up.", "", - "Default = '1000'." + "NOTE: Use 'Detect UID and GID' function to detect UID for user that runs this instance of NZBGet.", + "NOTE: If use other UID, only use UID present in system or set UID will not be applied on downloaded files." ], "select": ["UID"] }, { "name": "Group", "displayName": "Group", - "value": 1000, + "value": "", "description": [ - "Set Group (gid) to downloaded files. Use positive integers only, starting with '0' and up.", + "Set Group [GID] to downloaded files. Use positive integers only, starting with '0' and up.", "", - "Default = '1000'." + "NOTE: Use 'Detect UID and GID' function to detect GID for user that runs this instance of NZBGet.", + "NOTE: If use other GID, only use GID present in system or set GID will not be applied on downloaded files." ], "select": ["GID"] }, @@ -102,11 +118,12 @@ "section": "Categories", "name": "Owner", "displayName": "Owner", - "value": 1000, + "value": "", "description": [ - "Sets Owner (uid) to downloaded files in this category. Use positive integers only, starting with '0' and up.", + "Sets Owner [UID] to downloaded files in this category. Use positive integers only, starting with '0' and up.", "", - "Default = '1000'." + "NOTE: Use 'Detect UID and GID' function to detect UID for user that runs this instance of NZBGet.", + "NOTE: If use other UID, only use UID present in system or set UID will not be applied on downloaded files." ], "select": ["UID"] }, @@ -114,25 +131,38 @@ "section": "Categories", "name": "Group", "displayName": "Group", - "value": 1000, + "value": "", "description": [ - "Sets Group (gid) to downloaded files in this category. Use positive integers only, starting with '0' and up.", + "Sets Group [GID] to downloaded files in this category. Use positive integers only, starting with '0' and up.", "", - "Default = '1000'." + "NOTE: Use 'Detect UID and GID' function to detect GID for user that runs this instance of NZBGet.", + "NOTE: If use other GID, only use GID present in system or set GID will not be applied on downloaded files." ], "select": ["GID"] } ], "commands": [ + { + "name": "Detect", + "action": "Detect now", + "displayName": "Detect UID and GID", + "description": [ + "Detects users Owner[UID] and Group[GID]", + "", + "NOTE: Use this tool to detect Detects UID and GID for settings:", + "OWNER: detects UID for user that runs this instance of NZBGet", + "GROUP: detects GID for user that runs this instance of NZBGet" + ] + }, { "name": "Test", - "action": "Check now", - "displayName": "Test", + "action": "Test now", + "displayName": "Test Settings", "description": [ "Click to test the settings:", "", - "CATEGORIES: extension coorelates with main program", - "PATHS: extension coorelates with main program", + "CATEGORIES: extension coorelates with main settings", + "PATHS: extension coorelates with main settings", "ACCESS: extension checks validity of mask input", "OWNER: extension checks validity of UID input and present UID in system", "GROUP: extension checks validity of GID input and present GID in system"