Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8e4b936
swag: modernize module with improved error handling and UX
igorpecovnik Apr 29, 2026
3d15632
fix: replace lscr.io registry with docker hub for linuxserver images
igorpecovnik Apr 29, 2026
ee18006
swag: improve dialog box sizes and reduce click count
igorpecovnik Apr 29, 2026
d40ee98
docker: auto-install expect for unbuffer command
igorpecovnik Apr 29, 2026
39e39f8
swag: fix SSL certificate initialization and domain URL display
igorpecovnik Apr 29, 2026
d2e019a
swag: remove redundant lsio network check
igorpecovnik Apr 29, 2026
41a85b8
swag: use SWAG_URL file globally instead of .swag_url hidden file
igorpecovnik Apr 29, 2026
02444f9
global: use SWAG domain URL instead of IP in menu URLs
igorpecovnik Apr 29, 2026
9a5ed68
runtime: add SWAG to WebHosting menu URLs
igorpecovnik Apr 29, 2026
1c62617
transmission: auto-configure SWAG reverse proxy after installation
igorpecovnik Apr 29, 2026
0501433
transmission: fix SWAG proxy to use global SWAG_URL variable
igorpecovnik Apr 29, 2026
11a9639
transmission: handle SWAG .sample proxy config files
igorpecovnik Apr 29, 2026
ed3abf8
runtime: show SWAG subfolder paths instead of ports for proxied services
igorpecovnik Apr 29, 2026
584047d
transmission: add servicename to module_options for SWAG proxy
igorpecovnik Apr 29, 2026
518a88d
refactor: extract SWAG proxy configuration into reusable function
igorpecovnik Apr 29, 2026
c592598
feat: add SWAG auto-configuration to homepage module
igorpecovnik Apr 29, 2026
f7f1aed
fix: remove extra blank line in homepage module for consistency
igorpecovnik Apr 29, 2026
d04add4
fix: add missing TZ environment variable to homepage module
igorpecovnik Apr 29, 2026
abbdd07
feat: add HOMEPAGE_VAR environment variables for dynamic config
igorpecovnik Apr 29, 2026
4284d16
feat: add SWAG auto-configuration to portainer module
igorpecovnik Apr 29, 2026
a8971fb
fix: add port parameter to SWAG proxy configuration
igorpecovnik Apr 29, 2026
50520d6
fix: resolve Portainer 502 Bad Gateway error
igorpecovnik Apr 29, 2026
82b6d89
fix: update Portainer URL to use HTTPS and correct port
igorpecovnik Apr 29, 2026
2118009
feat: add SWAG auto-configuration to netbox module
igorpecovnik Apr 29, 2026
dcd1c52
feat: add SWAG auto-configuration to immich module
igorpecovnik Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions tools/modules/functions/module_docker_utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ docker_operation_progress() {
# Ensure Docker is available
docker_ensure_docker

# Ensure unbuffer is available (for real-time pull progress)
if ! command -v unbuffer >/dev/null 2>&1; then
pkg_install expect
fi
Comment on lines +145 to +148
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C3 'command -v unbuffer|pkg_install expect|unbuffer curl|case "\$operation" in' tools/modules/functions/module_docker_utils.sh

Repository: armbian/configng

Length of output: 896


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Search for all unbuffer occurrences
rg -n 'unbuffer' tools/modules/functions/module_docker_utils.sh

Repository: armbian/configng

Length of output: 258


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Get the full docker_operation_progress function to understand scope
rg -n -A150 'docker_operation_progress\(\)' tools/modules/functions/module_docker_utils.sh | head -200

Repository: armbian/configng

Length of output: 5930


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Check if expect is used elsewhere in the file
rg -n 'expect' tools/modules/functions/module_docker_utils.sh

Repository: armbian/configng

Length of output: 85


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Get context around line 298 to understand jq --unbuffered usage
sed -n '290,305p' tools/modules/functions/module_docker_utils.sh

Repository: armbian/configng

Length of output: 790


Scope expect installation to pull and add a safe fallback.

The unbuffer availability check and expect package installation run unconditionally for all operations (pull, rm, rmi, run), but unbuffer is only used in the pull operation (line 255). For non-pull operations, this causes unnecessary system mutation before operation validation, and any installation failure would incorrectly block rm/rmi/run operations. Gate this to pull only, and if unbuffer is unavailable, fall back to plain curl instead of hard-failing.

Proposed fix
 docker_operation_progress() {
 	local operation="$1"
 	local target="$2"
+	local use_unbuffer=0
@@
-	# Ensure unbuffer is available (for real-time pull progress)
-	if ! command -v unbuffer >/dev/null 2>&1; then
-		pkg_install expect
-	fi
+	# Only pull needs unbuffer; fallback to plain curl if unavailable
+	if [[ "$operation" == "pull" ]]; then
+		if command -v unbuffer >/dev/null 2>&1; then
+			use_unbuffer=1
+		elif pkg_install expect >/dev/null 2>&1 && command -v unbuffer >/dev/null 2>&1; then
+			use_unbuffer=1
+		fi
+	fi
@@
-				unbuffer curl --silent --show-error \
+				if [[ $use_unbuffer -eq 1 ]]; then
+					unbuffer curl --silent --show-error \
+						--unix-socket "$socket_path" \
+						-X POST "http://localhost/images/create?fromImage=${pull_image}&tag=${pull_tag}" \
+						-w "%{http_code}" \
+						-o "$raw_response_file" \
+						2> "$error_file" \
+					> "$http_code_file"
+				else
+					curl --silent --show-error \
+						--unix-socket "$socket_path" \
+						-X POST "http://localhost/images/create?fromImage=${pull_image}&tag=${pull_tag}" \
+						-w "%{http_code}" \
+						-o "$raw_response_file" \
+						2> "$error_file" \
+					> "$http_code_file"
+				fi
-					--unix-socket "$socket_path" \
-					-X POST "http://localhost/images/create?fromImage=${pull_image}&tag=${pull_tag}" \
-					-w "%{http_code}" \
-					-o "$raw_response_file" \
-					2> "$error_file" \
-				> "$http_code_file"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/modules/functions/module_docker_utils.sh` around lines 145 - 148, Move
the unbuffer availability check and pkg_install expect call out of the global
setup and into the branch that handles the pull operation (the code that
performs "pull" at / around the pull handler where unbuffer is used), so that
expect is only installed when performing pull; additionally, change the behavior
when unbuffer is not present to not hard-fail: if command -v unbuffer returns
false, skip pkg_install (or attempt it but do not abort on failure) and use a
safe fallback that calls plain curl (or the non-unbuffered code path) for
showing download progress; update the `unbuffer` check and any calls that assume
it in the pull handler to prefer unbuffer when available and otherwise use the
curl fallback, ensuring pkg_install expect is not invoked for rm/rmi/run
operations.


# Argument validation
if [[ -z "$operation" || -z "$target" ]]; then
dialog_msgbox "Usage Error" "Usage: docker_operation_progress <pull|rm|rmi> <target>\n\n pull <image> - Pull Docker image\n rm <container> - Remove container\n rmi <image> - Remove image" 12 60
Expand Down Expand Up @@ -467,3 +472,56 @@ docker_operation_progress() {

return 0
}

#
# Configure SWAG reverse proxy for a service
# Usage: docker_configure_swag_proxy <servicename> [port] [protocol]
#
# Parameters:
# servicename - Name of the service (e.g., "transmission", "sonarr")
# port - Optional: Override the default port in the proxy config
# protocol - Optional: Override the protocol (http/https) in the proxy config
#
# Returns: 0 on success, 1 if proxy config not found or enabling failed
#
docker_configure_swag_proxy() {
local servicename="$1"
local port="$2"
local protocol="$3"

# Check if SWAG container exists
if ! docker container ls -a --format "{{.Names}}" | grep -q "^swag$"; then
return 2
fi
Comment on lines +492 to +495
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Treat missing swag as a no-op in this helper.

This helper is used for optional auto-configuration, but it returns 2 when the swag container is absent. Callers that don't special-case that status turn a successful install into a failure path, which is what the new module install flows now do.

Suggested fix
 	# Check if SWAG container exists
 	if ! docker container ls -a --format "{{.Names}}" | grep -q "^swag$"; then
-		return 2
+		return 0
 	fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Check if SWAG container exists
if ! docker container ls -a --format "{{.Names}}" | grep -q "^swag$"; then
return 2
fi
# Check if SWAG container exists
if ! docker container ls -a --format "{{.Names}}" | grep -q "^swag$"; then
return 0
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/modules/functions/module_docker_utils.sh` around lines 490 - 493, The
helper currently treats a missing "swag" container as an error by returning 2
when the check docker container ls -a --format "{{.Names}}" | grep -q "^swag$"
fails; change this behavior to be a no-op for optional auto-configuration by
removing the error return (or returning 0) so callers don’t treat absence of the
"swag" container as a failure—update the block that checks for the "swag"
container in module_docker_utils.sh accordingly.


# Check if SWAG has proxy config for this service (sample or actual)
local proxy_sample="/config/nginx/proxy-confs/${servicename}.subfolder.conf.sample"
local proxy_actual="/config/nginx/proxy-confs/${servicename}.subfolder.conf"

if docker exec swag test -f "$proxy_sample" 2>/dev/null; then
# Copy sample to actual config if it doesn't exist
if ! docker exec swag test -f "$proxy_actual" 2>/dev/null; then
docker exec swag cp "$proxy_sample" "$proxy_actual" 2>/dev/null
fi

# If port is specified, update it in the config
if [[ -n "$port" ]]; then
docker exec swag sed -i "s/set \\\$upstream_port [0-9]*/set \\\$upstream_port ${port}/g" "$proxy_actual" 2>/dev/null
fi
Comment on lines +501 to +510
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail fast when creating or rewriting the proxy config fails.

Both cp and sed -i are unchecked here. The helper can therefore return success with no generated config or with the old upstream_port still in place.

Suggested fix
-	if docker exec swag test -f "$proxy_sample" 2>/dev/null; then
-		# Copy sample to actual config if it doesn't exist
-		if ! docker exec swag test -f "$proxy_actual" 2>/dev/null; then
-			docker exec swag cp "$proxy_sample" "$proxy_actual" 2>/dev/null
-		fi
-
-		# If port is specified, update it in the config
-		if [[ -n "$port" ]]; then
-			docker exec swag sed -i "s/set \\\$upstream_port [0-9]*/set \\\$upstream_port ${port}/g" "$proxy_actual" 2>/dev/null
-		fi
+	if docker exec swag test -f "$proxy_actual" 2>/dev/null || docker exec swag test -f "$proxy_sample" 2>/dev/null; then
+		if ! docker exec swag test -f "$proxy_actual" 2>/dev/null; then
+			docker exec swag cp "$proxy_sample" "$proxy_actual" 2>/dev/null || return 1
+		fi
+
+		if [[ -n "$port" ]]; then
+			docker exec swag sed -i "s/set \\\$upstream_port [0-9]*/set \\\$upstream_port ${port}/g" "$proxy_actual" 2>/dev/null || return 1
+		fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if docker exec swag test -f "$proxy_sample" 2>/dev/null; then
# Copy sample to actual config if it doesn't exist
if ! docker exec swag test -f "$proxy_actual" 2>/dev/null; then
docker exec swag cp "$proxy_sample" "$proxy_actual" 2>/dev/null
fi
# If port is specified, update it in the config
if [[ -n "$port" ]]; then
docker exec swag sed -i "s/set \\\$upstream_port [0-9]*/set \\\$upstream_port ${port}/g" "$proxy_actual" 2>/dev/null
fi
if docker exec swag test -f "$proxy_actual" 2>/dev/null || docker exec swag test -f "$proxy_sample" 2>/dev/null; then
# Copy sample to actual config if it doesn't exist
if ! docker exec swag test -f "$proxy_actual" 2>/dev/null; then
docker exec swag cp "$proxy_sample" "$proxy_actual" 2>/dev/null || return 1
fi
# If port is specified, update it in the config
if [[ -n "$port" ]]; then
docker exec swag sed -i "s/set \\\$upstream_port [0-9]*/set \\\$upstream_port ${port}/g" "$proxy_actual" 2>/dev/null || return 1
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/modules/functions/module_docker_utils.sh` around lines 499 - 508, The
code currently runs docker exec swag cp "$proxy_sample" "$proxy_actual" and
docker exec swag sed -i ... without checking their exit status, so failures can
be hidden; update the block around the cp and sed invocations (references:
variables proxy_sample, proxy_actual, port and the docker exec swag cp / docker
exec swag sed -i commands) to check each command's return code and fail fast: if
the cp fails, log an error and exit/return non-zero immediately instead of
continuing, and similarly after running sed -i (only when port is set) verify it
succeeded and abort with an error if it did not; ensure the error messages
provide context (which file and command failed) so callers can detect and handle
the failure.


# If protocol is specified, update it in the config
if [[ -n "$protocol" ]]; then
docker exec swag sed -i "s/set \\\$upstream_proto [a-z]*/set \\\$upstream_proto ${protocol}/g" "$proxy_actual" 2>/dev/null
fi

# Enable the proxy configuration
if docker exec swag touch "/config/nginx/proxy-confs/${servicename}.subfolder.conf.enabled" 2>/dev/null; then
# Reload nginx to apply
docker exec swag nginx -s reload >/dev/null 2>&1
return 0
Comment on lines +517 to +521
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't return success until the nginx reload succeeds.

docker exec swag nginx -s reload is ignored here, so the helper can return 0 even when SWAG rejects the new config. That makes the new auto-configuration look successful while leaving the proxy inactive.

Proposed fix
 		# Enable the proxy configuration
-		if docker exec swag touch "/config/nginx/proxy-confs/${servicename}.subfolder.conf.enabled" 2>/dev/null; then
-			# Reload nginx to apply
-			docker exec swag nginx -s reload >/dev/null 2>&1
-			return 0
-		fi
+		if ! docker exec swag touch "/config/nginx/proxy-confs/${servicename}.subfolder.conf.enabled" 2>/dev/null; then
+			return 1
+		fi
+		if ! docker exec swag nginx -s reload >/dev/null 2>&1; then
+			return 1
+		fi
+		return 0
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Enable the proxy configuration
if docker exec swag touch "/config/nginx/proxy-confs/${servicename}.subfolder.conf.enabled" 2>/dev/null; then
# Reload nginx to apply
docker exec swag nginx -s reload >/dev/null 2>&1
return 0
# Enable the proxy configuration
if ! docker exec swag touch "/config/nginx/proxy-confs/${servicename}.subfolder.conf.enabled" 2>/dev/null; then
return 1
fi
if ! docker exec swag nginx -s reload >/dev/null 2>&1; then
return 1
fi
return 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/modules/functions/module_docker_utils.sh` around lines 503 - 507, The
script currently returns success immediately after touching the SWAG proxy conf;
change the logic in the block that runs docker exec swag touch
"/config/nginx/proxy-confs/${servicename}.subfolder.conf.enabled" so that it
only returns 0 if the subsequent docker exec swag nginx -s reload succeeds; run
the reload, capture its exit status (from the docker exec nginx command), and
propagate a non-zero return if the reload fails (include context in logs if
present) instead of ignoring the reload's failure.

fi
return 1
fi

return 1
}
7 changes: 7 additions & 0 deletions tools/modules/functions/module_env_init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ function set_runtime_variables() {
LOCALIPADD=$(ip -4 addr show dev $DEFAULT_ADAPTER | awk '/inet/ {print $2}' | cut -d'/' -f1)
LOCALSUBNET=$(echo ${LOCALIPADD} | cut -d"." -f1-3).0/24

# Check if SWAG is installed and use its domain URL for display
# This replaces LOCALIPADD with the actual domain in menu URLs
SWAG_URL=""
if [[ -f "${SOFTWARE_FOLDER}/swag/config/SWAG_URL" ]]; then
SWAG_URL=$(cat "${SOFTWARE_FOLDER}/swag/config/SWAG_URL")
fi

# create local lan and docker lan whitelist for transmission
TRANSMISSION_WHITELIST=$(echo ${LOCALIPADD} | cut -d"." -f1-3)".*"
local docker_subnet=$(docker network inspect lsio 2> /dev/null | grep Subnet | xargs | cut -d" " -f2 | cut -d"/" -f1 | cut -d"." -f1-2)
Expand Down
123 changes: 79 additions & 44 deletions tools/modules/runtime/config.runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,34 @@ locale_setting="$LANG"
installed_software="$(see_current_apt)"
held_packages=$(apt-mark showhold)

# Use SWAG domain URL if available, otherwise fall back to local IP
# This makes all menu URLs show the actual domain instead of IP when SWAG is installed
DISPLAY_URL="${SWAG_URL:-$LOCALIPADD}"

# Helper function to determine service URL format
# Returns "https://DISPLAY_URL/service" if SWAG proxy is enabled, otherwise "http://DISPLAY_URL:port"
get_service_url() {
local service_name="$1"
local service_port="$2"

# Check if SWAG is running
if ! docker container ls -a --format "{{.Names}}" 2>/dev/null | grep -q "^swag$"; then
echo "http://$DISPLAY_URL:$service_port"
return
fi

# Check if SWAG proxy is enabled for this service
local proxy_enabled_file="/config/nginx/proxy-confs/${service_name}.subfolder.conf.enabled"
if ! docker exec swag test -f "$proxy_enabled_file" 2>/dev/null; then
echo "http://$DISPLAY_URL:$service_port"
return
fi

# SWAG proxy is enabled - return subfolder URL
echo "https://$DISPLAY_URL/$service_name"
}


module_options+=(
["update_json_data,author"]="@Tearran"
["update_json_data,ref_link"]=""
Expand Down Expand Up @@ -103,72 +131,79 @@ update_sub_submenu_data "System" "Storage" "NFS04" "$NFS_CLIENTS_NUMBER"

# Database
update_sub_submenu_data "Software" "Database" "DAT002" "Server: $LOCALIPADD"
update_sub_submenu_data "Software" "Database" "MYA002" "http://$LOCALIPADD:${module_options["module_phpmyadmin,port"]}"
update_sub_submenu_data "Software" "Database" "MYA002" "http://$DISPLAY_URL:${module_options["module_phpmyadmin,port"]}"

# Finance
update_sub_submenu_data "Software" "Finance" "ABU002" "http://$LOCALIPADD:${module_options["module_actualbudget,port"]}"
update_sub_submenu_data "Software" "Finance" "WAL002" "http://$LOCALIPADD:${module_options["module_wallos,port"]}"
update_sub_submenu_data "Software" "Finance" "ABU002" "http://$DISPLAY_URL:${module_options["module_actualbudget,port"]}"
update_sub_submenu_data "Software" "Finance" "WAL002" "http://$DISPLAY_URL:${module_options["module_wallos,port"]}"

# Media
update_sub_submenu_data "Software" "Media" "OMV002" "http://$LOCALIPADD:${module_options["module_omv,port"]}"
update_sub_submenu_data "Software" "Media" "MED002" "http://$LOCALIPADD:${module_options["module_plexmediaserver,port"]}"
update_sub_submenu_data "Software" "Media" "EMB002" "http://$LOCALIPADD:${module_options["module_embyserver,port"]}"
update_sub_submenu_data "Software" "Media" "FIL002" "http://$LOCALIPADD:${module_options["module_filebrowser,port"]}"
update_sub_submenu_data "Software" "Media" "STR002" "http://$LOCALIPADD:${module_options["module_stirling,port"]}"
update_sub_submenu_data "Software" "Media" "STC002" "http://$LOCALIPADD:${module_options["module_syncthing,port"]%% *}" # removing second port from url
update_sub_submenu_data "Software" "Media" "NCT002" "https://$LOCALIPADD:${module_options["module_nextcloud,port"]}"
update_sub_submenu_data "Software" "Media" "OWC002" "http://$LOCALIPADD:${module_options["module_owncloud,port"]}"
update_sub_submenu_data "Software" "Media" "JMS002" "http://$LOCALIPADD:${module_options["module_jellyfin,port"]}"
update_sub_submenu_data "Software" "Media" "IMM002" "http://$LOCALIPADD:${module_options["module_immich,port"]}"
update_sub_submenu_data "Software" "Media" "NAV002" "http://$LOCALIPADD:${module_options["module_navidrome,port"]}"
update_sub_submenu_data "Software" "Media" "OMV002" "http://$DISPLAY_URL:${module_options["module_omv,port"]}"
update_sub_submenu_data "Software" "Media" "MED002" "http://$DISPLAY_URL:${module_options["module_plexmediaserver,port"]}"
update_sub_submenu_data "Software" "Media" "EMB002" "http://$DISPLAY_URL:${module_options["module_embyserver,port"]}"
update_sub_submenu_data "Software" "Media" "FIL002" "http://$DISPLAY_URL:${module_options["module_filebrowser,port"]}"
update_sub_submenu_data "Software" "Media" "STR002" "http://$DISPLAY_URL:${module_options["module_stirling,port"]}"
update_sub_submenu_data "Software" "Media" "STC002" "http://$DISPLAY_URL:${module_options["module_syncthing,port"]%% *}" # removing second port from url
update_sub_submenu_data "Software" "Media" "NCT002" "https://$DISPLAY_URL:${module_options["module_nextcloud,port"]}"
update_sub_submenu_data "Software" "Media" "OWC002" "http://$DISPLAY_URL:${module_options["module_owncloud,port"]}"
update_sub_submenu_data "Software" "Media" "JMS002" "http://$DISPLAY_URL:${module_options["module_jellyfin,port"]}"
update_sub_submenu_data "Software" "Media" "IMM002" "$(get_service_url "immich" "${module_options["module_immich,port"]}")"
update_sub_submenu_data "Software" "Media" "NAV002" "http://$DISPLAY_URL:${module_options["module_navidrome,port"]}"

# Containers
update_sub_submenu_data "Software" "Containers" "POR002" "http://$LOCALIPADD:${module_options["module_portainer,port"]%% *}" # removing second port from url
# Portainer uses HTTPS on port 9443 (not the edge agent port 9000)
if docker container ls -a --format "{{.Names}}" 2>/dev/null | grep -q "^swag$" && \
docker exec swag test -f "/config/nginx/proxy-confs/portainer.subfolder.conf.enabled" 2>/dev/null; then
update_sub_submenu_data "Software" "Containers" "POR002" "https://$DISPLAY_URL/portainer"
else
update_sub_submenu_data "Software" "Containers" "POR002" "https://$DISPLAY_URL:9443"
fi

# Backup
update_sub_submenu_data "Software" "Backup" "DPL002" "http://$LOCALIPADD:${module_options["module_duplicati,port"]%% *}" # removing second port from url
update_sub_submenu_data "Software" "Backup" "DPL002" "http://$DISPLAY_URL:${module_options["module_duplicati,port"]%% *}" # removing second port from url

# Printing
update_sub_submenu_data "Software" "Printing" "OCT002" "http://$LOCALIPADD:${module_options["module_octoprint,port"]}"
update_sub_submenu_data "Software" "Printing" "OCT002" "http://$DISPLAY_URL:${module_options["module_octoprint,port"]}"

# DevTools
[[ -f /etc/rsyncd.conf ]] && update_sub_submenu_data "Software" "DevTools" "DEV011" "$(grep -oP '(?<=^\[).*(?=\])' /etc/rsyncd.conf | xargs)"

# Home automation
update_sub_submenu_data "Software" "HomeAutomation" "HAB002" "http://$LOCALIPADD:${module_options["module_openhab,port"]}"
update_sub_submenu_data "Software" "HomeAutomation" "HAS002" "http://$LOCALIPADD:${module_options["module_haos,port"]}"
update_sub_submenu_data "Software" "HomeAutomation" "DOM002" "http://$LOCALIPADD:${module_options["module_domoticz,port"]}"
update_sub_submenu_data "Software" "HomeAutomation" "EVCC02" "http://$LOCALIPADD:${module_options["module_evcc,port"]}"
update_sub_submenu_data "Software" "HomeAutomation" "HAB002" "http://$DISPLAY_URL:${module_options["module_openhab,port"]}"
update_sub_submenu_data "Software" "HomeAutomation" "HAS002" "http://$DISPLAY_URL:${module_options["module_haos,port"]}"
update_sub_submenu_data "Software" "HomeAutomation" "DOM002" "http://$DISPLAY_URL:${module_options["module_domoticz,port"]}"
update_sub_submenu_data "Software" "HomeAutomation" "EVCC02" "http://$DISPLAY_URL:${module_options["module_evcc,port"]}"

# DNS
update_sub_submenu_data "Software" "DNS" "PIH003" "http://$LOCALIPADD:${module_options["module_pi_hole,port"]%% *}/admin" # removing second port from url
update_sub_submenu_data "Software" "DNS" "ADG002" "http://$LOCALIPADD:${module_options["module_adguardhome,port"]%% *}" # removing second port from url
update_sub_submenu_data "Software" "DNS" "PIH003" "http://$DISPLAY_URL:${module_options["module_pi_hole,port"]%% *}/admin" # removing second port from url
update_sub_submenu_data "Software" "DNS" "ADG002" "http://$DISPLAY_URL:${module_options["module_adguardhome,port"]%% *}" # removing second port from url

# Monitoring
update_sub_submenu_data "Software" "Monitoring" "UPK002" "http://$LOCALIPADD:${module_options["module_uptimekuma,port"]}"
update_sub_submenu_data "Software" "Monitoring" "NTD002" "http://$LOCALIPADD:${module_options["module_netdata,port"]}"
update_sub_submenu_data "Software" "Monitoring" "GRA002" "http://$LOCALIPADD:${module_options["module_grafana,port"]}"
update_sub_submenu_data "Software" "Monitoring" "NAX002" "http://$LOCALIPADD:${module_options["module_netalertx,port"]}"
update_sub_submenu_data "Software" "Monitoring" "PRO002" "http://$LOCALIPADD:${module_options["module_prometheus,port"]}"
update_sub_submenu_data "Software" "Monitoring" "UPK002" "http://$DISPLAY_URL:${module_options["module_uptimekuma,port"]}"
update_sub_submenu_data "Software" "Monitoring" "NTD002" "http://$DISPLAY_URL:${module_options["module_netdata,port"]}"
update_sub_submenu_data "Software" "Monitoring" "GRA002" "http://$DISPLAY_URL:${module_options["module_grafana,port"]}"
update_sub_submenu_data "Software" "Monitoring" "NAX002" "http://$DISPLAY_URL:${module_options["module_netalertx,port"]}"
update_sub_submenu_data "Software" "Monitoring" "PRO002" "http://$DISPLAY_URL:${module_options["module_prometheus,port"]}"

# Management
update_sub_submenu_data "Software" "Management" "CPT002" "https://$LOCALIPADD:${module_options["module_cockpit,port"]}"
update_sub_submenu_data "Software" "Management" "HPG002" "http://$LOCALIPADD:${module_options["module_homepage,port"]}"
update_sub_submenu_data "Software" "Management" "NBOX02" "http://$LOCALIPADD:${module_options["module_netbox,port"]}"
update_sub_submenu_data "Software" "Management" "CPT002" "https://$DISPLAY_URL:${module_options["module_cockpit,port"]}"
update_sub_submenu_data "Software" "Management" "HPG002" "http://$DISPLAY_URL:${module_options["module_homepage,port"]}"
update_sub_submenu_data "Software" "Management" "NBOX02" "$(get_service_url "netbox" "${module_options["module_netbox,port"]}")"

# Downloaders
update_sub_submenu_data "Software" "Downloaders" "DOW002" "http://$LOCALIPADD:${module_options["module_qbittorrent,port"]%% *}" # removing second port from url
update_sub_submenu_data "Software" "Downloaders" "DEL002" "http://$LOCALIPADD:${module_options["module_deluge,port"]%% *}" # removing second port from url
update_sub_submenu_data "Software" "Downloaders" "TRA002" "http://$LOCALIPADD:${module_options["module_transmission,port"]%% *}" # removing second port from url
update_sub_submenu_data "Software" "Downloaders" "SABN02" "http://$LOCALIPADD:${module_options["module_sabnzbd,port"]}"
update_sub_submenu_data "Software" "Downloaders" "MDS002" "http://$LOCALIPADD:${module_options["module_medusa,port"]}"
update_sub_submenu_data "Software" "Downloaders" "SON002" "http://$LOCALIPADD:${module_options["module_sonarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "RAD002" "http://$LOCALIPADD:${module_options["module_radarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "BAZ002" "http://$LOCALIPADD:${module_options["module_bazarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "LID002" "http://$LOCALIPADD:${module_options["module_lidarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "RDR002" "http://$LOCALIPADD:${module_options["module_readarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "DOW026" "http://$LOCALIPADD:${module_options["module_prowlarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "JEL002" "http://$LOCALIPADD:${module_options["module_jellyseerr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "DOW002" "http://$DISPLAY_URL:${module_options["module_qbittorrent,port"]%% *}" # removing second port from url
update_sub_submenu_data "Software" "Downloaders" "DEL002" "http://$DISPLAY_URL:${module_options["module_deluge,port"]%% *}" # removing second port from url
update_sub_submenu_data "Software" "Downloaders" "TRA002" "$(get_service_url ${module_options["module_transmission,servicename"]} ${module_options["module_transmission,port"]})" # removing second port from url
update_sub_submenu_data "Software" "Downloaders" "SABN02" "http://$DISPLAY_URL:${module_options["module_sabnzbd,port"]}"
update_sub_submenu_data "Software" "Downloaders" "MDS002" "http://$DISPLAY_URL:${module_options["module_medusa,port"]}"
update_sub_submenu_data "Software" "Downloaders" "SON002" "http://$DISPLAY_URL:${module_options["module_sonarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "RAD002" "http://$DISPLAY_URL:${module_options["module_radarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "BAZ002" "http://$DISPLAY_URL:${module_options["module_bazarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "LID002" "http://$DISPLAY_URL:${module_options["module_lidarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "RDR002" "http://$DISPLAY_URL:${module_options["module_readarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "DOW026" "http://$DISPLAY_URL:${module_options["module_prowlarr,port"]}"
update_sub_submenu_data "Software" "Downloaders" "JEL002" "http://$DISPLAY_URL:${module_options["module_jellyseerr,port"]}"

# web
update_sub_submenu_data "Software" "WebHosting" "GHOST2" "http://$LOCALIPADD:${module_options["module_ghost,port"]}/ghost"
update_sub_submenu_data "Software" "WebHosting" "GHOST2" "http://$DISPLAY_URL:${module_options["module_ghost,port"]}/ghost"
update_sub_submenu_data "Software" "WebHosting" "SWAG01" "https://$DISPLAY_URL"
2 changes: 1 addition & 1 deletion tools/modules/software/module_bazarr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module_options+=(
["module_bazarr,group"]="Downloaders"
["module_bazarr,port"]="6767"
["module_bazarr,arch"]="x86-64 arm64"
["module_bazarr,dockerimage"]="lscr.io/linuxserver/bazarr:latest"
["module_bazarr,dockerimage"]="linuxserver/bazarr:latest"
["module_bazarr,dockername"]="bazarr"
)
#
Expand Down
2 changes: 1 addition & 1 deletion tools/modules/software/module_code-server.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module_options+=(
["module_code-server,group"]="Development"
["module_code-server,port"]="8443"
["module_code-server,arch"]="x86-64 arm64"
["module_code-server,dockerimage"]="lscr.io/linuxserver/code-server:latest"
["module_code-server,dockerimage"]="linuxserver/code-server:latest"
["module_code-server,dockername"]="code-server"
)
#
Expand Down
2 changes: 1 addition & 1 deletion tools/modules/software/module_deluge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module_options+=(
["module_deluge,group"]="Downloaders"
["module_deluge,port"]="8112"
["module_deluge,arch"]="x86-64 arm64"
["module_deluge,dockerimage"]="lscr.io/linuxserver/deluge:latest"
["module_deluge,dockerimage"]="linuxserver/deluge:latest"
["module_deluge,dockername"]="deluge"
)
#
Expand Down
2 changes: 1 addition & 1 deletion tools/modules/software/module_duplicati.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module_options+=(
["module_duplicati,group"]="Backup"
["module_duplicati,port"]="8200"
["module_duplicati,arch"]="x86-64 arm64"
["module_duplicati,dockerimage"]="lscr.io/linuxserver/duplicati:latest"
["module_duplicati,dockerimage"]="linuxserver/duplicati:latest"
["module_duplicati,dockername"]="duplicati"
)
#
Expand Down
2 changes: 1 addition & 1 deletion tools/modules/software/module_embyserver.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module_options+=(
["module_embyserver,group"]="Media"
["module_embyserver,port"]="8091"
["module_embyserver,arch"]="x86-64 arm64"
["module_embyserver,dockerimage"]="lscr.io/linuxserver/emby:latest"
["module_embyserver,dockerimage"]="linuxserver/emby:latest"
["module_embyserver,dockername"]="emby"
)
#
Expand Down
6 changes: 6 additions & 0 deletions tools/modules/software/module_homepage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module_options+=(
["module_homepage,arch"]=""
["module_homepage,dockerimage"]="ghcr.io/gethomepage/homepage:latest"
["module_homepage,dockername"]="homepage"
["module_homepage,servicename"]="homepage"
)
#
# Module Homepage
Expand Down Expand Up @@ -40,12 +41,17 @@ function module_homepage () {
--name="$dockername" \
-e PUID="${DOCKER_USERUID}" \
-e PGID="${DOCKER_GROUPUID}" \
-e TZ="$(cat /etc/timezone)" \
-e HOMEPAGE_ALLOWED_HOSTS="${LOCALIPADD}:${port},homepage.local:${port},localhost:${port}" \
-e HOMEPAGE_VAR_LOCALIPADD="${LOCALIPADD}" \
-e HOMEPAGE_VAR_SWAG_URL="${SWAG_URL:-}" \
-p "${port}:3000" \
-v "${base_dir}/config:/app/config" \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--restart=always \
"$dockerimage"
# Auto-configure SWAG reverse proxy if available
docker_configure_swag_proxy "homepage" "3000"
Comment on lines +53 to +54
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Apply the same install-status guard here.

This optional proxy step can override the result of docker_operation_progress run. On a failed container start, the module can still return success; on a host without swag, a successful Homepage install returns failure.

Suggested fix
-			docker_configure_swag_proxy "homepage" "3000"
+			docker_configure_swag_proxy "homepage" "3000" || [[ $? -eq 2 ]]
-				"$dockerimage"
+				"$dockerimage" || return 1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/modules/software/module_homepage.sh` around lines 53 - 54, The
docker_configure_swag_proxy call can override or misreport the module install
result; wrap the docker_configure_swag_proxy "homepage" "3000" invocation in the
same install-status guard used after docker_operation_progress run so the proxy
configuration only runs when the container start/install succeeded. Check the
success status returned by docker_operation_progress (or the module's install
status variable) before calling docker_configure_swag_proxy and skip the proxy
step when the install failed or when swag is absent.

;;
"${commands[1]}") # remove
# Remove container and image (functions handle existence checks)
Expand Down
4 changes: 4 additions & 0 deletions tools/modules/software/module_immich.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module_options+=(
["module_immich,arch"]="x86-64 arm64"
["module_immich,dockerimage"]="ghcr.io/imagegenius/immich:latest"
["module_immich,dockername"]="immich"
["module_immich,servicename"]="immich"
)
#
# Module immich
Expand Down Expand Up @@ -145,6 +146,9 @@ function module_immich () {
echo "XXX"; echo "100"; echo "Timed out waiting for Immich"; echo "XXX"
) | dialog_gauge "$title" "Starting Immich..." 8 60

# Auto-configure SWAG reverse proxy if available
docker_configure_swag_proxy "immich" "8080"

# Verify Immich is responding
if ! curl -sf http://localhost:${port}/ > /dev/null; then
dialog_msgbox "$title Warning" \
Expand Down
2 changes: 1 addition & 1 deletion tools/modules/software/module_jellyfin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module_options+=(
["module_jellyfin,group"]="Media"
["module_jellyfin,port"]="8096"
["module_jellyfin,arch"]="x86-64 arm64"
["module_jellyfin,dockerimage"]="lscr.io/linuxserver/jellyfin:latest"
["module_jellyfin,dockerimage"]="linuxserver/jellyfin:latest"
["module_jellyfin,dockername"]="jellyfin"
)
#
Expand Down
Loading
Loading