refactor(storage): resolve every app directory through appDir
The main sweep — ~260 call sites across ~100 files move from string
concatenation on a single root to appDir/storageAppDirs/storageAppConfigs.
On a single-root install the resolved paths are identical, so this is a
no-op until a location is registered.
Enumerators were the interesting half. `for d in "$containers_dir"/*/`
appears in the menus, the registry/artifact scanners and the DNS setup —
and a shell glob cannot list a rootless 751 tree at all, which is the
same bug config_find_file.sh already documents in a comment. Routing them
through storageAppDirs (which enumerates as the owning user) fixes that
alongside the multi-root work.
Three places needed judgement rather than substitution:
db_app_scan.sh deletes database rows and port allocations for apps whose
folder is missing, and reaps "empty" app dirs. With a storage location
unmounted, every app on it looks exactly like that. Each of those
branches now gates on appStorageAvailable first — an app on an unplugged
drive is skipped with a notice, never deleted.
instance_create.sh rewrites cloned hooks so an instance touches its own
directory instead of the base app's. Its sed matched ${containers_dir}<type>,
which this sweep just replaced with $(appDir <type>) — so it would have
silently stopped redirecting, and an instance would have written to the
original's files (the adguard auth adapter case its own comment warns
about). Now matches both appDir forms, verified against bare, quoted,
unrelated-app, legacy and prose cases.
peer_shell/peer_pull streamed and extracted relative to the primary root.
Both now use the app's own root, and peer_shell keeps a single-root
fallback since it runs as a restricted SSH shell with no LibrePortal env.
Also fixes a pre-existing bug found on the way: webui_app_config.sh
tested "$containers_dir/frontend/data/last_update", one level short of the
real tree under the libreportal app dir, so the WebUI refresh trigger
after a config update has never once fired.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
2d24a764a8
commit
8b5e02c760
@ -5,7 +5,7 @@ authAdapter_adguard_setPassword() {
|
|||||||
user="${user:-${CFG_ADGUARD_ADMIN_USER:-admin}}"
|
user="${user:-${CFG_ADGUARD_ADMIN_USER:-admin}}"
|
||||||
[[ -z "$password" ]] && password=$(generateRandomPassword)
|
[[ -z "$password" ]] && password=$(generateRandomPassword)
|
||||||
|
|
||||||
local yaml="${containers_dir}adguard/conf/AdGuardHome.yaml"
|
local yaml="$(appDir adguard)/conf/AdGuardHome.yaml"
|
||||||
[[ ! -f "$yaml" ]] && { isError "AdGuardHome.yaml not found at $yaml."; return 1; }
|
[[ ! -f "$yaml" ]] && { isError "AdGuardHome.yaml not found at $yaml."; return 1; }
|
||||||
if ! command -v htpasswd >/dev/null 2>&1; then
|
if ! command -v htpasswd >/dev/null 2>&1; then
|
||||||
isError "htpasswd is required to bcrypt the new password."
|
isError "htpasswd is required to bcrypt the new password."
|
||||||
|
|||||||
@ -18,7 +18,7 @@ adguard_install_post_start()
|
|||||||
# install pipeline; the resolved host port is stored in the PORTS_TAG_1
|
# install pipeline; the resolved host port is stored in the PORTS_TAG_1
|
||||||
# docker-compose tag (format `external:internal`). Pull it from there
|
# docker-compose tag (format `external:internal`). Pull it from there
|
||||||
# so the curl + URL printout actually point somewhere real.
|
# so the curl + URL printout actually point somewhere real.
|
||||||
local adguard_compose_file="$containers_dir$app_name/docker-compose.yml"
|
local adguard_compose_file="$(appDir "$app_name")/docker-compose.yml"
|
||||||
local adguard_port_pair
|
local adguard_port_pair
|
||||||
adguard_port_pair=$(tagsManagerGetTagContent "$adguard_compose_file" "PORTS_TAG_1")
|
adguard_port_pair=$(tagsManagerGetTagContent "$adguard_compose_file" "PORTS_TAG_1")
|
||||||
local adguard_admin_port="${adguard_port_pair%%:*}"
|
local adguard_admin_port="${adguard_port_pair%%:*}"
|
||||||
@ -89,11 +89,11 @@ JSON
|
|||||||
|
|
||||||
local result
|
local result
|
||||||
if [[ "$public" == "true" ]]; then
|
if [[ "$public" == "true" ]]; then
|
||||||
result=$(runFileOp sed -i "s|allow_unencrypted_doh: false|allow_unencrypted_doh: true|g" "$containers_dir$app_name/conf/AdGuardHome.yaml")
|
result=$(runFileOp sed -i "s|allow_unencrypted_doh: false|allow_unencrypted_doh: true|g" "$(appDir "$app_name")/conf/AdGuardHome.yaml")
|
||||||
checkSuccess "Setting allow_unencrypted_doh to false for Traefik"
|
checkSuccess "Setting allow_unencrypted_doh to false for Traefik"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
result=$(runFileOp sed -i "s|anonymize_client_ip: false: false|anonymize_client_ip: true|g" "$containers_dir$app_name/conf/AdGuardHome.yaml")
|
result=$(runFileOp sed -i "s|anonymize_client_ip: false: false|anonymize_client_ip: true|g" "$(appDir "$app_name")/conf/AdGuardHome.yaml")
|
||||||
checkSuccess "Setting anonymize_client_ip to true for privacy reasons"
|
checkSuccess "Setting anonymize_client_ip to true for privacy reasons"
|
||||||
|
|
||||||
# Force the admin web bind back to 0.0.0.0:3000 inside the container.
|
# Force the admin web bind back to 0.0.0.0:3000 inside the container.
|
||||||
@ -102,7 +102,7 @@ JSON
|
|||||||
# the install API call AdGuardHome sometimes ends up bound to
|
# the install API call AdGuardHome sometimes ends up bound to
|
||||||
# 0.0.0.0:80 (its build-time default) — exactly what causes "unable
|
# 0.0.0.0:80 (its build-time default) — exactly what causes "unable
|
||||||
# to connect" on the host port.
|
# to connect" on the host port.
|
||||||
local adguard_yaml="$containers_dir$app_name/conf/AdGuardHome.yaml"
|
local adguard_yaml="$(appDir "$app_name")/conf/AdGuardHome.yaml"
|
||||||
if [[ -f "$adguard_yaml" ]]; then
|
if [[ -f "$adguard_yaml" ]]; then
|
||||||
runFileOp sed -i 's|^\(\s*address:\s*\)0\.0\.0\.0:[0-9]\+|\10.0.0.0:3000|' "$adguard_yaml"
|
runFileOp sed -i 's|^\(\s*address:\s*\)0\.0\.0\.0:[0-9]\+|\10.0.0.0:3000|' "$adguard_yaml"
|
||||||
runFileOp sed -i 's|^\(\s*bind_host:\s*\).*|\10.0.0.0|' "$adguard_yaml"
|
runFileOp sed -i 's|^\(\s*bind_host:\s*\).*|\10.0.0.0|' "$adguard_yaml"
|
||||||
|
|||||||
@ -18,18 +18,18 @@ authelia_install_post_compose()
|
|||||||
|
|
||||||
local result
|
local result
|
||||||
result=$(copyResource "$app_name" "configuration.yml" "config" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
result=$(copyResource "$app_name" "configuration.yml" "config" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
||||||
checkSuccess "Copying configuration.yml to $containers_dir$app_name/config"
|
checkSuccess "Copying configuration.yml to $(appDir "$app_name")/config"
|
||||||
|
|
||||||
result=$(copyResource "$app_name" "users_database.yml" "config" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
result=$(copyResource "$app_name" "users_database.yml" "config" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
||||||
checkSuccess "Copying users_database.yml to $containers_dir$app_name/config"
|
checkSuccess "Copying users_database.yml to $(appDir "$app_name")/config"
|
||||||
|
|
||||||
local authelia_config_file="$containers_dir$app_name/config/configuration.yml"
|
local authelia_config_file="$(appDir "$app_name")/config/configuration.yml"
|
||||||
runFileOp sed -i "s|AUTHELIA_THEME_PLACEHOLDER|$CFG_AUTHELIA_THEME|g" "$authelia_config_file"
|
runFileOp sed -i "s|AUTHELIA_THEME_PLACEHOLDER|$CFG_AUTHELIA_THEME|g" "$authelia_config_file"
|
||||||
runFileOp sed -i "s|AUTHELIA_DOMAIN_PLACEHOLDER|$domain_full|g" "$authelia_config_file"
|
runFileOp sed -i "s|AUTHELIA_DOMAIN_PLACEHOLDER|$domain_full|g" "$authelia_config_file"
|
||||||
runFileOp sed -i "s|AUTHELIA_HOST_PLACEHOLDER|$host_setup|g" "$authelia_config_file"
|
runFileOp sed -i "s|AUTHELIA_HOST_PLACEHOLDER|$host_setup|g" "$authelia_config_file"
|
||||||
checkSuccess "Substituting Authelia configuration values (theme=$CFG_AUTHELIA_THEME domain=$domain_full host=$host_setup)"
|
checkSuccess "Substituting Authelia configuration values (theme=$CFG_AUTHELIA_THEME domain=$domain_full host=$host_setup)"
|
||||||
|
|
||||||
local authelia_secrets_dir="$containers_dir$app_name/secrets"
|
local authelia_secrets_dir="$(appDir "$app_name")/secrets"
|
||||||
runFileOp mkdir -p "$authelia_secrets_dir"
|
runFileOp mkdir -p "$authelia_secrets_dir"
|
||||||
local secret_name secret_file
|
local secret_name secret_file
|
||||||
for secret_name in JWT_SECRET SESSION_SECRET STORAGE_ENCRYPTION_KEY; do
|
for secret_name in JWT_SECRET SESSION_SECRET STORAGE_ENCRYPTION_KEY; do
|
||||||
@ -58,7 +58,7 @@ authelia_install_post_start()
|
|||||||
|
|
||||||
local authelia_admin_user="${CFG_AUTHELIA_ADMIN_USERNAME:-admin}"
|
local authelia_admin_user="${CFG_AUTHELIA_ADMIN_USERNAME:-admin}"
|
||||||
local authelia_admin_pass="${CFG_AUTHELIA_ADMIN_PASSWORD_1:-authelia}"
|
local authelia_admin_pass="${CFG_AUTHELIA_ADMIN_PASSWORD_1:-authelia}"
|
||||||
local authelia_users_file="$containers_dir$app_name/config/users_database.yml"
|
local authelia_users_file="$(appDir "$app_name")/config/users_database.yml"
|
||||||
local authelia_attempts=0
|
local authelia_attempts=0
|
||||||
while ((authelia_attempts < 30)); do
|
while ((authelia_attempts < 30)); do
|
||||||
if runFileOp docker exec authelia-service authelia --version >/dev/null 2>&1; then
|
if runFileOp docker exec authelia-service authelia --version >/dev/null 2>&1; then
|
||||||
|
|||||||
@ -12,7 +12,7 @@ bookstack_install_post_start()
|
|||||||
local bookstack_target_email="${CFG_BOOKSTACK_ADMIN_EMAIL:-admin@admin.com}"
|
local bookstack_target_email="${CFG_BOOKSTACK_ADMIN_EMAIL:-admin@admin.com}"
|
||||||
local bookstack_target_pass="${CFG_BOOKSTACK_ADMIN_PASSWORD_1:-password}"
|
local bookstack_target_pass="${CFG_BOOKSTACK_ADMIN_PASSWORD_1:-password}"
|
||||||
|
|
||||||
local bookstack_compose_file="$containers_dir$app_name/docker-compose.yml"
|
local bookstack_compose_file="$(appDir "$app_name")/docker-compose.yml"
|
||||||
local bookstack_port_pair
|
local bookstack_port_pair
|
||||||
bookstack_port_pair=$(tagsManagerGetTagContent "$bookstack_compose_file" "PORTS_TAG_1")
|
bookstack_port_pair=$(tagsManagerGetTagContent "$bookstack_compose_file" "PORTS_TAG_1")
|
||||||
local bookstack_host_port="${bookstack_port_pair%%:*}"
|
local bookstack_host_port="${bookstack_port_pair%%:*}"
|
||||||
|
|||||||
@ -175,7 +175,7 @@ installCrowdsecHost()
|
|||||||
# -f guard always failed, and the key was never mirrored. The key
|
# -f guard always failed, and the key was never mirrored. The key
|
||||||
# is declared in crowdsec.config, so that is where it belongs.
|
# is declared in crowdsec.config, so that is where it belongs.
|
||||||
# The helper handles cscli + tee + chown + chmod atomically.
|
# The helper handles cscli + tee + chown + chmod atomically.
|
||||||
local cfg_file="${containers_dir}crowdsec/crowdsec.config"
|
local cfg_file="$(appDir crowdsec)/crowdsec.config"
|
||||||
local key_file="/etc/crowdsec/traefik_bouncer.key"
|
local key_file="/etc/crowdsec/traefik_bouncer.key"
|
||||||
|
|
||||||
# What the config currently holds, if anything. Quotes and whitespace
|
# What the config currently holds, if anything. Quotes and whitespace
|
||||||
|
|||||||
@ -31,7 +31,7 @@ appCrowdsecRotateBouncerKey()
|
|||||||
# Mirror it the same way the installer does, so the config page and the key
|
# Mirror it the same way the installer does, so the config page and the key
|
||||||
# file agree. updateConfigOption escapes the value, writes as the owner of
|
# file agree. updateConfigOption escapes the value, writes as the owner of
|
||||||
# the containers tree, and re-sources.
|
# the containers tree, and re-sources.
|
||||||
local cfg_file="${containers_dir}${app_name}/${app_name}.config"
|
local cfg_file="$(appDir "$app_name")/${app_name}.config"
|
||||||
if [[ -f "$cfg_file" ]]; then
|
if [[ -f "$cfg_file" ]]; then
|
||||||
updateConfigOption "CFG_CROWDSEC_TRAEFIK_LAPI_KEY" "$bouncer_key" "$cfg_file"
|
updateConfigOption "CFG_CROWDSEC_TRAEFIK_LAPI_KEY" "$bouncer_key" "$cfg_file"
|
||||||
else
|
else
|
||||||
@ -41,7 +41,7 @@ appCrowdsecRotateBouncerKey()
|
|||||||
# Traefik reads the key from the bind-mounted file at startup, so it keeps
|
# Traefik reads the key from the bind-mounted file at startup, so it keeps
|
||||||
# presenting the revoked key until it restarts. Without this the rotation
|
# presenting the revoked key until it restarts. Without this the rotation
|
||||||
# looks successful while every bouncer check fails with 403.
|
# looks successful while every bouncer check fails with 403.
|
||||||
if [[ -d "${containers_dir}traefik" ]]; then
|
if [[ -d "$(appDir traefik)" ]]; then
|
||||||
dockerComposeRestart traefik
|
dockerComposeRestart traefik
|
||||||
checkSuccess "Restarting Traefik to load the new bouncer key"
|
checkSuccess "Restarting Traefik to load the new bouncer key"
|
||||||
else
|
else
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
# (read from the app's `# Category :` header).
|
# (read from the app's `# Category :` header).
|
||||||
appDashyUpdateConf()
|
appDashyUpdateConf()
|
||||||
{
|
{
|
||||||
local conf_file="${containers_dir}dashy/etc/conf.yml"
|
local conf_file="$(appDir dashy)/etc/conf.yml"
|
||||||
local services_json="$(webuiDir)/frontend/data/apps/generated/apps-services.json"
|
local services_json="$(webuiDir)/frontend/data/apps/generated/apps-services.json"
|
||||||
local icons_src_dir="$(webuiDir)/frontend/icons/apps"
|
local icons_src_dir="$(webuiDir)/frontend/icons/apps"
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ appDashyUpdateConf()
|
|||||||
# Look at the actual docker container instead — if the container
|
# Look at the actual docker container instead — if the container
|
||||||
# exists or the install dir is present, generate the conf.
|
# exists or the install dir is present, generate the conf.
|
||||||
if ! runFileOp docker ps -a --format '{{.Names}}' 2>/dev/null | grep -qE '^(dashy|dashy-service)$' \
|
if ! runFileOp docker ps -a --format '{{.Names}}' 2>/dev/null | grep -qE '^(dashy|dashy-service)$' \
|
||||||
&& [[ ! -d "${containers_dir}dashy" ]]; then
|
&& [[ ! -d "$(appDir dashy)" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ appDashyManageShortcuts()
|
|||||||
selected=$(toolArgsGet "$tool_args" "selected")
|
selected=$(toolArgsGet "$tool_args" "selected")
|
||||||
selected="${selected//%7C/|}"
|
selected="${selected//%7C/|}"
|
||||||
|
|
||||||
local cfg_file="${containers_dir}dashy/dashy.config"
|
local cfg_file="$(appDir dashy)/dashy.config"
|
||||||
updateConfigOption "CFG_DASHY_SHORTCUTS" "$selected" "$cfg_file"
|
updateConfigOption "CFG_DASHY_SHORTCUTS" "$selected" "$cfg_file"
|
||||||
export CFG_DASHY_SHORTCUTS="$selected"
|
export CFG_DASHY_SHORTCUTS="$selected"
|
||||||
isSuccessful "Saved CFG_DASHY_SHORTCUTS=$selected"
|
isSuccessful "Saved CFG_DASHY_SHORTCUTS=$selected"
|
||||||
|
|||||||
@ -16,7 +16,7 @@ gitea_install_post_compose()
|
|||||||
if [[ -n "$CFG_GITEA_METRICS_TOKEN_1" ]]; then
|
if [[ -n "$CFG_GITEA_METRICS_TOKEN_1" ]]; then
|
||||||
local result
|
local result
|
||||||
result=$(runFileOp sed -i "s|GITEA_METRICS_TOKEN_PLACEHOLDER|${CFG_GITEA_METRICS_TOKEN_1}|g" \
|
result=$(runFileOp sed -i "s|GITEA_METRICS_TOKEN_PLACEHOLDER|${CFG_GITEA_METRICS_TOKEN_1}|g" \
|
||||||
"$containers_dir$app_name/resources/monitoring/prometheus-scrape.yml")
|
"$(appDir "$app_name")/resources/monitoring/prometheus-scrape.yml")
|
||||||
checkSuccess "Synced Gitea /metrics token to the Prometheus scrape config"
|
checkSuccess "Synced Gitea /metrics token to the Prometheus scrape config"
|
||||||
else
|
else
|
||||||
isNotice "CFG_GITEA_METRICS_TOKEN_1 is empty — Gitea /metrics scrape may 401."
|
isNotice "CFG_GITEA_METRICS_TOKEN_1 is empty — Gitea /metrics scrape may 401."
|
||||||
|
|||||||
@ -52,7 +52,7 @@ appNetworkApplyMode_gluetun()
|
|||||||
# apps drop out automatically. Self-skips if gluetun isn't installed.
|
# apps drop out automatically. Self-skips if gluetun isn't installed.
|
||||||
appNetworkRegisterPorts_gluetun()
|
appNetworkRegisterPorts_gluetun()
|
||||||
{
|
{
|
||||||
local gluetun_compose="${containers_dir}gluetun/docker-compose.yml"
|
local gluetun_compose="$(appDir gluetun)/docker-compose.yml"
|
||||||
if [[ ! -f "$gluetun_compose" ]]; then return 0; fi
|
if [[ ! -f "$gluetun_compose" ]]; then return 0; fi
|
||||||
if ! command -v sqlite3 >/dev/null 2>&1; then return 0; fi
|
if ! command -v sqlite3 >/dev/null 2>&1; then return 0; fi
|
||||||
if [[ ! -f "$docker_dir/$db_file" ]]; then return 0; fi
|
if [[ ! -f "$docker_dir/$db_file" ]]; then return 0; fi
|
||||||
@ -66,7 +66,7 @@ appNetworkRegisterPorts_gluetun()
|
|||||||
# leaves an undeclared name EMPTY in the CALLER's scope at EOF.
|
# leaves an undeclared name EMPTY in the CALLER's scope at EOF.
|
||||||
while IFS= read -r app; do
|
while IFS= read -r app; do
|
||||||
[[ -z "$app" || "$app" == "gluetun" ]] && continue
|
[[ -z "$app" || "$app" == "gluetun" ]] && continue
|
||||||
local app_config_file="${containers_dir}${app}/${app}.config"
|
local app_config_file="$(appDir "$app")/${app}.config"
|
||||||
[[ -f "$app_config_file" ]] || continue
|
[[ -f "$app_config_file" ]] || continue
|
||||||
local net
|
local net
|
||||||
net=$(grep -E "^CFG_${app^^}_NETWORK=" "$app_config_file" 2>/dev/null | cut -d'=' -f2 | tr -d '"')
|
net=$(grep -E "^CFG_${app^^}_NETWORK=" "$app_config_file" 2>/dev/null | cut -d'=' -f2 | tr -d '"')
|
||||||
@ -104,7 +104,7 @@ appNetworkRegisterPorts_gluetun()
|
|||||||
runFileOp mv "$tmp" "$gluetun_compose"
|
runFileOp mv "$tmp" "$gluetun_compose"
|
||||||
if runFileOp docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^gluetun-service$'; then
|
if runFileOp docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^gluetun-service$'; then
|
||||||
isNotice "Gluetun forwarded ports changed; recreating gluetun-service to apply."
|
isNotice "Gluetun forwarded ports changed; recreating gluetun-service to apply."
|
||||||
(cd "${containers_dir}gluetun" && runFileOp docker compose up -d --force-recreate gluetun-service >/dev/null 2>&1) || true
|
(cd "$(appDir gluetun)" && runFileOp docker compose up -d --force-recreate gluetun-service >/dev/null 2>&1) || true
|
||||||
# Recreating gluetun gives it a new container ID, which orphans every
|
# Recreating gluetun gives it a new container ID, which orphans every
|
||||||
# `network_mode: container:gluetun-service` reference. Re-attach all
|
# `network_mode: container:gluetun-service` reference. Re-attach all
|
||||||
# routed apps so they share the new netns instead of getting their own.
|
# routed apps so they share the new netns instead of getting their own.
|
||||||
|
|||||||
@ -37,7 +37,7 @@ appGluetunRecreateRouted()
|
|||||||
# leaves an undeclared name EMPTY in the CALLER's scope at EOF.
|
# leaves an undeclared name EMPTY in the CALLER's scope at EOF.
|
||||||
while IFS= read -r app; do
|
while IFS= read -r app; do
|
||||||
[[ -z "$app" || "$app" == "gluetun" ]] && continue
|
[[ -z "$app" || "$app" == "gluetun" ]] && continue
|
||||||
local app_config_file="${containers_dir}${app}/${app}.config"
|
local app_config_file="$(appDir "$app")/${app}.config"
|
||||||
[[ -f "$app_config_file" ]] || continue
|
[[ -f "$app_config_file" ]] || continue
|
||||||
|
|
||||||
local net
|
local net
|
||||||
@ -45,7 +45,7 @@ appGluetunRecreateRouted()
|
|||||||
| cut -d'=' -f2 | tr -d '"')
|
| cut -d'=' -f2 | tr -d '"')
|
||||||
[[ "$net" != "gluetun" ]] && continue
|
[[ "$net" != "gluetun" ]] && continue
|
||||||
|
|
||||||
local app_compose="${containers_dir}${app}/docker-compose.yml"
|
local app_compose="$(appDir "$app")/docker-compose.yml"
|
||||||
[[ -f "$app_compose" ]] || continue
|
[[ -f "$app_compose" ]] || continue
|
||||||
|
|
||||||
# Skip apps with no running/created container — recreate would
|
# Skip apps with no running/created container — recreate would
|
||||||
@ -56,7 +56,7 @@ appGluetunRecreateRouted()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
isNotice "Re-attaching ${app} to gluetun's namespace (force-recreate)..."
|
isNotice "Re-attaching ${app} to gluetun's namespace (force-recreate)..."
|
||||||
dockerCommandRun "cd ${containers_dir}${app} && docker compose up -d --force-recreate ${app}-service" >/dev/null 2>&1 || true
|
dockerCommandRun "cd $(appDir "$app") && docker compose up -d --force-recreate ${app}-service" >/dev/null 2>&1 || true
|
||||||
((recreated++))
|
((recreated++))
|
||||||
done <<< "$installed_apps"
|
done <<< "$installed_apps"
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ gluetunRouteExistingAppsPrompt()
|
|||||||
# leaves an undeclared name EMPTY in the CALLER's scope at EOF.
|
# leaves an undeclared name EMPTY in the CALLER's scope at EOF.
|
||||||
while IFS= read -r app; do
|
while IFS= read -r app; do
|
||||||
[[ -z "$app" ]] && continue
|
[[ -z "$app" ]] && continue
|
||||||
local cfg_file="${containers_dir}${app}/${app}.config"
|
local cfg_file="$(appDir "$app")/${app}.config"
|
||||||
[[ -f "$cfg_file" ]] || continue
|
[[ -f "$cfg_file" ]] || continue
|
||||||
local category
|
local category
|
||||||
category=$(grep -E "^CFG_${app^^}_CATEGORY=" "$cfg_file" 2>/dev/null \
|
category=$(grep -E "^CFG_${app^^}_CATEGORY=" "$cfg_file" 2>/dev/null \
|
||||||
@ -56,7 +56,7 @@ gluetunRouteExistingAppsPrompt()
|
|||||||
|
|
||||||
echo " The following installed apps can be routed through Gluetun:"
|
echo " The following installed apps can be routed through Gluetun:"
|
||||||
for app in "${eligible[@]}"; do
|
for app in "${eligible[@]}"; do
|
||||||
local cfg_file="${containers_dir}${app}/${app}.config"
|
local cfg_file="$(appDir "$app")/${app}.config"
|
||||||
local current
|
local current
|
||||||
current=$(grep -E "^CFG_${app^^}_NETWORK=" "$cfg_file" 2>/dev/null | head -1 | cut -d'=' -f2)
|
current=$(grep -E "^CFG_${app^^}_NETWORK=" "$cfg_file" 2>/dev/null | head -1 | cut -d'=' -f2)
|
||||||
[[ -z "$current" ]] && current="default"
|
[[ -z "$current" ]] && current="default"
|
||||||
@ -73,7 +73,7 @@ gluetunRouteExistingAppsPrompt()
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
for app in "${eligible[@]}"; do
|
for app in "${eligible[@]}"; do
|
||||||
local cfg_file="${containers_dir}${app}/${app}.config"
|
local cfg_file="$(appDir "$app")/${app}.config"
|
||||||
local current
|
local current
|
||||||
current=$(grep -E "^CFG_${app^^}_NETWORK=" "$cfg_file" 2>/dev/null | head -1 | cut -d'=' -f2)
|
current=$(grep -E "^CFG_${app^^}_NETWORK=" "$cfg_file" 2>/dev/null | head -1 | cut -d'=' -f2)
|
||||||
[[ -z "$current" ]] && current="default"
|
[[ -z "$current" ]] && current="default"
|
||||||
|
|||||||
@ -22,9 +22,9 @@ grafana_install_post_start()
|
|||||||
# install user and failed per file with "Operation not permitted", failing the
|
# install user and failed per file with "Operation not permitted", failing the
|
||||||
# step on every reinstall — while a fresh install passed, because the dir was
|
# step on every reinstall — while a fresh install passed, because the dir was
|
||||||
# still empty. Grafana's files must keep Grafana's ownership regardless.
|
# still empty. Grafana's files must keep Grafana's ownership regardless.
|
||||||
if [ -d "${containers_dir}grafana/grafana_storage" ]; then
|
if [ -d "$(appDir grafana)/grafana_storage" ]; then
|
||||||
local result
|
local result
|
||||||
result=$(runFileOp chmod 0777 "${containers_dir}grafana/grafana_storage")
|
result=$(runFileOp chmod 0777 "$(appDir grafana)/grafana_storage")
|
||||||
checkSuccess "Set permissions to grafana_storage folder."
|
checkSuccess "Set permissions to grafana_storage folder."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,13 +15,13 @@ headscaleCommands()
|
|||||||
isHeader "Install Headscale Apps List"
|
isHeader "Install Headscale Apps List"
|
||||||
|
|
||||||
# Find all subdirectories under the directory where your apps are installed
|
# Find all subdirectories under the directory where your apps are installed
|
||||||
for app_dir in "$containers_dir"/*/; do
|
while IFS= read -r app_dir; do
|
||||||
if [[ -d "$app_dir" ]]; then
|
if [[ -d "$app_dir" ]]; then
|
||||||
# Extract the app name (folder name)
|
# Extract the app name (folder name)
|
||||||
local app_name=$(basename "$app_dir")
|
local app_name=$(basename "$app_dir")
|
||||||
local app_names+=("$app_name")
|
local app_names+=("$app_name")
|
||||||
fi
|
fi
|
||||||
done
|
done < <(storageAppDirs)
|
||||||
|
|
||||||
# Check if any apps were found
|
# Check if any apps were found
|
||||||
if [ ${#app_names[@]} -eq 0 ]; then
|
if [ ${#app_names[@]} -eq 0 ]; then
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
headscaleEditConfig()
|
headscaleEditConfig()
|
||||||
{
|
{
|
||||||
local config_file="${containers_dir}headscale/config/config.yaml"
|
local config_file="$(appDir headscale)/config/config.yaml"
|
||||||
local previous_md5=$(md5sum "$config_file" | awk '{print $1}')
|
local previous_md5=$(md5sum "$config_file" | awk '{print $1}')
|
||||||
$CFG_TEXT_EDITOR "$config_file"
|
$CFG_TEXT_EDITOR "$config_file"
|
||||||
local current_md5=$(md5sum "$config_file" | awk '{print $1}')
|
local current_md5=$(md5sum "$config_file" | awk '{print $1}')
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
setupHeadscaleGetHostname()
|
setupHeadscaleGetHostname()
|
||||||
{
|
{
|
||||||
local config_file="${containers_dir}headscale/config/config.yaml"
|
local config_file="$(appDir headscale)/config/config.yaml"
|
||||||
if [ -f "$config_file" ]; then
|
if [ -f "$config_file" ]; then
|
||||||
# Read the line with "server_url" and extract the hostname
|
# Read the line with "server_url" and extract the hostname
|
||||||
headscale_live_hostname=$(grep "server_url:" "$config_file" | awk -F'server_url: ' '{print $2}')
|
headscale_live_hostname=$(grep "server_url:" "$config_file" | awk -F'server_url: ' '{print $2}')
|
||||||
|
|||||||
@ -9,7 +9,7 @@ headscale_install_post_compose()
|
|||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
|
|
||||||
local result
|
local result
|
||||||
result=$(createFolders "loud" $docker_install_user $containers_dir$app_name/config)
|
result=$(createFolders "loud" $docker_install_user $(appDir "$app_name")/config)
|
||||||
checkSuccess "Create config folder"
|
checkSuccess "Create config folder"
|
||||||
|
|
||||||
result=$(copyResource "$app_name" "config.yaml" "config" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
result=$(copyResource "$app_name" "config.yaml" "config" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
||||||
|
|||||||
@ -5,10 +5,10 @@ tailscaleInstallToContainer()
|
|||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local type="$2"
|
local type="$2"
|
||||||
|
|
||||||
local result; result=$(createFolders "loud" $docker_install_user $containers_dir$app_name/tailscale)
|
local result; result=$(createFolders "loud" $docker_install_user $(appDir "$app_name")/tailscale)
|
||||||
checkSuccess "Creating Tailscale folder"
|
checkSuccess "Creating Tailscale folder"
|
||||||
|
|
||||||
copyFile "loud" "${install_containers_dir}headscale/resources/tailscale.sh" "$containers_dir$app_name/tailscale/tailscale.sh" $docker_install_user | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1
|
copyFile "loud" "${install_containers_dir}headscale/resources/tailscale.sh" "$(appDir "$app_name")/tailscale/tailscale.sh" $docker_install_user | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1
|
||||||
|
|
||||||
if [[ "$type" != "install" ]]; then
|
if [[ "$type" != "install" ]]; then
|
||||||
dockerComposeRestart $app_name;
|
dockerComposeRestart $app_name;
|
||||||
|
|||||||
@ -19,20 +19,20 @@ jitsimeet_install_post_setup()
|
|||||||
echo "The latest tag is: $latest_tag"
|
echo "The latest tag is: $latest_tag"
|
||||||
|
|
||||||
local result
|
local result
|
||||||
result=$(createFolders "loud" $docker_install_user $containers_dir$app_name)
|
result=$(createFolders "loud" $docker_install_user $(appDir "$app_name"))
|
||||||
checkSuccess "Creating $app_name container installation folder"
|
checkSuccess "Creating $app_name container installation folder"
|
||||||
result=$(cd $containers_dir$app_name && runFileOp rm -rf $containers_dir$app_name/$latest_tag.zip)
|
result=$(cd $(appDir "$app_name") && runFileOp rm -rf $(appDir "$app_name")/$latest_tag.zip)
|
||||||
checkSuccess "Deleting zip file to prevent conflicts"
|
checkSuccess "Deleting zip file to prevent conflicts"
|
||||||
result=$(createTouch $containers_dir$app_name/$latest_tag.txt $docker_install_user && echo "Installed \"$latest_tag\" on \"$backupDate\"!" > $latest_tag.txt)
|
result=$(createTouch $(appDir "$app_name")/$latest_tag.txt $docker_install_user && echo "Installed \"$latest_tag\" on \"$backupDate\"!" > $latest_tag.txt)
|
||||||
checkSuccess "Create logging txt file"
|
checkSuccess "Create logging txt file"
|
||||||
|
|
||||||
result=$(runFileOp wget -O $containers_dir$app_name/$latest_tag.zip $git_url/archive/refs/tags/$latest_tag.zip)
|
result=$(runFileOp wget -O $(appDir "$app_name")/$latest_tag.zip $git_url/archive/refs/tags/$latest_tag.zip)
|
||||||
checkSuccess "Downloading tagged zip file from GitHub"
|
checkSuccess "Downloading tagged zip file from GitHub"
|
||||||
result=$(runFileOp unzip -o $containers_dir$app_name/$latest_tag.zip -d $containers_dir$app_name)
|
result=$(runFileOp unzip -o $(appDir "$app_name")/$latest_tag.zip -d $(appDir "$app_name"))
|
||||||
checkSuccess "Unzip downloaded file"
|
checkSuccess "Unzip downloaded file"
|
||||||
result=$(runFileOp mv $containers_dir$app_name/docker-jitsi-meet-$latest_tag/* $containers_dir$app_name)
|
result=$(runFileOp mv $(appDir "$app_name")/docker-jitsi-meet-$latest_tag/* $(appDir "$app_name"))
|
||||||
checkSuccess "Moving all files from zip file to install directory"
|
checkSuccess "Moving all files from zip file to install directory"
|
||||||
result=$(runFileOp rm -rf $containers_dir$app_name/$latest_tag.zip && runFileOp rm -rf $containers_dir$app_name/$latest_tag/)
|
result=$(runFileOp rm -rf $(appDir "$app_name")/$latest_tag.zip && runFileOp rm -rf $(appDir "$app_name")/$latest_tag/)
|
||||||
checkSuccess "Removing downloaded zip file as no longer needed"
|
checkSuccess "Removing downloaded zip file as no longer needed"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,36 +48,36 @@ jitsimeet_install_post_compose()
|
|||||||
dockerSetupEnvFile
|
dockerSetupEnvFile
|
||||||
|
|
||||||
local result
|
local result
|
||||||
result=$(runFileOp sed -i "s|CONFIG=~/.jitsi-meet-cfg|CONFIG=$containers_dir$app_name/.jitsi-meet-cfg|g" $containers_dir$app_name/.env)
|
result=$(runFileOp sed -i "s|CONFIG=~/.jitsi-meet-cfg|CONFIG=$(appDir "$app_name")/.jitsi-meet-cfg|g" $(appDir "$app_name")/.env)
|
||||||
checkSuccess "Updating .env file with new install path"
|
checkSuccess "Updating .env file with new install path"
|
||||||
|
|
||||||
result=$(runFileOp sed -i "s|#PUBLIC_URL=https://meet.example.com|PUBLIC_URL=https://$host_setup|g" $containers_dir$app_name/.env)
|
result=$(runFileOp sed -i "s|#PUBLIC_URL=https://meet.example.com|PUBLIC_URL=https://$host_setup|g" $(appDir "$app_name")/.env)
|
||||||
checkSuccess "Updating .env file with Public URL to $host_setup"
|
checkSuccess "Updating .env file with Public URL to $host_setup"
|
||||||
|
|
||||||
result=$(runFileOp sed -i "s|HTTP_PORT=8000|HTTP_PORT=$usedport1|g" $containers_dir$app_name/.env)
|
result=$(runFileOp sed -i "s|HTTP_PORT=8000|HTTP_PORT=$usedport1|g" $(appDir "$app_name")/.env)
|
||||||
checkSuccess "Updating .env file with HTTP_PORT to $usedport1"
|
checkSuccess "Updating .env file with HTTP_PORT to $usedport1"
|
||||||
|
|
||||||
result=$(runFileOp sed -i "s|HTTPS_PORT=8443|HTTPS_PORT=$usedport2|g" $containers_dir$app_name/.env)
|
result=$(runFileOp sed -i "s|HTTPS_PORT=8443|HTTPS_PORT=$usedport2|g" $(appDir "$app_name")/.env)
|
||||||
checkSuccess "Updating .env file with HTTP_PORT to $usedport2"
|
checkSuccess "Updating .env file with HTTP_PORT to $usedport2"
|
||||||
|
|
||||||
# Defaults missing from the shipped .env (see jitsi/docker-jitsi-meet
|
# Defaults missing from the shipped .env (see jitsi/docker-jitsi-meet
|
||||||
# commit 12051700562d…). Append them here so the install boots.
|
# commit 12051700562d…). Append them here so the install boots.
|
||||||
result=$(echo "XMPP_DOMAIN=meet.jitsi" | runFileWrite -a "$containers_dir$app_name/.env")
|
result=$(echo "XMPP_DOMAIN=meet.jitsi" | runFileWrite -a "$(appDir "$app_name")/.env")
|
||||||
checkSuccess "Updating .env file with missing option : XMPP_DOMAIN"
|
checkSuccess "Updating .env file with missing option : XMPP_DOMAIN"
|
||||||
|
|
||||||
result=$(echo "XMPP_SERVER=xmpp.meet.jitsi" | runFileWrite -a "$containers_dir$app_name/.env")
|
result=$(echo "XMPP_SERVER=xmpp.meet.jitsi" | runFileWrite -a "$(appDir "$app_name")/.env")
|
||||||
checkSuccess "Updating .env file with missing option : XMPP_SERVER"
|
checkSuccess "Updating .env file with missing option : XMPP_SERVER"
|
||||||
|
|
||||||
result=$(echo "JVB_PORT=$usedport4" | runFileWrite -a "$containers_dir$app_name/.env")
|
result=$(echo "JVB_PORT=$usedport4" | runFileWrite -a "$(appDir "$app_name")/.env")
|
||||||
checkSuccess "Updating .env file with missing option : JVB_PORT"
|
checkSuccess "Updating .env file with missing option : JVB_PORT"
|
||||||
|
|
||||||
result=$(echo "JVB_TCP_MAPPED_PORT=$usedport5" | runFileWrite -a "$containers_dir$app_name/.env")
|
result=$(echo "JVB_TCP_MAPPED_PORT=$usedport5" | runFileWrite -a "$(appDir "$app_name")/.env")
|
||||||
checkSuccess "Updating .env file with missing option : JVB_TCP_MAPPED_PORT"
|
checkSuccess "Updating .env file with missing option : JVB_TCP_MAPPED_PORT"
|
||||||
|
|
||||||
result=$(echo "JVB_TCP_PORT=$usedport5" | runFileWrite -a "$containers_dir$app_name/.env")
|
result=$(echo "JVB_TCP_PORT=$usedport5" | runFileWrite -a "$(appDir "$app_name")/.env")
|
||||||
checkSuccess "Updating .env file with missing option : JVB_TCP_PORT"
|
checkSuccess "Updating .env file with missing option : JVB_TCP_PORT"
|
||||||
|
|
||||||
result=$(cd "$containers_dir$app_name" && runFileOp ./gen-passwords.sh)
|
result=$(cd "$(appDir "$app_name")" && runFileOp ./gen-passwords.sh)
|
||||||
checkSuccess "Running Jitsi Meet gen-passwords.sh script"
|
checkSuccess "Running Jitsi Meet gen-passwords.sh script"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,10 +91,10 @@ jitsimeet_install_post_start()
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
local result
|
local result
|
||||||
result=$(runFileOp sed -i "s|80|$usedport1|g" $containers_dir$app_name/web/rootfs/defaults/default)
|
result=$(runFileOp sed -i "s|80|$usedport1|g" $(appDir "$app_name")/web/rootfs/defaults/default)
|
||||||
checkSuccess "Updating NGINX default site port 80 to $usedport1"
|
checkSuccess "Updating NGINX default site port 80 to $usedport1"
|
||||||
|
|
||||||
result=$(runFileOp sed -i "s|443|$usedport2|g" $containers_dir$app_name/web/rootfs/defaults/default)
|
result=$(runFileOp sed -i "s|443|$usedport2|g" $(appDir "$app_name")/web/rootfs/defaults/default)
|
||||||
checkSuccess "Updating NGINX default site port 443 to $usedport2"
|
checkSuccess "Updating NGINX default site port 443 to $usedport2"
|
||||||
|
|
||||||
dockerComposeRestart $app_name
|
dockerComposeRestart $app_name
|
||||||
|
|||||||
@ -129,7 +129,7 @@ installLibrePortal()
|
|||||||
if [[ "$libreportal_bootstrap_install" != "true" ]]; then
|
if [[ "$libreportal_bootstrap_install" != "true" ]]; then
|
||||||
((menu_number++))
|
((menu_number++))
|
||||||
echo ""
|
echo ""
|
||||||
echo "---- $menu_number. You can find $app_name files at $containers_dir$app_name"
|
echo "---- $menu_number. You can find $app_name files at $(appDir "$app_name")"
|
||||||
echo ""
|
echo ""
|
||||||
echo " You can now navigate to your new service using one of the options below : "
|
echo " You can now navigate to your new service using one of the options below : "
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@ -9,7 +9,7 @@ libreportal_catalog_install_post_setup()
|
|||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local def="${install_containers_dir%/}/$app_name"
|
local def="${install_containers_dir%/}/$app_name"
|
||||||
local live="$containers_dir$app_name"
|
local live="$(appDir "$app_name")"
|
||||||
local dest="$live/data"
|
local dest="$live/data"
|
||||||
|
|
||||||
# The compose bind-mounts nginx.conf as a FILE; the generic installer
|
# The compose bind-mounts nginx.conf as a FILE; the generic installer
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
mastodon_install_post_setup()
|
mastodon_install_post_setup()
|
||||||
{
|
{
|
||||||
local app_name="${1:-mastodon}"
|
local app_name="${1:-mastodon}"
|
||||||
local config_file="${containers_dir}${app_name}/${app_name}.config"
|
local config_file="$(appDir "$app_name")/${app_name}.config"
|
||||||
[[ -f "$config_file" ]] || return 0
|
[[ -f "$config_file" ]] || return 0
|
||||||
|
|
||||||
# Keep a pair that already has the right shape. The length test is also what
|
# Keep a pair that already has the right shape. The length test is also what
|
||||||
|
|||||||
@ -48,7 +48,7 @@ _matrixApi() {
|
|||||||
local script="$1"; shift
|
local script="$1"; shift
|
||||||
local admin_user="${CFG_MATRIX_ADMIN_USERNAME:-admin}"
|
local admin_user="${CFG_MATRIX_ADMIN_USERNAME:-admin}"
|
||||||
local admin_pass="${CFG_MATRIX_ADMIN_PASSWORD_1}"
|
local admin_pass="${CFG_MATRIX_ADMIN_PASSWORD_1}"
|
||||||
local cache="${containers_dir}matrix/${_matrix_token_cache}"
|
local cache="$(appDir matrix)/${_matrix_token_cache}"
|
||||||
|
|
||||||
if [[ -z "$admin_pass" || "$admin_pass" == RANDOMIZEDPASSWORD* ]]; then
|
if [[ -z "$admin_pass" || "$admin_pass" == RANDOMIZEDPASSWORD* ]]; then
|
||||||
isError "No Matrix admin password in matrix.config — cannot authenticate to the admin API."
|
isError "No Matrix admin password in matrix.config — cannot authenticate to the admin API."
|
||||||
|
|||||||
@ -66,11 +66,11 @@ _matrixServerName()
|
|||||||
_matrixPublicBaseUrl()
|
_matrixPublicBaseUrl()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local compose="$containers_dir$app_name/docker-compose.yml"
|
local compose="$(appDir "$app_name")/docker-compose.yml"
|
||||||
|
|
||||||
# Real HTTPS only when Traefik is actually installed AND a domain exists;
|
# Real HTTPS only when Traefik is actually installed AND a domain exists;
|
||||||
# otherwise the router in the compose is decorative.
|
# otherwise the router in the compose is decorative.
|
||||||
if [[ -d "${containers_dir}traefik" && -n "$domain_full" ]]; then
|
if [[ -d "$(appDir traefik)" && -n "$domain_full" ]]; then
|
||||||
local host
|
local host
|
||||||
host=$(tagsManagerGetTagContent "$compose" "DOMAINSUBNAME_TAG_1")
|
host=$(tagsManagerGetTagContent "$compose" "DOMAINSUBNAME_TAG_1")
|
||||||
if [[ -n "$host" && "$host" != DOMAINSUBNAME_DATA* ]]; then
|
if [[ -n "$host" && "$host" != DOMAINSUBNAME_DATA* ]]; then
|
||||||
@ -94,7 +94,7 @@ _matrixPublicBaseUrl()
|
|||||||
matrix_install_post_compose()
|
matrix_install_post_compose()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local app_dir="$containers_dir$app_name"
|
local app_dir="$(appDir "$app_name")"
|
||||||
local data_dir="$app_dir/data"
|
local data_dir="$app_dir/data"
|
||||||
|
|
||||||
((menu_number++))
|
((menu_number++))
|
||||||
@ -254,7 +254,7 @@ matrix_install_post_compose()
|
|||||||
matrix_install_post_start()
|
matrix_install_post_start()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local app_dir="$containers_dir$app_name"
|
local app_dir="$(appDir "$app_name")"
|
||||||
|
|
||||||
((menu_number++))
|
((menu_number++))
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@ -28,7 +28,7 @@ moneyapp_install_post_compose()
|
|||||||
# * https://<host>.<domain> when traefik is on AND a real domain
|
# * https://<host>.<domain> when traefik is on AND a real domain
|
||||||
# is configured (CFG_DOMAIN_<n>)
|
# is configured (CFG_DOMAIN_<n>)
|
||||||
# * http://<server-ip>:<port> otherwise (raw port exposure)
|
# * http://<server-ip>:<port> otherwise (raw port exposure)
|
||||||
local moneyapp_compose_file="$containers_dir$app_name/docker-compose.yml"
|
local moneyapp_compose_file="$(appDir "$app_name")/docker-compose.yml"
|
||||||
local moneyapp_auth_url=""
|
local moneyapp_auth_url=""
|
||||||
# host_setup is built as `<app>.<domain>` — when CFG_DOMAIN_<n> is
|
# host_setup is built as `<app>.<domain>` — when CFG_DOMAIN_<n> is
|
||||||
# empty it ends up as `<app>.`, a valid-but-broken hostname. Reject
|
# empty it ends up as `<app>.`, a valid-but-broken hostname. Reject
|
||||||
|
|||||||
@ -25,9 +25,9 @@ nextcloud_install_post_compose()
|
|||||||
# Clear a stub directory left behind by any previous attempt, otherwise the
|
# Clear a stub directory left behind by any previous attempt, otherwise the
|
||||||
# copy lands INSIDE it (resources/nginx.conf/nginx.conf) and the mount fails
|
# copy lands INSIDE it (resources/nginx.conf/nginx.conf) and the mount fails
|
||||||
# exactly as before. Shared helper — same repair the WebUI's own config uses.
|
# exactly as before. Shared helper — same repair the WebUI's own config uses.
|
||||||
repairStubDirForFile "$containers_dir$app_name/resources/nginx.conf" "loud"
|
repairStubDirForFile "$(appDir "$app_name")/resources/nginx.conf" "loud"
|
||||||
|
|
||||||
local result
|
local result
|
||||||
result=$(copyResource "$app_name" "nginx.conf" "resources" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
result=$(copyResource "$app_name" "nginx.conf" "resources" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
||||||
checkSuccess "Copying nginx.conf to $containers_dir$app_name/resources"
|
checkSuccess "Copying nginx.conf to $(appDir "$app_name")/resources"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ onlyoffice_install_message_data()
|
|||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local username password
|
local username password
|
||||||
username=$(grep -oP 'username=\K[^ ]+' "$containers_dir$app_name/docker-compose.yml" 2>/dev/null)
|
username=$(grep -oP 'username=\K[^ ]+' "$(appDir "$app_name")/docker-compose.yml" 2>/dev/null)
|
||||||
password=$(grep -oP 'password=\K[^ ]+' "$containers_dir$app_name/docker-compose.yml" 2>/dev/null)
|
password=$(grep -oP 'password=\K[^ ]+' "$(appDir "$app_name")/docker-compose.yml" 2>/dev/null)
|
||||||
echo "$username $password"
|
echo "$username $password"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ owncloud_install_post_compose()
|
|||||||
isNotice "Failed to retrieve the web page."
|
isNotice "Failed to retrieve the web page."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local file_path="$containers_dir$app_name/docker-compose.yml"
|
local file_path="$(appDir "$app_name")/docker-compose.yml"
|
||||||
local file_name="docker-compose.yml"
|
local file_name="docker-compose.yml"
|
||||||
local result
|
local result
|
||||||
result=$(runFileOp sed -i \
|
result=$(runFileOp sed -i \
|
||||||
|
|||||||
@ -9,10 +9,10 @@ prometheus_install_post_compose()
|
|||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
|
|
||||||
local result
|
local result
|
||||||
result=$(createFolders "loud" $docker_install_user "$containers_dir$app_name/$app_name")
|
result=$(createFolders "loud" $docker_install_user "$(appDir "$app_name")/$app_name")
|
||||||
checkSuccess "Created $app_name folder in $app_name"
|
checkSuccess "Created $app_name folder in $app_name"
|
||||||
|
|
||||||
result=$(createTouch "$containers_dir$app_name/$app_name/$app_name.yml" $docker_install_user)
|
result=$(createTouch "$(appDir "$app_name")/$app_name/$app_name.yml" $docker_install_user)
|
||||||
checkSuccess "Created $app_name.yml file for $app_name"
|
checkSuccess "Created $app_name.yml file for $app_name"
|
||||||
|
|
||||||
result=$(copyResource "$app_name" "$app_name.yml" "$app_name" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
result=$(copyResource "$app_name" "$app_name.yml" "$app_name" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
||||||
@ -23,8 +23,8 @@ prometheus_install_post_start()
|
|||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
|
|
||||||
if [ -f "${containers_dir}prometheus/prometheus/prometheus.yml" ]; then
|
if [ -f "$(appDir prometheus)/prometheus/prometheus.yml" ]; then
|
||||||
updateFileOwnership "${containers_dir}prometheus/prometheus/prometheus.yml" $docker_install_user $docker_install_user
|
updateFileOwnership "$(appDir prometheus)/prometheus/prometheus.yml" $docker_install_user $docker_install_user
|
||||||
fi
|
fi
|
||||||
# Prometheus runs as nobody (65534) inside the container, which rootless maps
|
# Prometheus runs as nobody (65534) inside the container, which rootless maps
|
||||||
# to a subuid outside this user's authority (65534 -> 296605 here). Two
|
# to a subuid outside this user's authority (65534 -> 296605 here). Two
|
||||||
@ -48,14 +48,14 @@ prometheus_install_post_start()
|
|||||||
# every REINSTALL (a fresh install passed only because the dir
|
# every REINSTALL (a fresh install passed only because the dir
|
||||||
# was still empty). Those files must keep prometheus's ownership
|
# was still empty). Those files must keep prometheus's ownership
|
||||||
# anyway — that is what lets it write them.
|
# anyway — that is what lets it write them.
|
||||||
if [ -d "${containers_dir}prometheus/prometheus" ]; then
|
if [ -d "$(appDir prometheus)/prometheus" ]; then
|
||||||
local result
|
local result
|
||||||
result=$(runFileOp chmod -R a+rX,go-w "${containers_dir}prometheus/prometheus")
|
result=$(runFileOp chmod -R a+rX,go-w "$(appDir prometheus)/prometheus")
|
||||||
checkSuccess "Set permissions to prometheus folder."
|
checkSuccess "Set permissions to prometheus folder."
|
||||||
fi
|
fi
|
||||||
if [ -d "${containers_dir}prometheus/prom_data" ]; then
|
if [ -d "$(appDir prometheus)/prom_data" ]; then
|
||||||
local result
|
local result
|
||||||
result=$(runFileOp chmod 0777 "${containers_dir}prometheus/prom_data")
|
result=$(runFileOp chmod 0777 "$(appDir prometheus)/prom_data")
|
||||||
checkSuccess "Set permissions to prom_data folder."
|
checkSuccess "Set permissions to prom_data folder."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
# way round needing a real account here.
|
# way round needing a real account here.
|
||||||
|
|
||||||
_rocketchatBaseUrl() {
|
_rocketchatBaseUrl() {
|
||||||
local compose="${containers_dir}rocketchat/docker-compose.yml"
|
local compose="$(appDir rocketchat)/docker-compose.yml"
|
||||||
local url
|
local url
|
||||||
url=$(runFileOp grep -oP '^\s*-\s*ROOT_URL=\K\S+' "$compose" 2>/dev/null | head -1)
|
url=$(runFileOp grep -oP '^\s*-\s*ROOT_URL=\K\S+' "$compose" 2>/dev/null | head -1)
|
||||||
url="${url%%#*}"
|
url="${url%%#*}"
|
||||||
|
|||||||
@ -6,7 +6,7 @@ searxng_install_post_start()
|
|||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
|
|
||||||
local searxng_settings="$containers_dir$app_name/searxng-data/settings.yml"
|
local searxng_settings="$(appDir "$app_name")/searxng-data/settings.yml"
|
||||||
local searxng_timeout=10
|
local searxng_timeout=10
|
||||||
local searxng_counter=0
|
local searxng_counter=0
|
||||||
while [ ! -f "$searxng_settings" ]; do
|
while [ ! -f "$searxng_settings" ]; do
|
||||||
|
|||||||
@ -47,7 +47,7 @@ stalwart_mode()
|
|||||||
{
|
{
|
||||||
local mode="${CFG_STALWART_MODE:-auto}"
|
local mode="${CFG_STALWART_MODE:-auto}"
|
||||||
if [[ "$mode" == "auto" ]]; then
|
if [[ "$mode" == "auto" ]]; then
|
||||||
if [[ -d "${containers_dir}traefik" ]]; then mode="public"; else mode="private"; fi
|
if [[ -d "$(appDir traefik)" ]]; then mode="public"; else mode="private"; fi
|
||||||
fi
|
fi
|
||||||
[[ "$mode" == "public" || "$mode" == "private" ]] || mode="private"
|
[[ "$mode" == "public" || "$mode" == "private" ]] || mode="private"
|
||||||
printf '%s' "$mode"
|
printf '%s' "$mode"
|
||||||
@ -105,7 +105,7 @@ stalwart_apply_port_access()
|
|||||||
stalwart_install_post_setup()
|
stalwart_install_post_setup()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local cfg="$containers_dir$app_name/$app_name.config"
|
local cfg="$(appDir "$app_name")/$app_name.config"
|
||||||
[[ -f "$cfg" ]] || return 0
|
[[ -f "$cfg" ]] || return 0
|
||||||
|
|
||||||
local mode; mode=$(stalwart_mode)
|
local mode; mode=$(stalwart_mode)
|
||||||
@ -223,7 +223,7 @@ stalwart_install_provision()
|
|||||||
# the only route left is DNS-01 — which needs the DNS provider integration.
|
# the only route left is DNS-01 — which needs the DNS provider integration.
|
||||||
# Without it mail clients get a self-signed certificate on 993 and no amount
|
# Without it mail clients get a self-signed certificate on 993 and no amount
|
||||||
# of waiting fixes it.
|
# of waiting fixes it.
|
||||||
if [[ "$mode" == "public" && -d "${containers_dir}traefik" \
|
if [[ "$mode" == "public" && -d "$(appDir traefik)" \
|
||||||
&& ( "${CFG_STALWART_DNS_PROVIDER:-manual}" == "manual" || -z "${CFG_STALWART_DNS_API_TOKEN:-}" ) ]]; then
|
&& ( "${CFG_STALWART_DNS_PROVIDER:-manual}" == "manual" || -z "${CFG_STALWART_DNS_API_TOKEN:-}" ) ]]; then
|
||||||
isNotice "Traefik owns ports 80 and 443, so Stalwart cannot validate a certificate"
|
isNotice "Traefik owns ports 80 and 443, so Stalwart cannot validate a certificate"
|
||||||
isNotice " for its mail ports on its own. Set CFG_STALWART_DNS_PROVIDER and a token"
|
isNotice " for its mail ports on its own. Set CFG_STALWART_DNS_PROVIDER and a token"
|
||||||
@ -351,7 +351,7 @@ stalwart_install_post_start()
|
|||||||
# Resolved admin port comes from the compose tag (format `external:internal`),
|
# Resolved admin port comes from the compose tag (format `external:internal`),
|
||||||
# the same source adguard's hook reads — the legacy $usedport1 isn't populated
|
# the same source adguard's hook reads — the legacy $usedport1 isn't populated
|
||||||
# by the current install pipeline.
|
# by the current install pipeline.
|
||||||
local compose_file="$containers_dir$app_name/docker-compose.yml"
|
local compose_file="$(appDir "$app_name")/docker-compose.yml"
|
||||||
local admin_pair admin_port
|
local admin_pair admin_port
|
||||||
admin_pair=$(tagsManagerGetTagContent "$compose_file" "PORTS_TAG_1" 2>/dev/null)
|
admin_pair=$(tagsManagerGetTagContent "$compose_file" "PORTS_TAG_1" 2>/dev/null)
|
||||||
admin_port="${admin_pair%%:*}"
|
admin_port="${admin_pair%%:*}"
|
||||||
@ -440,7 +440,7 @@ stalwart_install_post_start()
|
|||||||
echo ""
|
echo ""
|
||||||
isNotice "Point mail and calendar clients at: ${mail_host}"
|
isNotice "Point mail and calendar clients at: ${mail_host}"
|
||||||
isNotice " Certificates are self-signed, so clients will ask you to trust it once."
|
isNotice " Certificates are self-signed, so clients will ask you to trust it once."
|
||||||
[[ -d "${containers_dir}headscale" ]] && \
|
[[ -d "$(appDir headscale)" ]] && \
|
||||||
isNotice " Headscale is installed, so this also reaches you from anywhere on your tailnet."
|
isNotice " Headscale is installed, so this also reaches you from anywhere on your tailnet."
|
||||||
echo ""
|
echo ""
|
||||||
isNotice "To exchange mail with the internet later, switch this app to public"
|
isNotice "To exchange mail with the internet later, switch this app to public"
|
||||||
|
|||||||
@ -27,7 +27,7 @@ appStalwartSetMode() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local cfg="${containers_dir}stalwart/stalwart.config"
|
local cfg="$(appDir stalwart)/stalwart.config"
|
||||||
if [[ ! -f "$cfg" ]]; then
|
if [[ ! -f "$cfg" ]]; then
|
||||||
isError "Stalwart does not look installed — no config at $cfg"
|
isError "Stalwart does not look installed — no config at $cfg"
|
||||||
return 1
|
return 1
|
||||||
@ -96,7 +96,7 @@ appStalwartSetMode() {
|
|||||||
plan=$(printf '{"@type":"upsert","object":"AcmeProvider","matchOn":["directory"],"value":{"acme":{"directory":"https://acme-v02.api.letsencrypt.org/directory","contact":{"%s":true},"challengeType":"%s"}}}\n{"@type":"update","object":"Domain","id":"%s","value":{"certificateManagement":{"@type":"Automatic","acmeProviderId":"#acme"}}}' \
|
plan=$(printf '{"@type":"upsert","object":"AcmeProvider","matchOn":["directory"],"value":{"acme":{"directory":"https://acme-v02.api.letsencrypt.org/directory","contact":{"%s":true},"challengeType":"%s"}}}\n{"@type":"update","object":"Domain","id":"%s","value":{"certificateManagement":{"@type":"Automatic","acmeProviderId":"#acme"}}}' \
|
||||||
"$contact" "$challenge" "$domain_id")
|
"$contact" "$challenge" "$domain_id")
|
||||||
|
|
||||||
if [[ "$challenge" == "TlsAlpn01" && -d "${containers_dir}traefik" ]]; then
|
if [[ "$challenge" == "TlsAlpn01" && -d "$(appDir traefik)" ]]; then
|
||||||
isNotice "Traefik holds ports 80 and 443, so this certificate request will not"
|
isNotice "Traefik holds ports 80 and 443, so this certificate request will not"
|
||||||
isNotice " validate. Set CFG_STALWART_DNS_PROVIDER and a token to validate over"
|
isNotice " validate. Set CFG_STALWART_DNS_PROVIDER and a token to validate over"
|
||||||
isNotice " DNS instead — that is the only route that works alongside Traefik."
|
isNotice " DNS instead — that is the only route that works alongside Traefik."
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
# advertised URL happens to name.
|
# advertised URL happens to name.
|
||||||
_stoatApiLocal() {
|
_stoatApiLocal() {
|
||||||
local app_name="${1:-stoat}"
|
local app_name="${1:-stoat}"
|
||||||
local compose="$containers_dir$app_name/docker-compose.yml"
|
local compose="$(appDir "$app_name")/docker-compose.yml"
|
||||||
local ports external
|
local ports external
|
||||||
ports=$(tagsManagerGetTagContent "$compose" "PORTS_TAG_1" 2>/dev/null)
|
ports=$(tagsManagerGetTagContent "$compose" "PORTS_TAG_1" 2>/dev/null)
|
||||||
external="${ports%%:*}"
|
external="${ports%%:*}"
|
||||||
|
|||||||
@ -55,9 +55,9 @@ _stoatDomain()
|
|||||||
_stoatBaseUrl()
|
_stoatBaseUrl()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local compose="$containers_dir$app_name/docker-compose.yml"
|
local compose="$(appDir "$app_name")/docker-compose.yml"
|
||||||
|
|
||||||
if [[ -d "${containers_dir}traefik" && -n "$domain_full" ]]; then
|
if [[ -d "$(appDir traefik)" && -n "$domain_full" ]]; then
|
||||||
local host
|
local host
|
||||||
host=$(_stoatDomain)
|
host=$(_stoatDomain)
|
||||||
[[ -n "$host" ]] && { echo "https://${host}"; return 0; }
|
[[ -n "$host" ]] && { echo "https://${host}"; return 0; }
|
||||||
@ -226,7 +226,7 @@ _stoatOwnConfigFiles() {
|
|||||||
stoat_install_post_compose()
|
stoat_install_post_compose()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local app_dir="$containers_dir$app_name"
|
local app_dir="$(appDir "$app_name")"
|
||||||
|
|
||||||
((menu_number++))
|
((menu_number++))
|
||||||
echo ""
|
echo ""
|
||||||
@ -310,7 +310,7 @@ EOF
|
|||||||
stoat_install_post_start()
|
stoat_install_post_start()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local app_dir="$containers_dir$app_name"
|
local app_dir="$(appDir "$app_name")"
|
||||||
|
|
||||||
# Ports are assigned during compose-up, so on a domain-less install the URL
|
# Ports are assigned during compose-up, so on a domain-less install the URL
|
||||||
# baked in a moment ago was a guess. Correct it now and restart, but only if
|
# baked in a moment ago was a guess. Correct it now and restart, but only if
|
||||||
|
|||||||
@ -31,13 +31,13 @@ traefik_install_post_compose()
|
|||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
|
|
||||||
local result
|
local result
|
||||||
result=$(createFolders "loud" $docker_install_user "$containers_dir$app_name/etc" "$containers_dir$app_name/etc/certs" "$containers_dir$app_name/etc/dynamic" "$containers_dir$app_name/etc/dynamic/middlewears")
|
result=$(createFolders "loud" $docker_install_user "$(appDir "$app_name")/etc" "$(appDir "$app_name")/etc/certs" "$(appDir "$app_name")/etc/dynamic" "$(appDir "$app_name")/etc/dynamic/middlewears")
|
||||||
checkSuccess "Created etc and certs & dynamic Directories"
|
checkSuccess "Created etc and certs & dynamic Directories"
|
||||||
|
|
||||||
result=$(copyResource "$app_name" "traefik.yml" "etc")
|
result=$(copyResource "$app_name" "traefik.yml" "etc")
|
||||||
checkSuccess "Copy Traefik configuration file for $app_name"
|
checkSuccess "Copy Traefik configuration file for $app_name"
|
||||||
|
|
||||||
result=$(runFileOp sed -i "s|DEBUGLEVEL|$CFG_TRAEFIK_LOGGING|g" "$containers_dir$app_name/etc/traefik.yml")
|
result=$(runFileOp sed -i "s|DEBUGLEVEL|$CFG_TRAEFIK_LOGGING|g" "$(appDir "$app_name")/etc/traefik.yml")
|
||||||
checkSuccess "Configured Traefik debug level with: $CFG_TRAEFIK_LOGGING for $app_name"
|
checkSuccess "Configured Traefik debug level with: $CFG_TRAEFIK_LOGGING for $app_name"
|
||||||
|
|
||||||
configSetupFileWithData $app_name "traefik.yml" "etc"
|
configSetupFileWithData $app_name "traefik.yml" "etc"
|
||||||
@ -45,8 +45,8 @@ traefik_install_post_compose()
|
|||||||
# Apply CFG_TRAEFIK_DASHBOARD_ACCESS: rewrites api.insecure + the
|
# Apply CFG_TRAEFIK_DASHBOARD_ACCESS: rewrites api.insecure + the
|
||||||
# `traefik:` entrypoint in traefik.yml, and (for local-only) prefixes
|
# `traefik:` entrypoint in traefik.yml, and (for local-only) prefixes
|
||||||
# the compose port mapping with 127.0.0.1: so :8080 binds to loopback.
|
# the compose port mapping with 127.0.0.1: so :8080 binds to loopback.
|
||||||
local traefik_yml="$containers_dir$app_name/etc/traefik.yml"
|
local traefik_yml="$(appDir "$app_name")/etc/traefik.yml"
|
||||||
local compose_yml="$containers_dir$app_name/docker-compose.yml"
|
local compose_yml="$(appDir "$app_name")/docker-compose.yml"
|
||||||
local access="${CFG_TRAEFIK_DASHBOARD_ACCESS:-local-only}"
|
local access="${CFG_TRAEFIK_DASHBOARD_ACCESS:-local-only}"
|
||||||
|
|
||||||
case "$access" in
|
case "$access" in
|
||||||
@ -79,7 +79,7 @@ traefik_install_post_compose()
|
|||||||
result=$(copyResource "$app_name" "config.yml" "etc/dynamic")
|
result=$(copyResource "$app_name" "config.yml" "etc/dynamic")
|
||||||
checkSuccess "Copy Traefik Dynamic config.yml configuration file for $app_name"
|
checkSuccess "Copy Traefik Dynamic config.yml configuration file for $app_name"
|
||||||
|
|
||||||
result=$(runFileOp sed -i "s|ERRORWEBSITE|$CFG_TRAEFIK_404_SITE|g" "$containers_dir$app_name/etc/dynamic/config.yml")
|
result=$(runFileOp sed -i "s|ERRORWEBSITE|$CFG_TRAEFIK_404_SITE|g" "$(appDir "$app_name")/etc/dynamic/config.yml")
|
||||||
checkSuccess "Configured Traefik error website with URL: $CFG_TRAEFIK_404_SITE for $app_name"
|
checkSuccess "Configured Traefik error website with URL: $CFG_TRAEFIK_404_SITE for $app_name"
|
||||||
|
|
||||||
configSetupFileWithData $app_name "config.yml" "etc/dynamic"
|
configSetupFileWithData $app_name "config.yml" "etc/dynamic"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ trilium_install_post_start()
|
|||||||
|
|
||||||
local trilium_timeout=10
|
local trilium_timeout=10
|
||||||
local trilium_counter=0
|
local trilium_counter=0
|
||||||
while [ ! -f "$containers_dir$app_name/trilium-data/config.ini" ]; do
|
while [ ! -f "$(appDir "$app_name")/trilium-data/config.ini" ]; do
|
||||||
if [ "$trilium_counter" -ge "$trilium_timeout" ]; then
|
if [ "$trilium_counter" -ge "$trilium_timeout" ]; then
|
||||||
isNotice "File not found after 10 seconds. Exiting..."
|
isNotice "File not found after 10 seconds. Exiting..."
|
||||||
return 0
|
return 0
|
||||||
@ -24,7 +24,7 @@ trilium_install_post_start()
|
|||||||
done
|
done
|
||||||
|
|
||||||
local result
|
local result
|
||||||
result=$(runFileOp sed -i "s|port=8080|port=$usedport1|g" "$containers_dir$app_name/trilium-data/config.ini")
|
result=$(runFileOp sed -i "s|port=8080|port=$usedport1|g" "$(appDir "$app_name")/trilium-data/config.ini")
|
||||||
checkSuccess "Configured $app_name from default 8080 to $usedport1"
|
checkSuccess "Configured $app_name from default 8080 to $usedport1"
|
||||||
|
|
||||||
dockerComposeRestart $app_name
|
dockerComposeRestart $app_name
|
||||||
|
|||||||
@ -16,11 +16,11 @@ unbound_install_post_compose()
|
|||||||
# A previous attempt that started the container before this file existed
|
# A previous attempt that started the container before this file existed
|
||||||
# leaves conf.d as a Docker-created stub, and the copy below would then land
|
# leaves conf.d as a Docker-created stub, and the copy below would then land
|
||||||
# inside it. Same failure that kept Nextcloud's nginx from starting.
|
# inside it. Same failure that kept Nextcloud's nginx from starting.
|
||||||
repairStubDirForFile "$containers_dir$app_name/conf.d/libreportal.conf" "loud"
|
repairStubDirForFile "$(appDir "$app_name")/conf.d/libreportal.conf" "loud"
|
||||||
|
|
||||||
local result
|
local result
|
||||||
result=$(copyResource "$app_name" "libreportal.conf" "conf.d" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
result=$(copyResource "$app_name" "libreportal.conf" "conf.d" | runInstallWrite -a "$logs_dir/$docker_log_file" 2>&1)
|
||||||
checkSuccess "Copying libreportal.conf to $containers_dir$app_name/conf.d"
|
checkSuccess "Copying libreportal.conf to $(appDir "$app_name")/conf.d"
|
||||||
|
|
||||||
monitoringToggleAppConfig "$app_name" "conf.d/libreportal.conf"
|
monitoringToggleAppConfig "$app_name" "conf.d/libreportal.conf"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ wireguard_install_post_compose()
|
|||||||
# outside the docker network generator range (100-149), so the two
|
# outside the docker network generator range (100-149), so the two
|
||||||
# subnets cannot collide. Reusing the persisted value keeps existing
|
# subnets cannot collide. Reusing the persisted value keeps existing
|
||||||
# peer configs valid across reinstalls.
|
# peer configs valid across reinstalls.
|
||||||
local _wg_compose_for_subnet="$containers_dir$app_name/docker-compose.yml"
|
local _wg_compose_for_subnet="$(appDir "$app_name")/docker-compose.yml"
|
||||||
if [[ -z "$CFG_WIREGUARD_SUBNET" ]]; then
|
if [[ -z "$CFG_WIREGUARD_SUBNET" ]]; then
|
||||||
local _wg_second=$(( RANDOM % 51 + 200 ))
|
local _wg_second=$(( RANDOM % 51 + 200 ))
|
||||||
local _wg_third=$(( RANDOM % 256 ))
|
local _wg_third=$(( RANDOM % 256 ))
|
||||||
@ -57,7 +57,7 @@ wireguard_install_post_compose()
|
|||||||
echo "---- $menu_number. Resolving WG_HOST for peer configs"
|
echo "---- $menu_number. Resolving WG_HOST for peer configs"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
local wg_compose_file="$containers_dir$app_name/docker-compose.yml"
|
local wg_compose_file="$(appDir "$app_name")/docker-compose.yml"
|
||||||
local wg_host_value=""
|
local wg_host_value=""
|
||||||
local _wg_traefik_installed=0
|
local _wg_traefik_installed=0
|
||||||
if declare -f checkServiceInstalled >/dev/null 2>&1 && checkServiceInstalled "traefik"; then
|
if declare -f checkServiceInstalled >/dev/null 2>&1 && checkServiceInstalled "traefik"; then
|
||||||
|
|||||||
@ -7,9 +7,9 @@ appGetKeyData()
|
|||||||
local key="$3"
|
local key="$3"
|
||||||
|
|
||||||
# Check if the file exists
|
# Check if the file exists
|
||||||
if [ -f "$containers_dir$app_name/$file_path" ]; then
|
if [ -f "$(appDir "$app_name")/$file_path" ]; then
|
||||||
# Extract the line containing the key
|
# Extract the line containing the key
|
||||||
local key_line=$(grep "^$key=" "$containers_dir$app_name/$file_path")
|
local key_line=$(grep "^$key=" "$(appDir "$app_name")/$file_path")
|
||||||
|
|
||||||
# Extract the value using cut or awk
|
# Extract the value using cut or awk
|
||||||
local value=$(echo "$key_line" | cut -d '=' -f 2)
|
local value=$(echo "$key_line" | cut -d '=' -f 2)
|
||||||
|
|||||||
@ -13,7 +13,7 @@ appStatus()
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local app_dir="$containers_dir/$app_name"
|
local app_dir="$(appDir "$app_name")"
|
||||||
local compose_file="$app_dir/docker-compose.yml"
|
local compose_file="$app_dir/docker-compose.yml"
|
||||||
|
|
||||||
if [[ ! -f "$compose_file" ]]; then
|
if [[ ! -f "$compose_file" ]]; then
|
||||||
|
|||||||
@ -52,7 +52,7 @@ authAdapterCall() {
|
|||||||
# on showing the old one. Hence the explicit return 1 and warning below.
|
# on showing the old one. Hence the explicit return 1 and warning below.
|
||||||
authPersistCfg() {
|
authPersistCfg() {
|
||||||
local app="$1" key="$2" value="$3"
|
local app="$1" key="$2" value="$3"
|
||||||
local cfg="${containers_dir}${app}/${app}.config"
|
local cfg="$(appDir "$app")/${app}.config"
|
||||||
[[ ! -f "$cfg" ]] && cfg="${install_containers_dir}/${app}/${app}.config"
|
[[ ! -f "$cfg" ]] && cfg="${install_containers_dir}/${app}/${app}.config"
|
||||||
[[ ! -f "$cfg" ]] && return 1
|
[[ ! -f "$cfg" ]] && return 1
|
||||||
|
|
||||||
|
|||||||
@ -212,7 +212,7 @@ installApp()
|
|||||||
|
|
||||||
((menu_number++))
|
((menu_number++))
|
||||||
echo ""
|
echo ""
|
||||||
echo "---- $menu_number. You can find $app_name files at $containers_dir$app_name"
|
echo "---- $menu_number. You can find $app_name files at $(appDir "$app_name")"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Final-message data — apps that want extra args (creds, URLs, etc.)
|
# Final-message data — apps that want extra args (creds, URLs, etc.)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ backupAppRunHook()
|
|||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local phase="$2"
|
local phase="$2"
|
||||||
local hook="$containers_dir$app_name/backup.${phase}.hook.sh"
|
local hook="$(appDir "$app_name")/backup.${phase}.hook.sh"
|
||||||
|
|
||||||
[[ ! -f "$hook" ]] && return 0
|
[[ ! -f "$hook" ]] && return 0
|
||||||
|
|
||||||
|
|||||||
@ -10,8 +10,8 @@ backupAppStart()
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -d "$containers_dir$app_name" ]]; then
|
if [[ ! -d "$(appDir "$app_name")" ]]; then
|
||||||
isError "Cannot back up '$app_name' — not installed at $containers_dir$app_name"
|
isError "Cannot back up '$app_name' — not installed at $(appDir "$app_name")"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ backupAppStart()
|
|||||||
isNotice "Live strategy — containers stay running; databases dumped + private files captured via their containers"
|
isNotice "Live strategy — containers stay running; databases dumped + private files captured via their containers"
|
||||||
if ! backupDbDump "$stored_app_name" || ! backupFilesCapture "$stored_app_name"; then
|
if ! backupDbDump "$stored_app_name" || ! backupFilesCapture "$stored_app_name"; then
|
||||||
isError "Live capture failed — falling back to stop-snapshot-start for safety"
|
isError "Live capture failed — falling back to stop-snapshot-start for safety"
|
||||||
runFileOp rm -rf "${containers_dir:?}$stored_app_name/.lp-backup"
|
runFileOp rm -rf "$(appDir "$stored_app_name")/.lp-backup"
|
||||||
strategy="stop-snapshot-start"
|
strategy="stop-snapshot-start"
|
||||||
dockerComposeDown "$stored_app_name"
|
dockerComposeDown "$stored_app_name"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -43,7 +43,7 @@ backup_db_dump_subdir=".lp-backup/db"
|
|||||||
backupDbDescriptors()
|
backupDbDescriptors()
|
||||||
{
|
{
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local compose="$containers_dir$app/docker-compose.yml"
|
local compose="$(appDir "$app")/docker-compose.yml"
|
||||||
[[ -f "$compose" ]] || return 0
|
[[ -f "$compose" ]] || return 0
|
||||||
|
|
||||||
grep -E '^[[:space:]]*libreportal\.backup\.db[[:space:]]*:' "$compose" 2>/dev/null \
|
grep -E '^[[:space:]]*libreportal\.backup\.db[[:space:]]*:' "$compose" 2>/dev/null \
|
||||||
@ -65,7 +65,7 @@ backupDbHasDescriptors()
|
|||||||
backupAppIsLiveSafe()
|
backupAppIsLiveSafe()
|
||||||
{
|
{
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local compose="$containers_dir$app/docker-compose.yml"
|
local compose="$(appDir "$app")/docker-compose.yml"
|
||||||
[[ -f "$compose" ]] || return 1
|
[[ -f "$compose" ]] || return 1
|
||||||
if grep -qE '^[[:space:]]*libreportal\.backup\.live[[:space:]]*:[[:space:]]*["'\'']?true' "$compose" 2>/dev/null; then
|
if grep -qE '^[[:space:]]*libreportal\.backup\.live[[:space:]]*:[[:space:]]*["'\'']?true' "$compose" 2>/dev/null; then
|
||||||
return 0
|
return 0
|
||||||
@ -204,7 +204,7 @@ _backupDbImport()
|
|||||||
backupDbDump()
|
backupDbDump()
|
||||||
{
|
{
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local app_dir="$containers_dir$app"
|
local app_dir="$(appDir "$app")"
|
||||||
local dump_dir="$app_dir/$backup_db_dump_subdir"
|
local dump_dir="$app_dir/$backup_db_dump_subdir"
|
||||||
local desc kind container datadir path dump rc=0
|
local desc kind container datadir path dump rc=0
|
||||||
|
|
||||||
@ -286,7 +286,7 @@ backupDbDump()
|
|||||||
backupDbExcludePaths()
|
backupDbExcludePaths()
|
||||||
{
|
{
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local app_dir="$containers_dir$app"
|
local app_dir="$(appDir "$app")"
|
||||||
local desc kind container datadir path
|
local desc kind container datadir path
|
||||||
|
|
||||||
while IFS= read -r desc; do
|
while IFS= read -r desc; do
|
||||||
@ -315,7 +315,7 @@ backupDbExcludePaths()
|
|||||||
restoreDbRehydratePreStart()
|
restoreDbRehydratePreStart()
|
||||||
{
|
{
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local app_dir="$containers_dir$app"
|
local app_dir="$(appDir "$app")"
|
||||||
local dump_dir="$app_dir/$backup_db_dump_subdir"
|
local dump_dir="$app_dir/$backup_db_dump_subdir"
|
||||||
local desc kind container datadir path dump
|
local desc kind container datadir path dump
|
||||||
|
|
||||||
@ -350,7 +350,7 @@ restoreDbRehydratePreStart()
|
|||||||
restoreDbReplayPostStart()
|
restoreDbReplayPostStart()
|
||||||
{
|
{
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local app_dir="$containers_dir$app"
|
local app_dir="$(appDir "$app")"
|
||||||
local dump_dir="$app_dir/$backup_db_dump_subdir"
|
local dump_dir="$app_dir/$backup_db_dump_subdir"
|
||||||
local desc kind container datadir path dump
|
local desc kind container datadir path dump
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ borgBackupAppToLocation()
|
|||||||
local idx="$1"
|
local idx="$1"
|
||||||
local app_name="$2"
|
local app_name="$2"
|
||||||
local manifest_sha="$3"
|
local manifest_sha="$3"
|
||||||
local source_path="$containers_dir$app_name"
|
local source_path="$(appDir "$app_name")"
|
||||||
|
|
||||||
if [[ ! -d "$source_path" ]]; then
|
if [[ ! -d "$source_path" ]]; then
|
||||||
isError "Source path missing for $app_name: $source_path"
|
isError "Source path missing for $app_name: $source_path"
|
||||||
|
|||||||
@ -5,7 +5,7 @@ kopiaBackupAppToLocation()
|
|||||||
local idx="$1"
|
local idx="$1"
|
||||||
local app_name="$2"
|
local app_name="$2"
|
||||||
local manifest_sha="$3"
|
local manifest_sha="$3"
|
||||||
local source_path="$containers_dir$app_name"
|
local source_path="$(appDir "$app_name")"
|
||||||
|
|
||||||
if [[ ! -d "$source_path" ]]; then
|
if [[ ! -d "$source_path" ]]; then
|
||||||
isError "Source path missing for $app_name: $source_path"
|
isError "Source path missing for $app_name: $source_path"
|
||||||
|
|||||||
@ -23,7 +23,7 @@ kopiaForgetApp()
|
|||||||
|
|
||||||
kopiaEnvExport "$idx" || return 1
|
kopiaEnvExport "$idx" || return 1
|
||||||
|
|
||||||
local src="$containers_dir$app_name"
|
local src="$(appDir "$app_name")"
|
||||||
local host_tag="${CFG_INSTALL_NAME:-libreportal}"
|
local host_tag="${CFG_INSTALL_NAME:-libreportal}"
|
||||||
|
|
||||||
local policy_args=(policy set --global=false "$src")
|
local policy_args=(policy set --global=false "$src")
|
||||||
|
|||||||
@ -5,7 +5,7 @@ resticBackupAppToLocation()
|
|||||||
local idx="$1"
|
local idx="$1"
|
||||||
local app_name="$2"
|
local app_name="$2"
|
||||||
local manifest_sha="$3"
|
local manifest_sha="$3"
|
||||||
local source_path="$containers_dir$app_name"
|
local source_path="$(appDir "$app_name")"
|
||||||
|
|
||||||
if [[ ! -d "$source_path" ]]; then
|
if [[ ! -d "$source_path" ]]; then
|
||||||
isError "Source path missing for $app_name: $source_path"
|
isError "Source path missing for $app_name: $source_path"
|
||||||
|
|||||||
@ -118,7 +118,7 @@ resticRestoreAppLatest()
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local include_path="$containers_dir$app_name"
|
local include_path="$(appDir "$app_name")"
|
||||||
resticRestoreSnapshot "$idx" "$snapshot_id" "$target_dir" "$include_path"
|
resticRestoreSnapshot "$idx" "$snapshot_id" "$target_dir" "$include_path"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -49,7 +49,7 @@ backup_files_helper_image="busybox"
|
|||||||
backupFilesDescriptors()
|
backupFilesDescriptors()
|
||||||
{
|
{
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local compose="$containers_dir$app/docker-compose.yml"
|
local compose="$(appDir "$app")/docker-compose.yml"
|
||||||
[[ -f "$compose" ]] || return 0
|
[[ -f "$compose" ]] || return 0
|
||||||
|
|
||||||
grep -E '^[[:space:]]*libreportal\.backup\.files[[:space:]]*:' "$compose" 2>/dev/null \
|
grep -E '^[[:space:]]*libreportal\.backup\.files[[:space:]]*:' "$compose" 2>/dev/null \
|
||||||
@ -71,7 +71,7 @@ backupFilesHasDescriptors()
|
|||||||
backupFilesCapture()
|
backupFilesCapture()
|
||||||
{
|
{
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local app_dir="$containers_dir$app"
|
local app_dir="$(appDir "$app")"
|
||||||
local desc container cpath subdir uid gid stage rc=0
|
local desc container cpath subdir uid gid stage rc=0
|
||||||
|
|
||||||
backupFilesHasDescriptors "$app" || return 0
|
backupFilesHasDescriptors "$app" || return 0
|
||||||
@ -148,7 +148,7 @@ backupFilesCapture()
|
|||||||
backupFilesExcludePaths()
|
backupFilesExcludePaths()
|
||||||
{
|
{
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local app_dir="$containers_dir$app"
|
local app_dir="$(appDir "$app")"
|
||||||
local desc container cpath subdir uid gid
|
local desc container cpath subdir uid gid
|
||||||
|
|
||||||
while IFS= read -r desc; do
|
while IFS= read -r desc; do
|
||||||
@ -165,7 +165,7 @@ backupFilesExcludePaths()
|
|||||||
restoreFilesRehydratePreStart()
|
restoreFilesRehydratePreStart()
|
||||||
{
|
{
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local app_dir="$containers_dir$app"
|
local app_dir="$(appDir "$app")"
|
||||||
local desc container cpath subdir uid gid stage
|
local desc container cpath subdir uid gid stage
|
||||||
|
|
||||||
backupFilesHasDescriptors "$app" || return 0
|
backupFilesHasDescriptors "$app" || return 0
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
manifestCollect()
|
manifestCollect()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local app_dir="$containers_dir$app_name"
|
local app_dir="$(appDir "$app_name")"
|
||||||
|
|
||||||
local libreportal_commit
|
local libreportal_commit
|
||||||
libreportal_commit=$(git -C "${install_scripts_dir%/scripts/}" rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
libreportal_commit=$(git -C "${install_scripts_dir%/scripts/}" rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||||
|
|||||||
@ -6,7 +6,7 @@ manifestReadFromSnapshot()
|
|||||||
local snapshot_id="$2"
|
local snapshot_id="$2"
|
||||||
local app_name="$3"
|
local app_name="$3"
|
||||||
|
|
||||||
local manifest_path="$containers_dir$app_name/.libreportal-manifest.json"
|
local manifest_path="$(appDir "$app_name")/.libreportal-manifest.json"
|
||||||
engineDumpFile "$repo_name" "$snapshot_id" "$manifest_path" 2>/dev/null
|
engineDumpFile "$repo_name" "$snapshot_id" "$manifest_path" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
manifestWrite()
|
manifestWrite()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local app_dir="$containers_dir$app_name"
|
local app_dir="$(appDir "$app_name")"
|
||||||
local manifest_path="$app_dir/.libreportal-manifest.json"
|
local manifest_path="$app_dir/.libreportal-manifest.json"
|
||||||
|
|
||||||
if [[ ! -d "$app_dir" ]]; then
|
if [[ ! -d "$app_dir" ]]; then
|
||||||
@ -25,6 +25,6 @@ manifestWrite()
|
|||||||
manifestRemove()
|
manifestRemove()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local manifest_path="$containers_dir$app_name/.libreportal-manifest.json"
|
local manifest_path="$(appDir "$app_name")/.libreportal-manifest.json"
|
||||||
[[ -f "$manifest_path" ]] && runFileOp rm -f "$manifest_path"
|
[[ -f "$manifest_path" ]] && runFileOp rm -f "$manifest_path"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ backupVerifySnapshot()
|
|||||||
|
|
||||||
isNotice "Verifying ${snapshot_id:0:8} via scratch restore at $scratch"
|
isNotice "Verifying ${snapshot_id:0:8} via scratch restore at $scratch"
|
||||||
|
|
||||||
if ! engineRestoreSnapshot "$idx" "$snapshot_id" "$scratch" "$containers_dir$app_name"; then
|
if ! engineRestoreSnapshot "$idx" "$snapshot_id" "$scratch" "$(appDir "$app_name")"; then
|
||||||
isError "Verify restore FAILED for $app_name on $(resticLocationName "$idx")"
|
isError "Verify restore FAILED for $app_name on $(resticLocationName "$idx")"
|
||||||
runFileOp rm -rf "$scratch"
|
runFileOp rm -rf "$scratch"
|
||||||
return 1
|
return 1
|
||||||
@ -31,7 +31,7 @@ backupVerifySnapshot()
|
|||||||
# and replaced by dumps/captures under .lp-backup) — so just sanity-check the
|
# and replaced by dumps/captures under .lp-backup) — so just sanity-check the
|
||||||
# restore produced a non-empty tree.
|
# restore produced a non-empty tree.
|
||||||
local restored_count
|
local restored_count
|
||||||
restored_count=$(runFileOp find "$scratch$containers_dir$app_name" -type f 2>/dev/null | wc -l)
|
restored_count=$(runFileOp find "$scratch$(appDir "$app_name")" -type f 2>/dev/null | wc -l)
|
||||||
|
|
||||||
runFileOp rm -rf "$scratch"
|
runFileOp rm -rf "$scratch"
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
# never raw sudo. The install tree (our own code) is off-limits to hotfixes.
|
# never raw sudo. The install tree (our own code) is off-limits to hotfixes.
|
||||||
|
|
||||||
# --- paths -------------------------------------------------------------------
|
# --- paths -------------------------------------------------------------------
|
||||||
_artifactGenDir() { echo "${containers_dir%/}/libreportal/frontend/data/updater/generated"; }
|
_artifactGenDir() { echo "$(webuiDir)/frontend/data/updater/generated"; }
|
||||||
_artifactAppliedDir() { echo "$(_artifactGenDir)/applied"; }
|
_artifactAppliedDir() { echo "$(_artifactGenDir)/applied"; }
|
||||||
_artifactRecordFile() { echo "$(_artifactAppliedDir)/$1.json"; } # $1=id
|
_artifactRecordFile() { echo "$(_artifactAppliedDir)/$1.json"; } # $1=id
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ _artifactResolve() {
|
|||||||
if [[ ! "$_ART_APP" =~ ^[a-z0-9][a-z0-9_]{0,31}$ ]]; then
|
if [[ ! "$_ART_APP" =~ ^[a-z0-9][a-z0-9_]{0,31}$ ]]; then
|
||||||
isError "artifact: app artifact '$id' has a missing/unsafe applies_when.app slug -- refusing."; return 1
|
isError "artifact: app artifact '$id' has a missing/unsafe applies_when.app slug -- refusing."; return 1
|
||||||
fi
|
fi
|
||||||
elif [[ "$_ART_SCOPE" == "app" && ! -d "${containers_dir%/}/$_ART_APP" ]]; then
|
elif [[ "$_ART_SCOPE" == "app" && ! -d "$(appDir "$_ART_APP")" ]]; then
|
||||||
isNotice "artifact: '$id' targets '$_ART_APP' which is not installed -- not applicable."; return 2
|
isNotice "artifact: '$id' targets '$_ART_APP' which is not installed -- not applicable."; return 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -210,7 +210,7 @@ _artifactPathAllowed() {
|
|||||||
[[ "$path" =~ ^[A-Za-z0-9._/@:+-]+$ ]] || return 1
|
[[ "$path" =~ ^[A-Za-z0-9._/@:+-]+$ ]] || return 1
|
||||||
real="$(realpath -m -- "$path" 2>/dev/null)"; [[ -n "$real" ]] || return 1
|
real="$(realpath -m -- "$path" 2>/dev/null)"; [[ -n "$real" ]] || return 1
|
||||||
if [[ "$scope" == "app" ]]; then
|
if [[ "$scope" == "app" ]]; then
|
||||||
root="$(realpath -m -- "${containers_dir%/}/$app" 2>/dev/null)"
|
root="$(realpath -m -- "$(appDir "$app")" 2>/dev/null)"
|
||||||
[[ "$real" == "$root/"* ]] && return 0
|
[[ "$real" == "$root/"* ]] && return 0
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@ -221,7 +221,7 @@ _artifactPathAllowed() {
|
|||||||
|
|
||||||
# current image of an app's compose (first image: line), quotes/comment stripped.
|
# current image of an app's compose (first image: line), quotes/comment stripped.
|
||||||
_artifactComposeImage() {
|
_artifactComposeImage() {
|
||||||
local app="$1" f="${containers_dir%/}/$1/docker-compose.yml"
|
local app="$1" f="$(appDir "$1")/docker-compose.yml"
|
||||||
[[ -f "$f" ]] || return 1
|
[[ -f "$f" ]] || return 1
|
||||||
grep -m1 -E '^\s*image:' "$f" 2>/dev/null | sed -E 's/^\s*image:\s*//; s/["'"'"']//g; s/\s+#.*$//; s/\s+$//'
|
grep -m1 -E '^\s*image:' "$f" 2>/dev/null | sed -E 's/^\s*image:\s*//; s/["'"'"']//g; s/\s+#.*$//; s/\s+$//'
|
||||||
}
|
}
|
||||||
@ -320,7 +320,7 @@ _artifactBundleOwner() {
|
|||||||
_artifactApplyBundleFlow() {
|
_artifactApplyBundleFlow() {
|
||||||
local id="$1" art="$2" serial="$3"
|
local id="$1" art="$2" serial="$3"
|
||||||
local slug="$_ART_APP"
|
local slug="$_ART_APP"
|
||||||
local live_dir="${containers_dir%/}/$slug"
|
local live_dir="$(appDir "$slug")"
|
||||||
local def_root="${install_containers_dir%/}"
|
local def_root="${install_containers_dir%/}"
|
||||||
local def_dir="$def_root/$slug"
|
local def_dir="$def_root/$slug"
|
||||||
|
|
||||||
@ -388,7 +388,7 @@ _artifactApplyBundleFlow() {
|
|||||||
if [[ "$placed" == "1" ]]; then
|
if [[ "$placed" == "1" ]]; then
|
||||||
isNotice "Definition placed -- refreshing the App Center catalog..."
|
isNotice "Definition placed -- refreshing the App Center catalog..."
|
||||||
{ declare -F lpRegenWebui >/dev/null 2>&1 && lpRegenWebui force >/dev/null 2>&1; } || true
|
{ declare -F lpRegenWebui >/dev/null 2>&1 && lpRegenWebui force >/dev/null 2>&1; } || true
|
||||||
local apps_json="${containers_dir%/}/libreportal/frontend/data/apps/generated/apps.json"
|
local apps_json="$(webuiDir)/frontend/data/apps/generated/apps.json"
|
||||||
jq -e --arg app "$slug" '.apps[]? | select((.command // "") | endswith(" " + $app))' "$apps_json" >/dev/null 2>&1 || placed=0
|
jq -e --arg app "$slug" '.apps[]? | select((.command // "") | endswith(" " + $app))' "$apps_json" >/dev/null 2>&1 || placed=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -457,7 +457,7 @@ _artifactOpPrecheck() {
|
|||||||
image="$(jq -r '.image // empty' <<<"$op_json")"
|
image="$(jq -r '.image // empty' <<<"$op_json")"
|
||||||
{ _artifactSafeScalar "$from" && _artifactSafeScalar "$image"; } || { isError "op set-compose-image: image/from has an unsafe character"; return 1; }
|
{ _artifactSafeScalar "$from" && _artifactSafeScalar "$image"; } || { isError "op set-compose-image: image/from has an unsafe character"; return 1; }
|
||||||
[[ "$image" =~ ^[A-Za-z0-9._:/@-]+$ ]] || { isError "op set-compose-image: '$image' is not a valid image reference"; return 1; }
|
[[ "$image" =~ ^[A-Za-z0-9._:/@-]+$ ]] || { isError "op set-compose-image: '$image' is not a valid image reference"; return 1; }
|
||||||
[[ -d "${containers_dir%/}/$app" ]] || { isError "op set-compose-image: app '$app' not installed"; return 1; }
|
[[ -d "$(appDir "$app")" ]] || { isError "op set-compose-image: app '$app' not installed"; return 1; }
|
||||||
cur="$(_artifactComposeImage "$app")"
|
cur="$(_artifactComposeImage "$app")"
|
||||||
[[ -n "$from" && "$cur" == "$from" ]] || { isError "op set-compose-image: image is '$cur', expected '$from' (drift)"; return 1; }
|
[[ -n "$from" && "$cur" == "$from" ]] || { isError "op set-compose-image: image is '$cur', expected '$from' (drift)"; return 1; }
|
||||||
;;
|
;;
|
||||||
@ -511,7 +511,7 @@ _artifactOpApply() {
|
|||||||
set-compose-image)
|
set-compose-image)
|
||||||
local image cur f esc_img
|
local image cur f esc_img
|
||||||
image="$(jq -r '.image' <<<"$op_json")"
|
image="$(jq -r '.image' <<<"$op_json")"
|
||||||
f="${containers_dir%/}/$app/docker-compose.yml"
|
f="$(appDir "$app")/docker-compose.yml"
|
||||||
cur="$(_artifactComposeImage "$app")"
|
cur="$(_artifactComposeImage "$app")"
|
||||||
esc_img="$(printf '%s' "$image" | sed -e 's/[\/&]/\\&/g')"
|
esc_img="$(printf '%s' "$image" | sed -e 's/[\/&]/\\&/g')"
|
||||||
runFileOp sed -i "0,/^\([[:space:]]*\)image:.*/s//\1image: $esc_img/" "$f" || return 1
|
runFileOp sed -i "0,/^\([[:space:]]*\)image:.*/s//\1image: $esc_img/" "$f" || return 1
|
||||||
@ -547,7 +547,7 @@ _artifactOpUndo() {
|
|||||||
case "$op" in
|
case "$op" in
|
||||||
set-compose-image)
|
set-compose-image)
|
||||||
local image f esc_img; image="$(jq -r '.image' <<<"$op_json")"
|
local image f esc_img; image="$(jq -r '.image' <<<"$op_json")"
|
||||||
f="${containers_dir%/}/$app/docker-compose.yml"
|
f="$(appDir "$app")/docker-compose.yml"
|
||||||
esc_img="$(printf '%s' "$image" | sed -e 's/[\/&]/\\&/g')"
|
esc_img="$(printf '%s' "$image" | sed -e 's/[\/&]/\\&/g')"
|
||||||
runFileOp sed -i "0,/^\([[:space:]]*\)image:.*/s//\1image: $esc_img/" "$f" || return 1
|
runFileOp sed -i "0,/^\([[:space:]]*\)image:.*/s//\1image: $esc_img/" "$f" || return 1
|
||||||
;;
|
;;
|
||||||
@ -567,7 +567,7 @@ _artifactOpUndo() {
|
|||||||
local slug live def
|
local slug live def
|
||||||
slug="$(jq -r '.app // empty' <<<"$op_json")"
|
slug="$(jq -r '.app // empty' <<<"$op_json")"
|
||||||
[[ "$slug" =~ ^[a-z0-9][a-z0-9_]{0,31}$ ]] || { isError "undo: unsafe app slug '$slug'"; return 1; }
|
[[ "$slug" =~ ^[a-z0-9][a-z0-9_]{0,31}$ ]] || { isError "undo: unsafe app slug '$slug'"; return 1; }
|
||||||
live="${containers_dir%/}/$slug"; def="${install_containers_dir%/}/$slug"
|
live="$(appDir "$slug")"; def="${install_containers_dir%/}/$slug"
|
||||||
if [[ -d "$live" ]]; then
|
if [[ -d "$live" ]]; then
|
||||||
isError "undo: app '$slug' is INSTALLED -- uninstall it before removing its definition."; return 1
|
isError "undo: app '$slug' is INSTALLED -- uninstall it before removing its definition."; return 1
|
||||||
fi
|
fi
|
||||||
@ -845,7 +845,7 @@ artifactApplyAuto() {
|
|||||||
# skip if app-scoped but the app isn't installed (applicable gate; full
|
# skip if app-scoped but the app isn't installed (applicable gate; full
|
||||||
# gates re-checked at apply time)
|
# gates re-checked at apply time)
|
||||||
app="$(jq -r '.applies_when.app // empty' <<<"$art")"
|
app="$(jq -r '.applies_when.app // empty' <<<"$art")"
|
||||||
[[ -n "$app" && ! -d "${containers_dir%/}/$app" ]] && continue
|
[[ -n "$app" && ! -d "$(appDir "$app")" ]] && continue
|
||||||
# --detach: this runs inside the task processor's own poll, so following
|
# --detach: this runs inside the task processor's own poll, so following
|
||||||
# the new task in the foreground would wait for a task the (single-
|
# the new task in the foreground would wait for a task the (single-
|
||||||
# threaded) processor can't start until we return.
|
# threaded) processor can't start until we return.
|
||||||
|
|||||||
@ -73,7 +73,7 @@ cliHandleArtifactCommands()
|
|||||||
artifactListApplied()
|
artifactListApplied()
|
||||||
{
|
{
|
||||||
isHeader "Applied hotfixes"
|
isHeader "Applied hotfixes"
|
||||||
local dir; dir="${containers_dir%/}/libreportal/frontend/data/updater/generated/applied"
|
local dir; dir="$(webuiDir)/frontend/data/updater/generated/applied"
|
||||||
if ! compgen -G "$dir/*.json" >/dev/null 2>&1; then
|
if ! compgen -G "$dir/*.json" >/dev/null 2>&1; then
|
||||||
isSuccessful "0 hotfixes applied."
|
isSuccessful "0 hotfixes applied."
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@ -186,7 +186,7 @@ lpNotifyTaskResult()
|
|||||||
[[ "$events" == "off" ]] && return 0
|
[[ "$events" == "off" ]] && return 0
|
||||||
lpNotifyAnyChannelEnabled || return 0
|
lpNotifyAnyChannelEnabled || return 0
|
||||||
|
|
||||||
local f="${containers_dir%/}/libreportal/frontend/data/tasks/${task_id}.json"
|
local f="$(webuiDir)/frontend/data/tasks/${task_id}.json"
|
||||||
[[ -f "$f" ]] || return 0
|
[[ -f "$f" ]] || return 0
|
||||||
local status type app exit_code
|
local status type app exit_code
|
||||||
if command -v jq >/dev/null 2>&1; then
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
# build changes the digest and is attempted normally; the Update button
|
# build changes the digest and is attempted normally; the Update button
|
||||||
# stays available for a manual retry of the skipped one.
|
# stays available for a manual retry of the skipped one.
|
||||||
|
|
||||||
_updaterAutoGenDir() { echo "${containers_dir%/}/libreportal/frontend/data/updater/generated"; }
|
_updaterAutoGenDir() { echo "$(webuiDir)/frontend/data/updater/generated"; }
|
||||||
_updaterAutoDir() { echo "$(_updaterAutoGenDir)/auto"; }
|
_updaterAutoDir() { echo "$(_updaterAutoGenDir)/auto"; }
|
||||||
_updaterAutoStamp() { echo "$(_updaterAutoDir)/$1.digest"; } # $1=app
|
_updaterAutoStamp() { echo "$(_updaterAutoDir)/$1.digest"; } # $1=app
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ updaterAppPolicy()
|
|||||||
# this far (update available + policy auto).
|
# this far (update available + policy auto).
|
||||||
updaterAutoTaskPending()
|
updaterAutoTaskPending()
|
||||||
{
|
{
|
||||||
local app="$1" dir="${containers_dir%/}/libreportal/frontend/data/tasks"
|
local app="$1" dir="$(webuiDir)/frontend/data/tasks"
|
||||||
command -v jq >/dev/null 2>&1 || return 1
|
command -v jq >/dev/null 2>&1 || return 1
|
||||||
local files=( "$dir"/task_*.json )
|
local files=( "$dir"/task_*.json )
|
||||||
[[ -e "${files[0]}" ]] || return 1
|
[[ -e "${files[0]}" ]] || return 1
|
||||||
|
|||||||
@ -35,7 +35,7 @@ cliHandleUpdaterCommands()
|
|||||||
local scan_interval="${CFG_UPDATER_SCAN_INTERVAL:-30}"
|
local scan_interval="${CFG_UPDATER_SCAN_INTERVAL:-30}"
|
||||||
[[ "$scan_interval" =~ ^[0-9]+$ ]] || scan_interval=30
|
[[ "$scan_interval" =~ ^[0-9]+$ ]] || scan_interval=30
|
||||||
(( scan_interval == 0 )) && return 0
|
(( scan_interval == 0 )) && return 0
|
||||||
local scan_file="${containers_dir%/}/libreportal/frontend/data/updater/generated/updates.json"
|
local scan_file="$(webuiDir)/frontend/data/updater/generated/updates.json"
|
||||||
if [[ -f "$scan_file" ]]; then
|
if [[ -f "$scan_file" ]]; then
|
||||||
local _now _last; _now=$(date +%s); _last=$(stat -c '%Y' "$scan_file" 2>/dev/null || echo 0)
|
local _now _last; _now=$(date +%s); _last=$(stat -c '%Y' "$scan_file" 2>/dev/null || echo 0)
|
||||||
(( _now - _last < scan_interval * 60 )) && return 0
|
(( _now - _last < scan_interval * 60 )) && return 0
|
||||||
@ -174,7 +174,7 @@ updaterRefDigest()
|
|||||||
updaterSetAnchorRef()
|
updaterSetAnchorRef()
|
||||||
{
|
{
|
||||||
local app="$1" newref="$2"
|
local app="$1" newref="$2"
|
||||||
local compose="${containers_dir%/}/$app/docker-compose.yml"
|
local compose="$(appDir "$app")/docker-compose.yml"
|
||||||
[ -f "$compose" ] || return 1
|
[ -f "$compose" ] || return 1
|
||||||
local tmp; tmp="$(mktemp)"
|
local tmp; tmp="$(mktemp)"
|
||||||
awk -v s="${app//_/-}-service" -v ref="$newref" '
|
awk -v s="${app//_/-}-service" -v ref="$newref" '
|
||||||
@ -215,7 +215,7 @@ updaterApplyApp()
|
|||||||
# "manual" when a person pressed Update. Recorded in History; changes nothing
|
# "manual" when a person pressed Update. Recorded in History; changes nothing
|
||||||
# about how the update is applied — both take the snapshot, both can roll back.
|
# about how the update is applied — both take the snapshot, both can roll back.
|
||||||
local trigger="${2:-manual}"
|
local trigger="${2:-manual}"
|
||||||
local app_dir="$containers_dir/$_upd_app"
|
local app_dir="$(appDir "$_upd_app")"
|
||||||
if [[ ! -d "$app_dir" ]]; then isError "App '$_upd_app' is not installed."; return 1; fi
|
if [[ ! -d "$app_dir" ]]; then isError "App '$_upd_app' is not installed."; return 1; fi
|
||||||
|
|
||||||
if [[ "$trigger" == "auto" ]]; then
|
if [[ "$trigger" == "auto" ]]; then
|
||||||
@ -317,7 +317,7 @@ updaterRollbackApp()
|
|||||||
# explicitly first to actually pick up a new image.
|
# explicitly first to actually pick up a new image.
|
||||||
updaterComposePull()
|
updaterComposePull()
|
||||||
{
|
{
|
||||||
local app="$1" dir="${containers_dir%/}/$1"
|
local app="$1" dir="$(appDir "$1")"
|
||||||
[ -d "$dir" ] || return 1
|
[ -d "$dir" ] || return 1
|
||||||
if [[ "$CFG_DOCKER_INSTALL_TYPE" == "rootless" ]]; then
|
if [[ "$CFG_DOCKER_INSTALL_TYPE" == "rootless" ]]; then
|
||||||
dockerCommandRunInstallUser "cd $dir && docker compose pull" >/dev/null 2>&1
|
dockerCommandRunInstallUser "cd $dir && docker compose pull" >/dev/null 2>&1
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
# after reading release notes. The updater surfaces "34 available"; this runs
|
# after reading release notes. The updater surfaces "34 available"; this runs
|
||||||
# only when asked.
|
# only when asked.
|
||||||
|
|
||||||
_updaterUpgradeGenDir() { echo "${containers_dir%/}/libreportal/frontend/data/updater/generated"; }
|
_updaterUpgradeGenDir() { echo "$(webuiDir)/frontend/data/updater/generated"; }
|
||||||
|
|
||||||
# Rewrite the anchor image AND every image locked in step with it, plus their
|
# Rewrite the anchor image AND every image locked in step with it, plus their
|
||||||
# version sentinels, so the live compose stays self-consistent.
|
# version sentinels, so the live compose stays self-consistent.
|
||||||
@ -56,7 +56,7 @@ _updaterUpgradeGenDir() { echo "${containers_dir%/}/libreportal/frontend/data/up
|
|||||||
# return before this point.
|
# return before this point.
|
||||||
updaterSetAnchorVersion() {
|
updaterSetAnchorVersion() {
|
||||||
local app="$1" newtag="$2"
|
local app="$1" newtag="$2"
|
||||||
local compose="${containers_dir%/}/$app/docker-compose.yml"
|
local compose="$(appDir "$app")/docker-compose.yml"
|
||||||
[ -f "$compose" ] || return 1
|
[ -f "$compose" ] || return 1
|
||||||
local up; up="$(printf '%s' "$app" | tr '[:lower:]' '[:upper:]')"
|
local up; up="$(printf '%s' "$app" | tr '[:lower:]' '[:upper:]')"
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ updaterSetAnchorVersion() {
|
|||||||
# Current anchor tag for an app, straight from its live compose.
|
# Current anchor tag for an app, straight from its live compose.
|
||||||
updaterCurrentTag() {
|
updaterCurrentTag() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local compose="${containers_dir%/}/$app/docker-compose.yml"
|
local compose="$(appDir "$app")/docker-compose.yml"
|
||||||
[ -f "$compose" ] || return 1
|
[ -f "$compose" ] || return 1
|
||||||
updaterTagOf "$(updaterPrimaryImage "$app" "$compose")"
|
updaterTagOf "$(updaterPrimaryImage "$app" "$compose")"
|
||||||
}
|
}
|
||||||
@ -129,7 +129,7 @@ updaterUpgradeApp() {
|
|||||||
local app="$1" target="${2:-}" mode="${3:-}"
|
local app="$1" target="${2:-}" mode="${3:-}"
|
||||||
[ "$target" = "--dry-run" ] && { mode="--dry-run"; target=""; }
|
[ "$target" = "--dry-run" ] && { mode="--dry-run"; target=""; }
|
||||||
|
|
||||||
local app_dir="${containers_dir%/}/$app"
|
local app_dir="$(appDir "$app")"
|
||||||
[ -d "$app_dir" ] || { isError "App '$app' is not installed."; return 1; }
|
[ -d "$app_dir" ] || { isError "App '$app' is not installed."; return 1; }
|
||||||
|
|
||||||
local cur; cur="$(updaterCurrentTag "$app")"
|
local cur; cur="$(updaterCurrentTag "$app")"
|
||||||
@ -233,7 +233,7 @@ _updaterUpgradePruneImages() {
|
|||||||
(( ${#climbed[@]} >= 2 )) || return 0 # one step: previous IS the rollback target
|
(( ${#climbed[@]} >= 2 )) || return 0 # one step: previous IS the rollback target
|
||||||
|
|
||||||
local anchor repo
|
local anchor repo
|
||||||
anchor="$(updaterPrimaryImage "$app" "${containers_dir%/}/$app/docker-compose.yml")"
|
anchor="$(updaterPrimaryImage "$app" "$(appDir "$app")/docker-compose.yml")"
|
||||||
repo="$(updaterRepoTag "$anchor")"; repo="${repo%:*}"
|
repo="$(updaterRepoTag "$anchor")"; repo="${repo%:*}"
|
||||||
|
|
||||||
# Everything we moved off, minus the last one (kept for rollback).
|
# Everything we moved off, minus the last one (kept for rollback).
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
# most need verifying. Falls back to the convention when there is no compose.
|
# most need verifying. Falls back to the convention when there is no compose.
|
||||||
_updaterPrimaryContainer() {
|
_updaterPrimaryContainer() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local compose="${containers_dir%/}/$app/docker-compose.yml"
|
local compose="$(appDir "$app")/docker-compose.yml"
|
||||||
local fallback; fallback="$(printf '%s-service' "${app//_/-}")"
|
local fallback; fallback="$(printf '%s-service' "${app//_/-}")"
|
||||||
[ -f "$compose" ] || { printf '%s' "$fallback"; return 0; }
|
[ -f "$compose" ] || { printf '%s' "$fallback"; return 0; }
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ editAppConfig()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Use find to search for the app_name folder within $containers_dir
|
# Use find to search for the app_name folder within $containers_dir
|
||||||
local app_dir=$containers_dir$app_name
|
local app_dir=$(appDir "$app_name")
|
||||||
|
|
||||||
if [ -n "$app_dir" ]; then
|
if [ -n "$app_dir" ]; then
|
||||||
local config_file="$app_dir/$app_name.config"
|
local config_file="$app_dir/$app_name.config"
|
||||||
|
|||||||
@ -7,12 +7,12 @@ viewAppConfigs()
|
|||||||
|
|
||||||
# Get all installed apps from containers directory
|
# Get all installed apps from containers directory
|
||||||
local installed_apps=()
|
local installed_apps=()
|
||||||
for app_dir in "$containers_dir"/*/; do
|
while IFS= read -r app_dir; do
|
||||||
if [ -d "$app_dir" ]; then
|
if [ -d "$app_dir" ]; then
|
||||||
local app_name=$(basename "$app_dir")
|
local app_name=$(basename "$app_dir")
|
||||||
installed_apps+=("$app_name")
|
installed_apps+=("$app_name")
|
||||||
fi
|
fi
|
||||||
done
|
done < <(storageAppDirs)
|
||||||
|
|
||||||
if [ ${#installed_apps[@]} -eq 0 ]; then
|
if [ ${#installed_apps[@]} -eq 0 ]; then
|
||||||
isNotice "No installed applications found."
|
isNotice "No installed applications found."
|
||||||
@ -47,7 +47,7 @@ viewAppConfigs()
|
|||||||
local selected_app="${installed_apps[index]}"
|
local selected_app="${installed_apps[index]}"
|
||||||
|
|
||||||
# Get the config file for this app
|
# Get the config file for this app
|
||||||
local config_file="$containers_dir/${selected_app}/${selected_app}.config"
|
local config_file="$(appDir "$selected_app")/${selected_app}.config"
|
||||||
|
|
||||||
if [ -f "$config_file" ]; then
|
if [ -f "$config_file" ]; then
|
||||||
$CFG_TEXT_EDITOR "$config_file"
|
$CFG_TEXT_EDITOR "$config_file"
|
||||||
|
|||||||
@ -12,7 +12,7 @@ viewAppCategoryConfigs()
|
|||||||
local other_apps=()
|
local other_apps=()
|
||||||
|
|
||||||
# Collect all app_name folders and categorize them into installed and others
|
# Collect all app_name folders and categorize them into installed and others
|
||||||
for app_dir in "$containers_dir"/*/; do
|
while IFS= read -r app_dir; do
|
||||||
if [ -d "$app_dir" ]; then
|
if [ -d "$app_dir" ]; then
|
||||||
local app_name=$(basename "$app_dir")
|
local app_name=$(basename "$app_dir")
|
||||||
local app_config_file="$install_containers_dir$app_name/$app_name.sh"
|
local app_config_file="$install_containers_dir$app_name/$app_name.sh"
|
||||||
@ -30,7 +30,7 @@ viewAppCategoryConfigs()
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done < <(storageAppDirs)
|
||||||
|
|
||||||
if [[ ${#installed_apps[@]} -eq 0 && ${#other_apps[@]} -eq 0 ]]; then
|
if [[ ${#installed_apps[@]} -eq 0 && ${#other_apps[@]} -eq 0 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@ -9,12 +9,12 @@ checkApplicationsConfigFilesMissingVariables()
|
|||||||
app=$(basename "$live" .config)
|
app=$(basename "$live" .config)
|
||||||
remote="$install_containers_dir$app/$app.config"
|
remote="$install_containers_dir$app/$app.config"
|
||||||
reconcileConfigFile "$live" "$remote"
|
reconcileConfigFile "$live" "$remote"
|
||||||
# 2>/dev/null on the find: app data dirs (e.g. invidious/postgresdata,
|
# storageAppConfigs walks every storage root as the container user and
|
||||||
# nextcloud/html) are owned by container sub-UIDs that the manager user
|
# yields only <slug>/<slug>.config, so app data dirs owned by container
|
||||||
# can't read into, and they're harmless permission-denied chatter — find
|
# sub-UIDs (invidious/postgresdata, nextcloud/html) are never descended
|
||||||
# at this maxdepth doesn't need to enter them to satisfy -name '*.config'
|
# into — no permission-denied chatter, and no payload *.config mistaken
|
||||||
# at depth 2. Keep the noise out of CLI/log output.
|
# for an app.
|
||||||
done < <(runFileOp find "$containers_dir" -maxdepth 2 -type f -name '*.config' ! -name '*.bak' 2>/dev/null)
|
done < <(storageAppConfigs)
|
||||||
|
|
||||||
isSuccessful "Application config reconciliation completed."
|
isSuccessful "Application config reconciliation completed."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,9 +11,9 @@ configSetupFileWithData()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $custom_path == "" ]]; then
|
if [[ $custom_path == "" ]]; then
|
||||||
local file_path="$containers_dir$app_name"
|
local file_path="$(appDir "$app_name")"
|
||||||
elif [[ $custom_path != "" ]]; then
|
elif [[ $custom_path != "" ]]; then
|
||||||
local file_path="$containers_dir$app_name/$custom_path/"
|
local file_path="$(appDir "$app_name")/$custom_path/"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local full_file_path="$file_path/$file_name"
|
local full_file_path="$file_path/$file_name"
|
||||||
|
|||||||
@ -34,7 +34,7 @@ findConfigFileForOption()
|
|||||||
echo "$app_config"
|
echo "$app_config"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done < <(runFileOp find "${containers_dir%/}" -mindepth 2 -maxdepth 2 -type f -name '*.config' 2>/dev/null)
|
done < <(storageAppConfigs)
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,7 +129,7 @@ configBackfillAllApps()
|
|||||||
[[ "$app" == "template" ]] && continue
|
[[ "$app" == "template" ]] && continue
|
||||||
|
|
||||||
template="${dir}${app}.config"
|
template="${dir}${app}.config"
|
||||||
deployed="${containers_dir}${app}/${app}.config"
|
deployed="$(appDir "$app")/${app}.config"
|
||||||
[[ -f "$template" ]] || continue
|
[[ -f "$template" ]] || continue
|
||||||
runFileOp test -f "$deployed" || continue
|
runFileOp test -f "$deployed" || continue
|
||||||
|
|
||||||
|
|||||||
@ -11,13 +11,13 @@ viewComposeFiles()
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Find all subdirectories under $containers_dir
|
# Find all subdirectories under $containers_dir
|
||||||
for app_dir in "$containers_dir"/*/; do
|
while IFS= read -r app_dir; do
|
||||||
if [[ -d "$app_dir" ]]; then
|
if [[ -d "$app_dir" ]]; then
|
||||||
# Extract the app name (folder name)
|
# Extract the app name (folder name)
|
||||||
local app_name=$(basename "$app_dir")
|
local app_name=$(basename "$app_dir")
|
||||||
local app_names+=("$app_name")
|
local app_names+=("$app_name")
|
||||||
fi
|
fi
|
||||||
done
|
done < <(storageAppDirs)
|
||||||
|
|
||||||
# Check if any apps were found
|
# Check if any apps were found
|
||||||
if [ ${#app_names[@]} -eq 0 ]; then
|
if [ ${#app_names[@]} -eq 0 ]; then
|
||||||
@ -41,7 +41,7 @@ viewComposeFiles()
|
|||||||
# Check if the selected option is a valid number
|
# Check if the selected option is a valid number
|
||||||
if ((selected_option >= 1 && selected_option <= ${#app_names[@]})); then
|
if ((selected_option >= 1 && selected_option <= ${#app_names[@]})); then
|
||||||
local selected_app="${app_names[selected_option - 1]}"
|
local selected_app="${app_names[selected_option - 1]}"
|
||||||
local selected_app_dir="$containers_dir/$selected_app"
|
local selected_app_dir="$(appDir "$selected_app")"
|
||||||
|
|
||||||
# List Docker Compose files in the selected app's folder
|
# List Docker Compose files in the selected app's folder
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@ -10,7 +10,7 @@ dockerConfigSetupFileWithData()
|
|||||||
local file_name="docker-compose.$app_name.yml";
|
local file_name="docker-compose.$app_name.yml";
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local file_path="$containers_dir$app_name"
|
local file_path="$(appDir "$app_name")"
|
||||||
local full_file_path="$file_path/$file_name"
|
local full_file_path="$file_path/$file_name"
|
||||||
|
|
||||||
if command -v sqlite3 &> /dev/null && [[ -f "$docker_dir/$db_file" ]]; then
|
if command -v sqlite3 &> /dev/null && [[ -f "$docker_dir/$db_file" ]]; then
|
||||||
|
|||||||
@ -7,7 +7,7 @@ dockerConfigSetupToContainer()
|
|||||||
local flags="$3"
|
local flags="$3"
|
||||||
local config_overrides="$4"
|
local config_overrides="$4"
|
||||||
|
|
||||||
local target_path="$containers_dir$app_name"
|
local target_path="$(appDir "$app_name")"
|
||||||
local source_file="$install_containers_dir$app_name/$app_name.config"
|
local source_file="$install_containers_dir$app_name/$app_name.config"
|
||||||
local config_file="$app_name.config"
|
local config_file="$app_name.config"
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ exportBcryptPassword()
|
|||||||
local placeholder="$2"
|
local placeholder="$2"
|
||||||
local raw_password="$3"
|
local raw_password="$3"
|
||||||
local file="$4" # File where the placeholder was found
|
local file="$4" # File where the placeholder was found
|
||||||
local log_file="$containers_dir/bcrypt.txt"
|
local log_file="$(primaryRoot)/bcrypt.txt"
|
||||||
|
|
||||||
# bcrypt.txt lives under containers_dir (docker-install-owned) -> runFileOp.
|
# bcrypt.txt lives under containers_dir (docker-install-owned) -> runFileOp.
|
||||||
if [ ! -f "$log_file" ]; then
|
if [ ! -f "$log_file" ]; then
|
||||||
|
|||||||
@ -4,7 +4,7 @@ getStoredPassword()
|
|||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local variable_name="$2"
|
local variable_name="$2"
|
||||||
local log_file="$containers_dir/bcrypt.txt"
|
local log_file="$(primaryRoot)/bcrypt.txt"
|
||||||
|
|
||||||
if [ -f "$log_file" ]; then
|
if [ -f "$log_file" ]; then
|
||||||
runFileOp grep "^$app_name $variable_name " "$log_file" | awk '{print $3}' | tail -n 1
|
runFileOp grep "^$app_name $variable_name " "$log_file" | awk '{print $3}' | tail -n 1
|
||||||
|
|||||||
@ -19,8 +19,13 @@ databaseAppScan()
|
|||||||
checkSuccess "Install path not found or not a directory: $containers_dir"
|
checkSuccess "Install path not found or not a directory: $containers_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Scan the folder and retrieve folder names
|
# Scan every storage root. storageApps SKIPS a root whose drive is absent,
|
||||||
local folder_names=$(runFileOp find "$containers_dir" -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
# so an app on an unplugged disk is simply missing from this list — which is
|
||||||
|
# why every "the folder is gone" branch below must first ask
|
||||||
|
# appStorageAvailable before it deletes anything. Without that guard,
|
||||||
|
# unplugging a drive would delete the database rows and port allocations of
|
||||||
|
# every app living on it.
|
||||||
|
local folder_names=$(storageApps)
|
||||||
|
|
||||||
# Check if no folders are found
|
# Check if no folders are found
|
||||||
if [ -z "$folder_names" ]; then
|
if [ -z "$folder_names" ]; then
|
||||||
@ -38,8 +43,8 @@ databaseAppScan()
|
|||||||
while IFS='|' read -r folder_name status uninstall_date; do
|
while IFS='|' read -r folder_name status uninstall_date; do
|
||||||
if [[ -n "$folder_name" ]]; then
|
if [[ -n "$folder_name" ]]; then
|
||||||
existing_folder_names+=("$folder_name")
|
existing_folder_names+=("$folder_name")
|
||||||
# Check if the folder exists in the containers_dir
|
# Check if the folder exists on whichever storage root holds it
|
||||||
if [ -d "$containers_dir/$folder_name" ]; then
|
if [ -d "$(appDir "$folder_name")" ]; then
|
||||||
if (( status == 0 )); then
|
if (( status == 0 )); then
|
||||||
isNotice "The folder for $folder_name has been found."
|
isNotice "The folder for $folder_name has been found."
|
||||||
# Update the database to set the status to 1 (installed) and unset the uninstall_date
|
# Update the database to set the status to 1 (installed) and unset the uninstall_date
|
||||||
@ -51,8 +56,8 @@ databaseAppScan()
|
|||||||
fi
|
fi
|
||||||
done <<< "$existing_folders"
|
done <<< "$existing_folders"
|
||||||
|
|
||||||
# Loop through immediate subdirectories of $containers_dir
|
# Loop through every app directory on every available storage root
|
||||||
for app_dir in "$containers_dir"/*/; do
|
while IFS= read -r app_dir; do
|
||||||
# Get the app name from the folder name
|
# Get the app name from the folder name
|
||||||
local app_name=$(basename "$app_dir")
|
local app_name=$(basename "$app_dir")
|
||||||
|
|
||||||
@ -77,21 +82,27 @@ databaseAppScan()
|
|||||||
((updated_count++)) # Increment updated_count
|
((updated_count++)) # Increment updated_count
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done < <(storageAppDirs)
|
||||||
|
|
||||||
# Create an array to store folder names that should be removed from the database
|
# Create an array to store folder names that should be removed from the database
|
||||||
local folders_to_remove=()
|
local folders_to_remove=()
|
||||||
|
|
||||||
# Get a list of folder names that exist in the database but not in the current folder structure
|
# Get a list of folder names that exist in the database but not in the current folder structure
|
||||||
for folder_name in "${existing_folder_names[@]}"; do
|
for folder_name in "${existing_folder_names[@]}"; do
|
||||||
if [ ! -d "$containers_dir/$folder_name" ]; then
|
# Not gone — just on a drive that is not attached. Leave it alone.
|
||||||
|
appStorageAvailable "$folder_name" || continue
|
||||||
|
if [ ! -d "$(appDir "$folder_name")" ]; then
|
||||||
local folders_to_remove+=("$folder_name")
|
local folders_to_remove+=("$folder_name")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Get a list of folder names that exist in the database but not in the current folder structure
|
# Get a list of folder names that exist in the database but not in the current folder structure
|
||||||
for folder_name in "${existing_folder_names[@]}"; do
|
for folder_name in "${existing_folder_names[@]}"; do
|
||||||
if [ ! -d "$containers_dir/$folder_name" ]; then
|
if ! appStorageAvailable "$folder_name"; then
|
||||||
|
isNotice "Skipping $folder_name — its storage location is not mounted."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ ! -d "$(appDir "$folder_name")" ]; then
|
||||||
# Check if this folder is actually associated with an entry in the database
|
# Check if this folder is actually associated with an entry in the database
|
||||||
if [[ " ${folder_names[@]} " =~ " $folder_name " ]]; then
|
if [[ " ${folder_names[@]} " =~ " $folder_name " ]]; then
|
||||||
isNotice "Folder $folder_name no longer exists. Removing from the Database."
|
isNotice "Folder $folder_name no longer exists. Removing from the Database."
|
||||||
@ -119,7 +130,7 @@ databaseAppScan()
|
|||||||
# when there was no data to wipe.
|
# when there was no data to wipe.
|
||||||
for folder_name in $folder_names; do
|
for folder_name in $folder_names; do
|
||||||
[[ "$folder_name" == "libreportal" ]] && continue
|
[[ "$folder_name" == "libreportal" ]] && continue
|
||||||
local folder_path="$containers_dir/$folder_name"
|
local folder_path="$(appDir "$folder_name")"
|
||||||
|
|
||||||
if [ ! -d "$folder_path" ]; then
|
if [ ! -d "$folder_path" ]; then
|
||||||
isNotice "Folder $folder_name no longer exists — removing it from the database."
|
isNotice "Folder $folder_name no longer exists — removing it from the database."
|
||||||
|
|||||||
@ -104,7 +104,7 @@ databaseListInstalledApp()
|
|||||||
# row by matching service+name, then extract its url_path
|
# row by matching service+name, then extract its url_path
|
||||||
# (empty when the port has no web UI, e.g. DNS).
|
# (empty when the port has no web UI, e.g. DNS).
|
||||||
local _port_url_path=""
|
local _port_url_path=""
|
||||||
local _url_cfg="${containers_dir}/${app_name}/${app_name}.config"
|
local _url_cfg="$(appDir "$app_name")/${app_name}.config"
|
||||||
[[ ! -f "$_url_cfg" ]] && _url_cfg="${install_containers_dir}/${app_name}/${app_name}.config"
|
[[ ! -f "$_url_cfg" ]] && _url_cfg="${install_containers_dir}/${app_name}/${app_name}.config"
|
||||||
if [[ -f "$_url_cfg" ]]; then
|
if [[ -f "$_url_cfg" ]]; then
|
||||||
while IFS='=' read -r _vn _vv || [[ -n "$_vn" ]]; do
|
while IFS='=' read -r _vn _vv || [[ -n "$_vn" ]]; do
|
||||||
|
|||||||
@ -6,7 +6,7 @@ dockerComposeDownAllApps()
|
|||||||
# runFileOp: containers/ is owned by the container user (dockerinstall, 751)
|
# runFileOp: containers/ is owned by the container user (dockerinstall, 751)
|
||||||
# under rootless and isn't list-readable by the manager — enumerate as the
|
# under rootless and isn't list-readable by the manager — enumerate as the
|
||||||
# owner or this silently finds nothing and no apps get brought down.
|
# owner or this silently finds nothing and no apps get brought down.
|
||||||
local subdirectories=($(runFileOp find "$containers_dir" -mindepth 1 -maxdepth 1 -type d))
|
local subdirectories=($(storageAppDirs))
|
||||||
|
|
||||||
for dir in "${subdirectories[@]}"; do
|
for dir in "${subdirectories[@]}"; do
|
||||||
local app_name=$(basename "$dir")
|
local app_name=$(basename "$dir")
|
||||||
|
|||||||
@ -31,7 +31,7 @@ dockerComposeDown()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$OS_TYPE" == "Ubuntu" || "$OS_TYPE" == "Debian" ]]; then
|
if [[ "$OS_TYPE" == "Ubuntu" || "$OS_TYPE" == "Debian" ]]; then
|
||||||
if [ ! -f "$containers_dir$app_name/$compose_file" ]; then
|
if [ ! -f "$(appDir "$app_name")/$compose_file" ]; then
|
||||||
isNotice "Unable to find the compose file to docker compose down this application."
|
isNotice "Unable to find the compose file to docker compose down this application."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@ -41,10 +41,10 @@ dockerComposeDown()
|
|||||||
# header is never left without output.
|
# header is never left without output.
|
||||||
local mode="${type:-$CFG_DOCKER_INSTALL_TYPE}"
|
local mode="${type:-$CFG_DOCKER_INSTALL_TYPE}"
|
||||||
if [[ $mode == "rootless" ]]; then
|
if [[ $mode == "rootless" ]]; then
|
||||||
local result; result=$(dockerCommandRunInstallUser "cd $containers_dir$app_name && docker compose $setup_compose down" >/dev/null 2>&1)
|
local result; result=$(dockerCommandRunInstallUser "cd $(appDir "$app_name") && docker compose $setup_compose down" >/dev/null 2>&1)
|
||||||
checkSuccess "Shutting down container for $app_name"
|
checkSuccess "Shutting down container for $app_name"
|
||||||
elif [[ $mode == "rooted" ]]; then
|
elif [[ $mode == "rooted" ]]; then
|
||||||
local result; result=$(cd "$containers_dir$app_name" && docker compose $setup_compose down >/dev/null 2>&1)
|
local result; result=$(cd "$(appDir "$app_name")" && docker compose $setup_compose down >/dev/null 2>&1)
|
||||||
checkSuccess "Shutting down container for $app_name"
|
checkSuccess "Shutting down container for $app_name"
|
||||||
else
|
else
|
||||||
isNotice "Unknown Docker install type '$mode' — cannot shut down $app_name."
|
isNotice "Unknown Docker install type '$mode' — cannot shut down $app_name."
|
||||||
|
|||||||
@ -6,7 +6,7 @@ dockerComposeUpAllApps()
|
|||||||
# runFileOp: containers/ is owned by the container user (dockerinstall, 751)
|
# runFileOp: containers/ is owned by the container user (dockerinstall, 751)
|
||||||
# under rootless and isn't list-readable by the manager — enumerate as the
|
# under rootless and isn't list-readable by the manager — enumerate as the
|
||||||
# owner or this silently finds nothing and no apps come up.
|
# owner or this silently finds nothing and no apps come up.
|
||||||
local subdirectories=($(runFileOp find "$containers_dir" -mindepth 1 -maxdepth 1 -type d))
|
local subdirectories=($(storageAppDirs))
|
||||||
|
|
||||||
for dir in "${subdirectories[@]}"; do
|
for dir in "${subdirectories[@]}"; do
|
||||||
local app_name=$(basename "$dir")
|
local app_name=$(basename "$dir")
|
||||||
|
|||||||
@ -57,7 +57,7 @@ dockerComposeUp()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$OS_TYPE" == "Ubuntu" || "$OS_TYPE" == "Debian" ]]; then
|
if [[ "$OS_TYPE" == "Ubuntu" || "$OS_TYPE" == "Debian" ]]; then
|
||||||
if [ -f "$containers_dir$app_name/$compose_file" ]; then
|
if [ -f "$(appDir "$app_name")/$compose_file" ]; then
|
||||||
# Quiet pull + plain progress so progress redraws don't flood the log.
|
# Quiet pull + plain progress so progress redraws don't flood the log.
|
||||||
local _compose_quiet="--quiet-pull"
|
local _compose_quiet="--quiet-pull"
|
||||||
export COMPOSE_PROGRESS=plain
|
export COMPOSE_PROGRESS=plain
|
||||||
@ -66,7 +66,7 @@ dockerComposeUp()
|
|||||||
# ignores any edits to Dockerfile / source between installs.
|
# ignores any edits to Dockerfile / source between installs.
|
||||||
local _compose_build_flag=""
|
local _compose_build_flag=""
|
||||||
local _is_local_build=0
|
local _is_local_build=0
|
||||||
if [[ -f "$containers_dir$app_name/Dockerfile" ]]; then
|
if [[ -f "$(appDir "$app_name")/Dockerfile" ]]; then
|
||||||
_compose_build_flag="--build"
|
_compose_build_flag="--build"
|
||||||
_is_local_build=1
|
_is_local_build=1
|
||||||
fi
|
fi
|
||||||
@ -103,7 +103,7 @@ dockerComposeUp()
|
|||||||
}
|
}
|
||||||
' "$1"
|
' "$1"
|
||||||
}
|
}
|
||||||
local _compose_path="$containers_dir$app_name/$compose_file"
|
local _compose_path="$(appDir "$app_name")/$compose_file"
|
||||||
local _stale_tags
|
local _stale_tags
|
||||||
_stale_tags=$(_scanStaleTags "$_compose_path")
|
_stale_tags=$(_scanStaleTags "$_compose_path")
|
||||||
if [[ -n "$_stale_tags" ]]; then
|
if [[ -n "$_stale_tags" ]]; then
|
||||||
@ -141,7 +141,7 @@ dockerComposeUp()
|
|||||||
fi
|
fi
|
||||||
if [[ $CFG_DOCKER_INSTALL_TYPE == "rootless" ]]; then
|
if [[ $CFG_DOCKER_INSTALL_TYPE == "rootless" ]]; then
|
||||||
isNotice "Starting container for $app_name, this may take a while..."
|
isNotice "Starting container for $app_name, this may take a while..."
|
||||||
local result; result=$(dockerCommandRunInstallUser "cd $containers_dir$app_name && COMPOSE_PROGRESS=plain docker compose $setup_compose up $_compose_quiet $_compose_build_flag -d" 2>&1)
|
local result; result=$(dockerCommandRunInstallUser "cd $(appDir "$app_name") && COMPOSE_PROGRESS=plain docker compose $setup_compose up $_compose_quiet $_compose_build_flag -d" 2>&1)
|
||||||
_rc=$?
|
_rc=$?
|
||||||
_upReportComposeFailure "$app_name" "$_rc" "$result"
|
_upReportComposeFailure "$app_name" "$_rc" "$result"
|
||||||
# Restore $? to the compose exit code — a bare `checkSuccess`
|
# Restore $? to the compose exit code — a bare `checkSuccess`
|
||||||
@ -150,7 +150,7 @@ dockerComposeUp()
|
|||||||
( exit "$_rc" ); checkSuccess "Started container for $app_name"
|
( exit "$_rc" ); checkSuccess "Started container for $app_name"
|
||||||
elif [[ $CFG_DOCKER_INSTALL_TYPE == "rooted" ]]; then
|
elif [[ $CFG_DOCKER_INSTALL_TYPE == "rooted" ]]; then
|
||||||
isNotice "Starting container for $app_name, this may take a while..."
|
isNotice "Starting container for $app_name, this may take a while..."
|
||||||
local result; result=$(cd "$containers_dir$app_name" && COMPOSE_PROGRESS=plain docker compose $setup_compose up $_compose_quiet $_compose_build_flag -d 2>&1)
|
local result; result=$(cd "$(appDir "$app_name")" && COMPOSE_PROGRESS=plain docker compose $setup_compose up $_compose_quiet $_compose_build_flag -d 2>&1)
|
||||||
_rc=$?
|
_rc=$?
|
||||||
_upReportComposeFailure "$app_name" "$_rc" "$result"
|
_upReportComposeFailure "$app_name" "$_rc" "$result"
|
||||||
( exit "$_rc" ); checkSuccess "Started container for $app_name"
|
( exit "$_rc" ); checkSuccess "Started container for $app_name"
|
||||||
@ -158,10 +158,10 @@ dockerComposeUp()
|
|||||||
# Used for the CLI dockertype switcher.
|
# Used for the CLI dockertype switcher.
|
||||||
else
|
else
|
||||||
if [[ $type == "rootless" ]]; then
|
if [[ $type == "rootless" ]]; then
|
||||||
local result; result=$(dockerCommandRunInstallUser "cd $containers_dir$app_name && docker compose $setup_compose down"); _rc=$?
|
local result; result=$(dockerCommandRunInstallUser "cd $(appDir "$app_name") && docker compose $setup_compose down"); _rc=$?
|
||||||
checkSuccess "Shutting down container for $app_name"
|
checkSuccess "Shutting down container for $app_name"
|
||||||
elif [[ $type == "rooted" ]]; then
|
elif [[ $type == "rooted" ]]; then
|
||||||
local result; result=$(cd "$containers_dir$app_name" && docker compose $setup_compose down); _rc=$?
|
local result; result=$(cd "$(appDir "$app_name")" && docker compose $setup_compose down); _rc=$?
|
||||||
checkSuccess "Shutting down container for $app_name"
|
checkSuccess "Shutting down container for $app_name"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -20,7 +20,7 @@ dockerRestartApp()
|
|||||||
isError "Invalid service name: $service_name"
|
isError "Invalid service name: $service_name"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local app_dir="${containers_dir%/}/$app_name"
|
local app_dir="$(appDir "$app_name")"
|
||||||
if [[ ! -f "$app_dir/docker-compose.yml" ]]; then
|
if [[ ! -f "$app_dir/docker-compose.yml" ]]; then
|
||||||
isError "No compose file for '$app_name' at $app_dir/docker-compose.yml"
|
isError "No compose file for '$app_name' at $app_dir/docker-compose.yml"
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@ -21,12 +21,12 @@ dockerComposeDownRemove()
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -d "$containers_dir$app_name" ]]; then
|
if [[ -d "$(appDir "$app_name")" ]]; then
|
||||||
isNotice "Shutting down & removing all $app_name container data"
|
isNotice "Shutting down & removing all $app_name container data"
|
||||||
if [[ "$CFG_DOCKER_INSTALL_TYPE" == "rootless" ]]; then
|
if [[ "$CFG_DOCKER_INSTALL_TYPE" == "rootless" ]]; then
|
||||||
dockerCommandRunInstallUser "cd $containers_dir$app_name && docker compose down -v --remove-orphans" >/dev/null 2>&1
|
dockerCommandRunInstallUser "cd $(appDir "$app_name") && docker compose down -v --remove-orphans" >/dev/null 2>&1
|
||||||
else
|
else
|
||||||
(cd "$containers_dir$app_name" && docker compose down -v --remove-orphans) >/dev/null 2>&1
|
(cd "$(appDir "$app_name")" && docker compose down -v --remove-orphans) >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
isNotice "App directory '$app_name' not found — falling back to name-based container cleanup."
|
isNotice "App directory '$app_name' not found — falling back to name-based container cleanup."
|
||||||
|
|||||||
@ -11,7 +11,7 @@ dockerRemoveAppImages()
|
|||||||
|
|
||||||
isNotice "Removing Docker images for '$app_name'. Please wait..."
|
isNotice "Removing Docker images for '$app_name'. Please wait..."
|
||||||
|
|
||||||
local compose_dir="$containers_dir$app_name"
|
local compose_dir="$(appDir "$app_name")"
|
||||||
local compose_file="$compose_dir/docker-compose.yml"
|
local compose_file="$compose_dir/docker-compose.yml"
|
||||||
local compose_images=""
|
local compose_images=""
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ dockerCopyBuildContext()
|
|||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local source_dir="$install_containers_dir$app_name"
|
local source_dir="$install_containers_dir$app_name"
|
||||||
local target_dir="$containers_dir$app_name"
|
local target_dir="$(appDir "$app_name")"
|
||||||
|
|
||||||
if [[ -z "$app_name" ]]; then
|
if [[ -z "$app_name" ]]; then
|
||||||
isError "dockerCopyBuildContext: app_name is empty."
|
isError "dockerCopyBuildContext: app_name is empty."
|
||||||
|
|||||||
@ -28,7 +28,7 @@ dockerComposeSetupFile()
|
|||||||
local target_compose_file="docker-compose.$app_name.yml";
|
local target_compose_file="docker-compose.$app_name.yml";
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local target_path="$containers_dir$app_name"
|
local target_path="$(appDir "$app_name")"
|
||||||
local target_file="$target_path/$target_compose_file"
|
local target_file="$target_path/$target_compose_file"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ dockerComposeUpdate()
|
|||||||
|
|
||||||
# Fail2ban specifics
|
# Fail2ban specifics
|
||||||
if [[ "$app_name" == "fail2ban" ]]; then
|
if [[ "$app_name" == "fail2ban" ]]; then
|
||||||
local jail_local_file="$containers_dir/$app_name/config/$app_name/jail.local"
|
local jail_local_file="$(appDir "$app_name")/config/$app_name/jail.local"
|
||||||
|
|
||||||
if [ -f "$jail_local_file" ]; then
|
if [ -f "$jail_local_file" ]; then
|
||||||
if runFileOp grep -q "ignoreip = ips_whitelist" "$jail_local_file"; then
|
if runFileOp grep -q "ignoreip = ips_whitelist" "$jail_local_file"; then
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
# assignment carrying this exact IP means it IS live on the shared net.
|
# assignment carrying this exact IP means it IS live on the shared net.
|
||||||
_netServiceIsRouted() {
|
_netServiceIsRouted() {
|
||||||
local app="$1" ip="$2"
|
local app="$1" ip="$2"
|
||||||
local compose="${containers_dir}${app}/docker-compose.yml"
|
local compose="$(appDir "$app")/docker-compose.yml"
|
||||||
[[ -f "$compose" ]] || return 1 # no compose to consult -> don't skip
|
[[ -f "$compose" ]] || return 1 # no compose to consult -> don't skip
|
||||||
local esc_ip="${ip//./\\.}"
|
local esc_ip="${ip//./\\.}"
|
||||||
grep -Eq "^[[:space:]]*ipv4_address:[[:space:]]*${esc_ip}([[:space:]]|#|$)" "$compose" && return 1
|
grep -Eq "^[[:space:]]*ipv4_address:[[:space:]]*${esc_ip}([[:space:]]|#|$)" "$compose" && return 1
|
||||||
|
|||||||
@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
dockerSetupEnvFile()
|
dockerSetupEnvFile()
|
||||||
{
|
{
|
||||||
local result; result=$(copyFile "loud" $containers_dir$app_name/env.example $containers_dir$app_name/.env $docker_install_user)
|
local result; result=$(copyFile "loud" $(appDir "$app_name")/env.example $(appDir "$app_name")/.env $docker_install_user)
|
||||||
checkSuccess "Setting up .env file to path"
|
checkSuccess "Setting up .env file to path"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -152,7 +152,7 @@ switchMigrateBackupApps()
|
|||||||
# Enumerate under the OLD mode — containers/ is still owned by its container
|
# Enumerate under the OLD mode — containers/ is still owned by its container
|
||||||
# user here, so runFileOp (now resolved to that user) can list it (the
|
# user here, so runFileOp (now resolved to that user) can list it (the
|
||||||
# manager can't list the 751 dockerinstall-owned dir under rootless).
|
# manager can't list the 751 dockerinstall-owned dir under rootless).
|
||||||
local subdirectories=($(runFileOp find "$containers_dir" -mindepth 1 -maxdepth 1 -type d))
|
local subdirectories=($(storageAppDirs))
|
||||||
|
|
||||||
local failed=()
|
local failed=()
|
||||||
local dir app_name
|
local dir app_name
|
||||||
|
|||||||
@ -15,7 +15,7 @@ dockerSwitcherUpdateContainersToDockerType()
|
|||||||
CFG_DOCKER_INSTALL_TYPE="$old_mode"
|
CFG_DOCKER_INSTALL_TYPE="$old_mode"
|
||||||
resolveDockerInstallUser
|
resolveDockerInstallUser
|
||||||
fi
|
fi
|
||||||
local subdirectories=($(runFileOp find "$containers_dir" -maxdepth 1 -type d))
|
local subdirectories=($(storageAppDirs))
|
||||||
CFG_DOCKER_INSTALL_TYPE="$saved_type"
|
CFG_DOCKER_INSTALL_TYPE="$saved_type"
|
||||||
resolveDockerInstallUser
|
resolveDockerInstallUser
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
backupContainerFilesToTemp()
|
backupContainerFilesToTemp()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local source_folder="$containers_dir$app_name"
|
local source_folder="$(appDir "$app_name")"
|
||||||
|
|
||||||
temp_backup_folder="temp_$(date +%Y%m%d%H%M%S)_$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 6)"
|
temp_backup_folder="temp_$(date +%Y%m%d%H%M%S)_$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 6)"
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
backupContainerFilesRestore()
|
backupContainerFilesRestore()
|
||||||
{
|
{
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local source_folder="$containers_dir$app_name"
|
local source_folder="$(appDir "$app_name")"
|
||||||
|
|
||||||
if [ -d "$temp_backup_folder" ]; then
|
if [ -d "$temp_backup_folder" ]; then
|
||||||
local result; result=$(copyFiles "loud" "$temp_backup_folder" "$source_folder" $docker_install_user)
|
local result; result=$(copyFiles "loud" "$temp_backup_folder" "$source_folder" $docker_install_user)
|
||||||
|
|||||||
@ -13,7 +13,7 @@ copyResource()
|
|||||||
echo "App folder '$app_name' not found in '$install_containers_dir'."
|
echo "App folder '$app_name' not found in '$install_containers_dir'."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local destination_dir="$containers_dir$app_name"
|
local destination_dir="$(appDir "$app_name")"
|
||||||
|
|
||||||
if [ -n "$save_path" ]; then
|
if [ -n "$save_path" ]; then
|
||||||
local destination_dir="$destination_dir/$save_path"
|
local destination_dir="$destination_dir/$save_path"
|
||||||
|
|||||||
@ -21,14 +21,14 @@ fixPermissionsBeforeStart()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Traefik
|
# Traefik
|
||||||
if [ -f "${containers_dir}traefik/etc/certs/acme.json" ]; then
|
if [ -f "$(appDir traefik)/etc/certs/acme.json" ]; then
|
||||||
runOwnership app-file traefik etc/certs/acme.json
|
runOwnership app-file traefik etc/certs/acme.json
|
||||||
local result; result=$(runFileOp chmod 600 "${containers_dir}traefik/etc/certs/acme.json")
|
local result; result=$(runFileOp chmod 600 "$(appDir traefik)/etc/certs/acme.json")
|
||||||
checkSuccess "Set permissions to acme.json file for traefik"
|
checkSuccess "Set permissions to acme.json file for traefik"
|
||||||
fi
|
fi
|
||||||
if [ -f "${containers_dir}traefik/etc/traefik.yml" ]; then
|
if [ -f "$(appDir traefik)/etc/traefik.yml" ]; then
|
||||||
runOwnership app-file traefik etc/traefik.yml
|
runOwnership app-file traefik etc/traefik.yml
|
||||||
local result; result=$(runFileOp chmod 600 "${containers_dir}traefik/etc/traefik.yml")
|
local result; result=$(runFileOp chmod 600 "$(appDir traefik)/etc/traefik.yml")
|
||||||
checkSuccess "Set permissions to traefik.yml file for traefik"
|
checkSuccess "Set permissions to traefik.yml file for traefik"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ fixConfigPermissions()
|
|||||||
{
|
{
|
||||||
local silent_flag="$1"
|
local silent_flag="$1"
|
||||||
local app_name="$2"
|
local app_name="$2"
|
||||||
local config_file="$containers_dir$app_name/$app_name.config"
|
local config_file="$(appDir "$app_name")/$app_name.config"
|
||||||
|
|
||||||
local result; result=$(runFileOp chmod g+rw $config_file)
|
local result; result=$(runFileOp chmod g+rw $config_file)
|
||||||
if [ "$silent_flag" == "loud" ]; then
|
if [ "$silent_flag" == "loud" ]; then
|
||||||
|
|||||||
@ -430,16 +430,20 @@ _instanceRewriteTools() {
|
|||||||
sed -i -E "s/\b${type_u}_([A-Z0-9_]*_TAG)\b/${slug_u}_\1/g" "$f"
|
sed -i -E "s/\b${type_u}_([A-Z0-9_]*_TAG)\b/${slug_u}_\1/g" "$f"
|
||||||
|
|
||||||
# The app's own deployed directory. Hooks that build it from
|
# The app's own deployed directory. Hooks that build it from
|
||||||
# "${containers_dir}<type>/..." instead of "$containers_dir$app_name/..."
|
# "${containers_dir}<type>/..." instead of "$(appDir "$app_name")/..."
|
||||||
# read and WRITE the base app's files — adguard's auth adapter edits
|
# read and WRITE the base app's files — adguard's auth adapter edits
|
||||||
# ${containers_dir}adguard/conf/AdGuardHome.yaml, so an instance would
|
# $(appDir adguard)/conf/AdGuardHome.yaml, so an instance would
|
||||||
# have rewritten the original's config. Anchored on containers_dir so a
|
# have rewritten the original's config. Anchored on containers_dir so a
|
||||||
# bare mention of the app name in prose is left alone.
|
# bare mention of the app name in prose is left alone.
|
||||||
# Trailing / is NOT required: dashy tests [[ -d "${containers_dir}dashy" ]]
|
# Trailing / is NOT required: dashy tests [[ -d "$(appDir dashy)" ]]
|
||||||
# and gluetun does (cd "${containers_dir}gluetun" && …), both ending at
|
# and gluetun does (cd "$(appDir gluetun)" && …), both ending at
|
||||||
# the quote. Only the first path component is touched, so a data subdir
|
# the quote. Only the first path component is touched, so a data subdir
|
||||||
# that repeats the app name (${containers_dir}prometheus/prometheus/…)
|
# that repeats the app name ($(appDir prometheus)/prometheus/…)
|
||||||
# keeps its inner segment.
|
# keeps its inner segment.
|
||||||
|
# Post-sweep form: hooks resolve their dir with $(appDir <name>).
|
||||||
|
sed -i -E "s@(\\\$\(appDir )${type}(\))@\1${slug}\2@g" "$f"
|
||||||
|
sed -i -E "s@(\\\$\(appDir \")${type}(\"\))@\1${slug}\2@g" "$f"
|
||||||
|
# Legacy form, for any hook that still builds the path by hand.
|
||||||
sed -i -E "s@(\\\$\{containers_dir\})${type}([^A-Za-z0-9_-]|$)@\1${slug}\2@g" "$f"
|
sed -i -E "s@(\\\$\{containers_dir\})${type}([^A-Za-z0-9_-]|$)@\1${slug}\2@g" "$f"
|
||||||
sed -i -E "s@(\\\$\{install_containers_dir\}/?)${type}([^A-Za-z0-9_-]|$)@\1${slug}\2@g" "$f"
|
sed -i -E "s@(\\\$\{install_containers_dir\}/?)${type}([^A-Za-z0-9_-]|$)@\1${slug}\2@g" "$f"
|
||||||
|
|
||||||
@ -495,7 +499,7 @@ instanceCreate() {
|
|||||||
|
|
||||||
local slug="${type}_${id}"
|
local slug="${type}_${id}"
|
||||||
local slug_u="${slug^^}"
|
local slug_u="${slug^^}"
|
||||||
if [[ -d "${install_containers_dir%/}/$slug" || -d "${containers_dir%/}/$slug" ]]; then
|
if [[ -d "${install_containers_dir%/}/$slug" || -d "$(appDir "$slug")" ]]; then
|
||||||
isError "An app or instance named '$slug' already exists. Pick a different name."
|
isError "An app or instance named '$slug' already exists. Pick a different name."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -10,7 +10,7 @@ appUninstallMenu() {
|
|||||||
local installed_apps=()
|
local installed_apps=()
|
||||||
|
|
||||||
# Get all installed apps by checking for docker-compose.yml files
|
# Get all installed apps by checking for docker-compose.yml files
|
||||||
for app_dir in "$containers_dir"/*/; do
|
while IFS= read -r app_dir; do
|
||||||
if [ -d "$app_dir" ]; then
|
if [ -d "$app_dir" ]; then
|
||||||
local compose_file="$app_dir/docker-compose.yml"
|
local compose_file="$app_dir/docker-compose.yml"
|
||||||
if [ -f "$compose_file" ]; then
|
if [ -f "$compose_file" ]; then
|
||||||
@ -18,7 +18,7 @@ appUninstallMenu() {
|
|||||||
installed_apps+=("$app_name")
|
installed_apps+=("$app_name")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done < <(storageAppDirs)
|
||||||
|
|
||||||
if [ ${#installed_apps[@]} -eq 0 ]; then
|
if [ ${#installed_apps[@]} -eq 0 ]; then
|
||||||
isNotice "No installed applications found."
|
isNotice "No installed applications found."
|
||||||
|
|||||||
@ -4,7 +4,7 @@ resetToMenu()
|
|||||||
{
|
{
|
||||||
# Find all directories in the containers folder and create variables for them
|
# Find all directories in the containers folder and create variables for them
|
||||||
if [ -d "$containers_dir" ]; then
|
if [ -d "$containers_dir" ]; then
|
||||||
for dir in "$containers_dir"/*/; do
|
while IFS= read -r dir; do
|
||||||
if [ -d "$dir" ]; then
|
if [ -d "$dir" ]; then
|
||||||
# Get the directory name and remove any trailing slashes
|
# Get the directory name and remove any trailing slashes
|
||||||
local app_name=$(basename "$dir")
|
local app_name=$(basename "$dir")
|
||||||
@ -13,7 +13,7 @@ resetToMenu()
|
|||||||
# Set the variable
|
# Set the variable
|
||||||
eval "${app_name}=n"
|
eval "${app_name}=n"
|
||||||
fi
|
fi
|
||||||
done
|
done < <(storageAppDirs)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set any additional non-container variables here
|
# Set any additional non-container variables here
|
||||||
|
|||||||
@ -23,7 +23,7 @@ migratePreBackupDestination()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Only meaningful if the destination actually has this app installed.
|
# Only meaningful if the destination actually has this app installed.
|
||||||
if [[ ! -d "$containers_dir$app" ]]; then
|
if [[ ! -d "$(appDir "$app")" ]]; then
|
||||||
isNotice "No existing $app on destination — pre-migrate backup skipped"
|
isNotice "No existing $app on destination — pre-migrate backup skipped"
|
||||||
migrateEmit phase=pre-backup status=skipped reason=no-existing-app app="$app"
|
migrateEmit phase=pre-backup status=skipped reason=no-existing-app app="$app"
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user