diff --git a/.templates/pihole/build.py b/.templates/pihole/build.py deleted file mode 100755 index e2cc017b4..000000000 --- a/.templates/pihole/build.py +++ /dev/null @@ -1,411 +0,0 @@ -#!/usr/bin/env python3 - -issues = {} # Returned issues dict -buildHooks = {} # Options, and others hooks -haltOnErrors = True - -# Main wrapper function. Required to make local vars work correctly -def main(): - import os - import time - import ruamel.yaml - import signal - import sys - import subprocess - - from blessed import Terminal - from deps.chars import specialChars, commonTopBorder, commonBottomBorder, commonEmptyLine, padText - from deps.consts import servicesDirectory, templatesDirectory, volumesDirectory, servicesFileName, buildSettingsFileName - from deps.common_functions import getExternalPorts, getInternalPorts, checkPortConflicts, enterPortNumberWithWhiptail, generateRandomString - - global dockerComposeServicesYaml # The loaded memory YAML of all checked services - global toRun # Switch for which function to run when executed - global buildHooks # Where to place the options menu result - global currentServiceName # Name of the current service - global issues # Returned issues dict - global haltOnErrors # Turn on to allow erroring - - # runtime vars - portConflicts = [] - serviceVolume = volumesDirectory + currentServiceName - serviceService = servicesDirectory + currentServiceName - serviceTemplate = templatesDirectory + currentServiceName - buildSettings = serviceService + buildSettingsFileName - - yaml = ruamel.yaml.YAML() - yaml.preserve_quotes = True - - try: # If not already set, then set it. - hideHelpText = hideHelpText - except: - hideHelpText = False - - documentationHint = 'https://sensorsiot.github.io/IOTstack/Containers/Pi-hole' - - # This lets the menu know whether to put " >> Options " or not - # This function is REQUIRED. - def checkForOptionsHook(): - try: - buildHooks["options"] = callable(runOptionsMenu) - except: - buildHooks["options"] = False - return buildHooks - return buildHooks - - # This function is REQUIRED. - def checkForPreBuildHook(): - try: - buildHooks["preBuildHook"] = callable(preBuild) - except: - buildHooks["preBuildHook"] = False - return buildHooks - return buildHooks - - # This function is REQUIRED. - def checkForPostBuildHook(): - try: - buildHooks["postBuildHook"] = callable(postBuild) - except: - buildHooks["postBuildHook"] = False - return buildHooks - return buildHooks - - # This function is REQUIRED. - def checkForRunChecksHook(): - try: - buildHooks["runChecksHook"] = callable(runChecks) - except: - buildHooks["runChecksHook"] = False - return buildHooks - return buildHooks - - # This service will not check anything unless this is set - # This function is optional, and will run each time the menu is rendered - def runChecks(): - checkForIssues() - return [] - - # This function is optional, and will run after the docker-compose.yml file is written to disk. - def postBuild(): - return True - - # This function is optional, and will run just before the build docker-compose.yml code. - def preBuild(): - # Multi-service: - with open((r'%s/' % serviceTemplate) + servicesFileName) as objServiceFile: - serviceYamlTemplate = yaml.load(objServiceFile) - - oldBuildCache = {} - try: - with open(r'%s' % buildCache) as objBuildCache: - oldBuildCache = yaml.load(objBuildCache) - except: - pass - - buildCacheServices = {} - if "services" in oldBuildCache: - buildCacheServices = oldBuildCache["services"] - - if not os.path.exists(serviceService): - os.makedirs(serviceService, exist_ok=True) - - if os.path.exists(buildSettings): - # Password randomisation - with open(r'%s' % buildSettings) as objBuildSettingsFile: - piHoleYamlBuildOptions = yaml.load(objBuildSettingsFile) - if ( - piHoleYamlBuildOptions["databasePasswordOption"] == "Randomise password for this build" - or piHoleYamlBuildOptions["databasePasswordOption"] == "Randomise database password every build" - or piHoleYamlBuildOptions["databasePasswordOption"] == "Use default password for this build" - ): - if piHoleYamlBuildOptions["databasePasswordOption"] == "Use default password for this build": - newAdminPassword = "IOtSt4ckP1Hol3" - else: - newAdminPassword = generateRandomString() - - for (index, serviceName) in enumerate(serviceYamlTemplate): - dockerComposeServicesYaml[serviceName] = serviceYamlTemplate[serviceName] - if "environment" in serviceYamlTemplate[serviceName]: - for (envIndex, envName) in enumerate(serviceYamlTemplate[serviceName]["environment"]): - envName = envName.replace("%randomAdminPassword%", newAdminPassword) - dockerComposeServicesYaml[serviceName]["environment"][envIndex] = envName - - # Ensure you update the "Do nothing" and other 2 strings used for password settings in 'passwords.py' - if (piHoleYamlBuildOptions["databasePasswordOption"] == "Randomise password for this build"): - piHoleYamlBuildOptions["databasePasswordOption"] = "Do nothing" - with open(buildSettings, 'w') as outputFile: - yaml.dump(piHoleYamlBuildOptions, outputFile) - else: # Do nothing - don't change password - for (index, serviceName) in enumerate(buildCacheServices): - if serviceName in buildCacheServices: # Load service from cache if exists (to maintain password) - dockerComposeServicesYaml[serviceName] = buildCacheServices[serviceName] - else: - dockerComposeServicesYaml[serviceName] = serviceYamlTemplate[serviceName] - - else: - print("PiHole Warning: Build settings file not found, using default password") - time.sleep(1) - newAdminPassword = "IOtSt4ckP1Hol3" - for (index, serviceName) in enumerate(serviceYamlTemplate): - dockerComposeServicesYaml[serviceName] = serviceYamlTemplate[serviceName] - if "environment" in serviceYamlTemplate[serviceName]: - for (envIndex, envName) in enumerate(serviceYamlTemplate[serviceName]["environment"]): - envName = envName.replace("%randomAdminPassword%", newAdminPassword) - dockerComposeServicesYaml[serviceName]["environment"][envIndex] = envName - piHoleYamlBuildOptions = { - "version": "1", - "application": "IOTstack", - "service": "PiHole", - "comment": "PiHole Build Options" - } - - piHoleYamlBuildOptions["databasePasswordOption"] = "Do nothing" - with open(buildSettings, 'w') as outputFile: - yaml.dump(piHoleYamlBuildOptions, outputFile) - - return True - - - # ##################################### - # Supporting functions below - # ##################################### - - def checkForIssues(): - for (index, serviceName) in enumerate(dockerComposeServicesYaml): - if not currentServiceName == serviceName: # Skip self - currentServicePorts = getExternalPorts(currentServiceName, dockerComposeServicesYaml) - portConflicts = checkPortConflicts(serviceName, currentServicePorts, dockerComposeServicesYaml) - if (len(portConflicts) > 0): - issues["portConflicts"] = portConflicts - - ############################ - # Menu Logic - ############################ - - global currentMenuItemIndex - global selectionInProgress - global menuNavigateDirection - global needsRender - - selectionInProgress = True - currentMenuItemIndex = 0 - menuNavigateDirection = 0 - needsRender = 1 - term = Terminal() - hotzoneLocation = [((term.height // 16) + 6), 0] - - def goBack(): - global selectionInProgress - global needsRender - selectionInProgress = False - needsRender = 1 - return True - - def setPasswordOptions(): - global needsRender - global hasRebuiltAddons - passwordOptionsMenuFilePath = "./.templates/{currentService}/passwords.py".format(currentService=currentServiceName) - with open(passwordOptionsMenuFilePath, "rb") as pythonDynamicImportFile: - code = compile(pythonDynamicImportFile.read(), passwordOptionsMenuFilePath, "exec") - execGlobals = { - "currentServiceName": currentServiceName, - "renderMode": renderMode - } - execLocals = {} - screenActive = False - exec(code, execGlobals, execLocals) - signal.signal(signal.SIGWINCH, onResize) - screenActive = True - needsRender = 1 - - def enterPortNumberExec(): - # global term - global needsRender - global dockerComposeServicesYaml - externalPort = getExternalPorts(currentServiceName, dockerComposeServicesYaml)[0] - internalPort = getInternalPorts(currentServiceName, dockerComposeServicesYaml)[0] - newPortNumber = enterPortNumberWithWhiptail(term, dockerComposeServicesYaml, currentServiceName, hotzoneLocation, externalPort) - - if newPortNumber > 0: - dockerComposeServicesYaml[currentServiceName]["ports"][0] = "{newExtPort}:{oldIntPort}".format( - newExtPort = newPortNumber, - oldIntPort = internalPort - ) - createMenu() - needsRender = 1 - - def onResize(sig, action): - global piHoleBuildOptions - global currentMenuItemIndex - mainRender(1, piHoleBuildOptions, currentMenuItemIndex) - - piHoleBuildOptions = [] - - def createMenu(): - global piHoleBuildOptions - try: - piHoleBuildOptions = [] - portNumber = getExternalPorts(currentServiceName, dockerComposeServicesYaml)[0] - piHoleBuildOptions.append([ - "Change external WUI Port Number from: {port}".format(port=portNumber), - enterPortNumberExec - ]) - except: # Error getting port - pass - - piHoleBuildOptions.append([ - "PiHole Password Options", - setPasswordOptions - ]) - - piHoleBuildOptions.append(["Go back", goBack]) - - def runOptionsMenu(): - createMenu() - menuEntryPoint() - return True - - def renderHotZone(term, menu, selection, hotzoneLocation): - lineLengthAtTextStart = 71 - print(term.move(hotzoneLocation[0], hotzoneLocation[1])) - for (index, menuItem) in enumerate(menu): - toPrint = "" - if index == selection: - toPrint += ('{bv} -> {t.blue_on_green} {title} {t.normal} <-'.format(t=term, title=menuItem[0], bv=specialChars[renderMode]["borderVertical"])) - else: - toPrint += ('{bv} {t.normal} {title} '.format(t=term, title=menuItem[0], bv=specialChars[renderMode]["borderVertical"])) - - for i in range(lineLengthAtTextStart - len(menuItem[0])): - toPrint += " " - - toPrint += "{bv}".format(bv=specialChars[renderMode]["borderVertical"]) - - toPrint = term.center(toPrint) - - print(toPrint) - - def mainRender(needsRender, menu, selection): - term = Terminal() - - if needsRender == 1: - print(term.clear()) - print(term.move_y(term.height // 16)) - print(term.black_on_cornsilk4(term.center('IOTstack PiHole Options'))) - print("") - print(term.center(commonTopBorder(renderMode))) - print(term.center(commonEmptyLine(renderMode))) - print(term.center("{bv} Select Option to configure {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center(commonEmptyLine(renderMode))) - - if needsRender >= 1: - renderHotZone(term, menu, selection, hotzoneLocation) - - if needsRender == 1: - print(term.center(commonEmptyLine(renderMode))) - print(term.center(commonEmptyLine(renderMode))) - if not hideHelpText: - print(term.center(commonEmptyLine(renderMode))) - print(term.center("{bv} Controls: {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center("{bv} [Up] and [Down] to move selection cursor {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center("{bv} [H] Show/hide this text {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center("{bv} [Enter] to run command or save input {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center("{bv} [Escape] to go back to build stack menu {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center(commonEmptyLine(renderMode))) - if len(documentationHint) > 1: - if len(documentationHint) > 56: - documentationAndPadding = padText(documentationHint, 71) - print(term.center("{bv} Documentation: {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center("{bv} {dap} {bv}".format(bv=specialChars[renderMode]["borderVertical"], dap=documentationAndPadding))) - else: - documentationAndPadding = padText(documentationHint, 56) - print(term.center("{bv} Documentation: {dap} {bv}".format(bv=specialChars[renderMode]["borderVertical"], dap=documentationAndPadding))) - print(term.center(commonEmptyLine(renderMode))) - print(term.center(commonEmptyLine(renderMode))) - print(term.center(commonBottomBorder(renderMode))) - - def runSelection(selection): - import types - global piHoleBuildOptions - if len(piHoleBuildOptions[selection]) > 1 and isinstance(piHoleBuildOptions[selection][1], types.FunctionType): - piHoleBuildOptions[selection][1]() - else: - print(term.green_reverse('IOTstack Error: No function assigned to menu item: "{}"'.format(nodeRedBuildOptions[selection][0]))) - - def isMenuItemSelectable(menu, index): - if len(menu) > index: - if len(menu[index]) > 2: - if menu[index][2]["skip"] == True: - return False - return True - - def menuEntryPoint(): - # These need to be reglobalised due to eval() - global currentMenuItemIndex - global selectionInProgress - global menuNavigateDirection - global needsRender - global hideHelpText - global piHoleBuildOptions - term = Terminal() - with term.fullscreen(): - menuNavigateDirection = 0 - mainRender(needsRender, piHoleBuildOptions, currentMenuItemIndex) - selectionInProgress = True - with term.cbreak(): - while selectionInProgress: - menuNavigateDirection = 0 - - if needsRender: # Only rerender when changed to prevent flickering - mainRender(needsRender, piHoleBuildOptions, currentMenuItemIndex) - needsRender = 0 - - key = term.inkey(esc_delay=0.05) - if key.is_sequence: - if key.name == 'KEY_TAB': - menuNavigateDirection += 1 - if key.name == 'KEY_DOWN': - menuNavigateDirection += 1 - if key.name == 'KEY_UP': - menuNavigateDirection -= 1 - if key.name == 'KEY_LEFT': - goBack() - if key.name == 'KEY_ENTER': - runSelection(currentMenuItemIndex) - if key.name == 'KEY_ESCAPE': - return True - elif key: - if key == 'h': # H pressed - if hideHelpText: - hideHelpText = False - else: - hideHelpText = True - mainRender(1, piHoleBuildOptions, currentMenuItemIndex) - - if menuNavigateDirection != 0: # If a direction was pressed, find next selectable item - currentMenuItemIndex += menuNavigateDirection - currentMenuItemIndex = currentMenuItemIndex % len(piHoleBuildOptions) - needsRender = 2 - - while not isMenuItemSelectable(piHoleBuildOptions, currentMenuItemIndex): - currentMenuItemIndex += menuNavigateDirection - currentMenuItemIndex = currentMenuItemIndex % len(piHoleBuildOptions) - return True - - #################### - # End menu section - #################### - - if haltOnErrors: - eval(toRun)() - else: - try: - eval(toRun)() - except: - pass - -# This check isn't required, but placed here for debugging purposes -global currentServiceName # Name of the current service -if currentServiceName == 'pihole': - main() -else: - print("Error. '{}' Tried to run 'pihole' config".format(currentServiceName)) diff --git a/.templates/pihole/passwords.py b/.templates/pihole/passwords.py deleted file mode 100755 index 6a24483c3..000000000 --- a/.templates/pihole/passwords.py +++ /dev/null @@ -1,326 +0,0 @@ -#!/usr/bin/env python3 - -import signal - -def main(): - from blessed import Terminal - from deps.chars import specialChars, commonTopBorder, commonBottomBorder, commonEmptyLine - from deps.consts import servicesDirectory, templatesDirectory, buildSettingsFileName - import time - import subprocess - import ruamel.yaml - import os - - global signal - global currentServiceName - global menuSelectionInProgress - global mainMenuList - global currentMenuItemIndex - global renderMode - global paginationSize - global paginationStartIndex - global hideHelpText - - yaml = ruamel.yaml.YAML() - yaml.preserve_quotes = True - - try: # If not already set, then set it. - hideHelpText = hideHelpText - except: - hideHelpText = False - - term = Terminal() - hotzoneLocation = [((term.height // 16) + 6), 0] - paginationToggle = [10, term.height - 25] - paginationStartIndex = 0 - paginationSize = paginationToggle[0] - - serviceService = servicesDirectory + currentServiceName - serviceTemplate = templatesDirectory + currentServiceName - buildSettings = serviceService + buildSettingsFileName - - def goBack(): - global menuSelectionInProgress - global needsRender - menuSelectionInProgress = False - needsRender = 1 - return True - - mainMenuList = [] - - hotzoneLocation = [((term.height // 16) + 6), 0] - - menuSelectionInProgress = True - currentMenuItemIndex = 0 - menuNavigateDirection = 0 - - # Render Modes: - # 0 = No render needed - # 1 = Full render - # 2 = Hotzone only - needsRender = 1 - - def onResize(sig, action): - global mainMenuList - global currentMenuItemIndex - mainRender(1, mainMenuList, currentMenuItemIndex) - - def generateLineText(text, textLength=None, paddingBefore=0, lineLength=64): - result = "" - for i in range(paddingBefore): - result += " " - - textPrintableCharactersLength = textLength - - if (textPrintableCharactersLength) == None: - textPrintableCharactersLength = len(text) - - result += text - remainingSpace = lineLength - textPrintableCharactersLength - - for i in range(remainingSpace): - result += " " - - return result - - def renderHotZone(term, renderType, menu, selection, hotzoneLocation, paddingBefore = 4): - global paginationSize - selectedTextLength = len("-> ") - - print(term.move(hotzoneLocation[0], hotzoneLocation[1])) - - if paginationStartIndex >= 1: - print(term.center("{b} {uaf} {uaf}{uaf}{uaf} {ual} {b}".format( - b=specialChars[renderMode]["borderVertical"], - uaf=specialChars[renderMode]["upArrowFull"], - ual=specialChars[renderMode]["upArrowLine"] - ))) - else: - print(term.center(commonEmptyLine(renderMode))) - - for (index, menuItem) in enumerate(menu): # Menu loop - if index >= paginationStartIndex and index < paginationStartIndex + paginationSize: - lineText = generateLineText(menuItem[0], paddingBefore=paddingBefore) - - # Menu highlight logic - if index == selection: - formattedLineText = '-> {t.blue_on_green}{title}{t.normal} <-'.format(t=term, title=menuItem[0]) - paddedLineText = generateLineText(formattedLineText, textLength=len(menuItem[0]) + selectedTextLength, paddingBefore=paddingBefore - selectedTextLength) - toPrint = paddedLineText - else: - toPrint = '{title}{t.normal}'.format(t=term, title=lineText) - # ##### - - # Menu check render logic - if menuItem[1]["checked"]: - toPrint = " (X) " + toPrint - else: - toPrint = " ( ) " + toPrint - - toPrint = "{bv} {toPrint} {bv}".format(bv=specialChars[renderMode]["borderVertical"], toPrint=toPrint) # Generate border - toPrint = term.center(toPrint) # Center Text (All lines should have the same amount of printable characters) - # ##### - print(toPrint) - - if paginationStartIndex + paginationSize < len(menu): - print(term.center("{b} {daf} {daf}{daf}{daf} {dal} {b}".format( - b=specialChars[renderMode]["borderVertical"], - daf=specialChars[renderMode]["downArrowFull"], - dal=specialChars[renderMode]["downArrowLine"] - ))) - else: - print(term.center(commonEmptyLine(renderMode))) - print(term.center(commonEmptyLine(renderMode))) - print(term.center(commonEmptyLine(renderMode))) - - - def mainRender(needsRender, menu, selection): - global paginationStartIndex - global paginationSize - term = Terminal() - - if selection >= paginationStartIndex + paginationSize: - paginationStartIndex = selection - (paginationSize - 1) + 1 - needsRender = 1 - - if selection <= paginationStartIndex - 1: - paginationStartIndex = selection - needsRender = 1 - - if needsRender == 1: - print(term.clear()) - print(term.move_y(term.height // 16)) - print(term.black_on_cornsilk4(term.center('IOTstack PiHole Password Options'))) - print("") - print(term.center(commonTopBorder(renderMode))) - print(term.center(commonEmptyLine(renderMode))) - print(term.center("{bv} Select Password Option {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center(commonEmptyLine(renderMode))) - - if needsRender >= 1: - renderHotZone(term, needsRender, menu, selection, hotzoneLocation) - - if needsRender == 1: - print(term.center(commonEmptyLine(renderMode))) - if not hideHelpText: - if term.height < 32: - print(term.center(commonEmptyLine(renderMode))) - print(term.center("{bv} Not enough vertical room to render controls help text {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center(commonEmptyLine(renderMode))) - else: - print(term.center(commonEmptyLine(renderMode))) - print(term.center("{bv} Controls: {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center("{bv} [Space] to select option {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center("{bv} [Up] and [Down] to move selection cursor {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center("{bv} [H] Show/hide this text {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center("{bv} [Enter] to build and save option {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center("{bv} [Escape] to cancel changes {bv}".format(bv=specialChars[renderMode]["borderVertical"]))) - print(term.center(commonEmptyLine(renderMode))) - print(term.center(commonEmptyLine(renderMode))) - print(term.center(commonBottomBorder(renderMode))) - - def runSelection(selection): - import types - if len(mainMenuList[selection]) > 1 and isinstance(mainMenuList[selection][1], types.FunctionType): - mainMenuList[selection][1]() - else: - print(term.green_reverse('IOTstack Error: No function assigned to menu item: "{}"'.format(mainMenuList[selection][0]))) - - def isMenuItemSelectable(menu, index): - if len(menu) > index: - if len(menu[index]) > 1: - if "skip" in menu[index][1] and menu[index][1]["skip"] == True: - return False - return True - - def loadOptionsMenu(): - global mainMenuList - mainMenuList.append(["Use default password for this build", { "checked": True }]) - mainMenuList.append(["Randomise password for this build", { "checked": False }]) - # mainMenuList.append(["Randomise database password every build", { "checked": False }]) - mainMenuList.append(["Do nothing", { "checked": False }]) - - def checkMenuItem(selection): - global mainMenuList - for (index, menuItem) in enumerate(mainMenuList): - mainMenuList[index][1]["checked"] = False - - mainMenuList[selection][1]["checked"] = True - - def saveOptions(): - try: - if not os.path.exists(serviceService): - os.makedirs(serviceService, exist_ok=True) - - if os.path.exists(buildSettings): - with open(r'%s' % buildSettings) as objBuildSettingsFile: - piHoleYamlBuildOptions = yaml.load(objBuildSettingsFile) - else: - piHoleYamlBuildOptions = { - "version": "1", - "application": "IOTstack", - "service": "PiHole", - "comment": "Build Settings", - } - - piHoleYamlBuildOptions["databasePasswordOption"] = "" - - for (index, menuOption) in enumerate(mainMenuList): - if menuOption[1]["checked"]: - piHoleYamlBuildOptions["databasePasswordOption"] = menuOption[0] - break - - with open(buildSettings, 'w') as outputFile: - yaml.dump(piHoleYamlBuildOptions, outputFile) - - except Exception as err: - print("Error saving PiHole Password options", currentServiceName) - print(err) - return False - return True - - def loadOptions(): - try: - if not os.path.exists(serviceService): - os.makedirs(serviceService, exist_ok=True) - - if os.path.exists(buildSettings): - with open(r'%s' % buildSettings) as objBuildSettingsFile: - piHoleYamlBuildOptions = yaml.load(objBuildSettingsFile) - - for (index, menuOption) in enumerate(mainMenuList): - if menuOption[0] == piHoleYamlBuildOptions["databasePasswordOption"]: - checkMenuItem(index) - break - - except Exception as err: - print("Error loading PiHole Password options", currentServiceName) - print(err) - return False - return True - - - if __name__ == 'builtins': - global signal - term = Terminal() - signal.signal(signal.SIGWINCH, onResize) - loadOptionsMenu() - loadOptions() - with term.fullscreen(): - menuNavigateDirection = 0 - mainRender(needsRender, mainMenuList, currentMenuItemIndex) - menuSelectionInProgress = True - with term.cbreak(): - while menuSelectionInProgress: - menuNavigateDirection = 0 - - if not needsRender == 0: # Only rerender when changed to prevent flickering - mainRender(needsRender, mainMenuList, currentMenuItemIndex) - needsRender = 0 - - key = term.inkey(esc_delay=0.05) - if key.is_sequence: - if key.name == 'KEY_TAB': - if paginationSize == paginationToggle[0]: - paginationSize = paginationToggle[1] - else: - paginationSize = paginationToggle[0] - mainRender(1, mainMenuList, currentMenuItemIndex) - if key.name == 'KEY_DOWN': - menuNavigateDirection += 1 - if key.name == 'KEY_UP': - menuNavigateDirection -= 1 - if key.name == 'KEY_ENTER': - if saveOptions(): - return True - else: - print("Something went wrong. Try saving the list again.") - if key.name == 'KEY_ESCAPE': - menuSelectionInProgress = False - return True - elif key: - if key == ' ': # Space pressed - checkMenuItem(currentMenuItemIndex) # Update checked list - needsRender = 2 - elif key == 'h': # H pressed - if hideHelpText: - hideHelpText = False - else: - hideHelpText = True - mainRender(1, mainMenuList, currentMenuItemIndex) - - if menuNavigateDirection != 0: # If a direction was pressed, find next selectable item - currentMenuItemIndex += menuNavigateDirection - currentMenuItemIndex = currentMenuItemIndex % len(mainMenuList) - needsRender = 2 - - while not isMenuItemSelectable(mainMenuList, currentMenuItemIndex): - currentMenuItemIndex += menuNavigateDirection - currentMenuItemIndex = currentMenuItemIndex % len(mainMenuList) - return True - - return True - -originalSignalHandler = signal.getsignal(signal.SIGINT) -main() -signal.signal(signal.SIGWINCH, originalSignalHandler) diff --git a/.templates/pihole/service.yml b/.templates/pihole/service.yml index 341f34083..aaeff1f5b 100644 --- a/.templates/pihole/service.yml +++ b/.templates/pihole/service.yml @@ -2,21 +2,23 @@ pihole: container_name: pihole image: pihole/pihole:latest ports: - - "8089:80/tcp" - - "53:53/tcp" - - "53:53/udp" - - "67:67/udp" + - "8089:80/tcp" + - "53:53/tcp" + - "53:53/udp" + - "67:67/udp" environment: - - TZ=Etc/UTC - - WEBPASSWORD=%randomAdminPassword% - - INTERFACE=eth0 + - TZ=${TZ:-Etc/UTC} + - WEBPASSWORD= + # see https://sensorsiot.github.io/IOTstack/Containers/Pi-hole/#adminPassword + - INTERFACE=eth0 + # see https://github.com/pi-hole/docker-pi-hole#environment-variables volumes: - - ./volumes/pihole/etc-pihole:/etc/pihole - - ./volumes/pihole/etc-dnsmasq.d:/etc/dnsmasq.d + - ./volumes/pihole/etc-pihole:/etc/pihole + - ./volumes/pihole/etc-dnsmasq.d:/etc/dnsmasq.d dns: - - 127.0.0.1 - - 1.1.1.1 + - 127.0.0.1 + - 1.1.1.1 cap_add: - - NET_ADMIN + - NET_ADMIN restart: unless-stopped diff --git a/docs/Containers/Pi-hole.md b/docs/Containers/Pi-hole.md index d77c2679f..5790e83a8 100644 --- a/docs/Containers/Pi-hole.md +++ b/docs/Containers/Pi-hole.md @@ -10,7 +10,13 @@ Pi-hole is a fantastic utility to reduce ads. ## Environment variables { #envVars } -In conjunction with controls in Pi-hole's web GUI, environment variables govern much of Pi-hole's behaviour. If you are running new menu (master branch), the variables are inline in `docker-compose.yml`. If you are running old menu, the variables will be in `~/IOTstack/services/pihole/pihole.env` +In conjunction with controls in Pi-hole's web GUI, environment variables govern much of Pi-hole's behaviour. + +If you are running new menu (master branch), environment variables are inline in your compose file. If you are running old menu, the variables will be in: + +``` +~/IOTstack/services/pihole/pihole.env +``` > There is nothing about old menu which *requires* the variables to be stored in the `pihole.env` file. You can migrate everything to `docker-compose.yml` if you wish. @@ -21,62 +27,79 @@ Pi-hole's authoritative list of environment variables can be found [here](https: ### Admin password { #adminPassword } -The first time Pi-hole is launched, it checks for the `WEBPASSWORD` environment variable. If found, the right hand side becomes the administrative password. +By default, Pi-hole does not have an administrator password. That is because the default service definition provided by IOTstack contains the following environment variable with no value on its right hand side: -You can set the value of `WEBPASSWORD` in the IOTstack menu by: +``` yaml +- WEBPASSWORD= +``` -1. Placing the cursor on top of "pihole". -2. If Pi-hole is not already selected as a container, press space to select it. -3. Press the right arrow, and then -4. Choose "PiHole Password Options". +Each time the Pi-hole container is launched, it checks for the presence or absence of the `WEBPASSWORD` environment variable, then reacts like this: -From there, you have the choice of: +* If `WEBPASSWORD` is *defined* but does **not** have a value: -* *Use default password for this build* + - No admin password is set; + - Any previous admin password is cleared; + - You will be able to [connect to Pi-hole's web interface](#connectGUI) without providing a password (you won't even see the login screen); and + - The main menu (≡) will not contain a logout command. - Choosing this option results in: + This is the default situation for IOTstack. - ```yaml - - WEBPASSWORD=IOtSt4ckP1Hol3 - ``` +* If `WEBPASSWORD` is *defined* **and** has a value, that value will become the admin password. For example, to change your admin password to be "IOtSt4ckP1Hol3": -* *Randomise password for this build* + 1. Edit your compose file so that Pi-hole's service definition contains: - Choosing this option results in a randomly-generated password which you can find by inspecting your `docker-compose.yml`. + ``` yaml + - WEBPASSWORD=IOtSt4ckP1Hol3 + ``` -* *Do nothing* + 2. Run: - Choosing this option results in: + ``` console + $ cd ~/IOTstack + $ docker-compose up -d pihole + ``` - ```yaml - - WEBPASSWORD=%randomAdminPassword% - ``` + docker-compose will notice the change to the environment variable and re-create the container. The container will see that `WEBPASSWORD` has a value and will change the admin password to "IOtSt4ckP1Hol3". - which is a valid password string so "%randomAdminPassword%" will become the password. + You will be prompted for a password whenever you [connect to Pi-hole's web interface](#connectGUI). -Regardless of which option you choose, you can always edit your `docker-compose.yml` to change the value of the environment variable. For example: +* If `WEBPASSWORD` is *undefined* (absent from your compose file), Pi-hole behaves like this: -```yaml -- WEBPASSWORD=mybigsecret -``` + - If this is the first time Pi-hole has been launched, a random password is generated. + + Pi-hole senses "first launch" if it has to initialise its persistent storage area. See also [getting a clean slate](#cleanSlate). You can discover the password by running: -It is important to realise that `WEBPASSWORD` only has any effect on the very **first** launch. Once Pi-hole has been run at least once, the value of `WEBPASSWORD` is ignored and any changes you make will have no effect. + ``` console + $ docker logs pihole | grep random + ``` -If `WEBPASSWORD` is **not** set on first launch, Pi-hole defaults to a randomly-generated password which you can discover after the first launch like this: + Remember, docker logs are cleared each time a container is terminated or re-created so you need to run that command before the log disappears! + + - Otherwise, whatever password was set on the previous launch will be re-used. + +#### about `pihole -a -p` { #adminPassChange } + +Some Pi-hole documentation on the web recommends using the following command to change Pi-hole's admin password: ```console -$ docker logs pihole | grep random +$ docker exec pihole pihole -a -p «yourPasswordHere» ``` -> Remember, docker logs are ephemeral so you need to run that command before the log disappears! +That command works but its effect will always be overridden by `WEBPASSWORD`. For example, suppose your service definition contains: -If you ever need to reset Pi-hole's admin password to a known value, use the following command: +``` yaml +- WEBPASSWORD=myFirstPassword +``` -```console -$ docker exec pihole pihole -a -p mybigsecret +When you start the container, the admin password will be "myFirstPassword". If you run: + +``` console +$ docker exec pihole pihole -a -p mySecondPassword ``` -> replacing "mybigsecret" with your choice of password. +then "mySecondPassword" will become the admin password **until** the next time the container is re-created by docker-compose, at which point the password will be reset to "myFirstPassword". + +Given this behaviour, we recommend that you ignore the `pihole -a -p` command. ### Other variables { #otherVars }