diff --git a/.changeset/tidy-pugs-clap.md b/.changeset/tidy-pugs-clap.md new file mode 100644 index 00000000..285c1767 --- /dev/null +++ b/.changeset/tidy-pugs-clap.md @@ -0,0 +1,5 @@ +--- +"@repo/docs": patch +--- + +Fix the dynamic-config installer writing a bind address into `APP_PORT`, which broke Coolify on restart with `strconv.ParseUint: parsing "0.0.0.0:3000": invalid syntax`. `APP_PORT` is now a plain port number (default `3000`), user input is validated, and port `8000` is rejected since it is reserved for the Traefik dashboard entrypoint. diff --git a/apps/docs/.cspell.json b/apps/docs/.cspell.json index f3a4cabb..46435e89 100644 --- a/apps/docs/.cspell.json +++ b/apps/docs/.cspell.json @@ -61,6 +61,7 @@ "armv", "libc", "nextjs", - "twimg" + "twimg", + "strconv" ] } diff --git a/apps/docs/content/docs/style/installation/dynamic-config.mdx b/apps/docs/content/docs/style/installation/dynamic-config.mdx index 8c3c0150..f7a246fa 100644 --- a/apps/docs/content/docs/style/installation/dynamic-config.mdx +++ b/apps/docs/content/docs/style/installation/dynamic-config.mdx @@ -35,9 +35,16 @@ The steps below show how to do the same changes manually. Edit `/data/coolify/source/.env` and set Coolify's dashboard port to `3000` so the dashboard stays reachable even if Traefik is down: ``` -APP_PORT=0.0.0.0:3000 +APP_PORT=3000 ``` + + `APP_PORT` must be a plain port number. Coolify's compose also uses it in + `expose:`, which rejects a bind address, so a value like `0.0.0.0:3000` makes + the restart fail with `strconv.ParseUint: parsing "0.0.0.0:3000": invalid + syntax`. + + ### 2. Update Traefik Compose Edit `/data/coolify/proxy/docker-compose.yml`. @@ -203,7 +210,7 @@ More information about custom themes can be found in the [Theming](/docs/style/t To revert: 1. Delete `/data/coolify/proxy/dynamic/coolify-tweaks.yml` -2. Reset `APP_PORT` in `/data/coolify/source/.env` to your previous value (default Coolify uses `0.0.0.0:8000`). +2. Reset `APP_PORT` in `/data/coolify/source/.env` to your previous value (default Coolify uses `8000`). 3. Remove: ```yaml diff --git a/apps/docs/public/scripts/install/dynamic-config.sh b/apps/docs/public/scripts/install/dynamic-config.sh index 624cbd61..4ed5ad33 100644 --- a/apps/docs/public/scripts/install/dynamic-config.sh +++ b/apps/docs/public/scripts/install/dynamic-config.sh @@ -3,7 +3,7 @@ ## Do not modify this file. You will lose the ability to install! ## ## Environment variables that can be set: -## APP_PORT_DEFAULT - Default bind address for Coolify dashboard (default: 0.0.0.0:3000) +## APP_PORT_DEFAULT - Default host port for the Coolify dashboard (default: 3000) ## COOLIFY_TWEAKS_CSS_URL - Override the Tweaks CSS URL ## SKIP_BACKUP - Set to "true" to skip backup of docker-compose, and .env @@ -22,7 +22,8 @@ DYNAMIC_DIR="$PROXY_DIR/dynamic" COMPOSE_FILE="$PROXY_DIR/docker-compose.yml" CURRENT_USER=$USER -APP_PORT_DEFAULT="${APP_PORT_DEFAULT:-0.0.0.0:3000}" +APP_PORT_DEFAULT="${APP_PORT_DEFAULT:-3000}" +TRAEFIK_DASHBOARD_PORT="8000" SKIP_BACKUP="${SKIP_BACKUP:-${1:-false}}" COOLIFY_TWEAKS_DEFAULT_CSS_URL="https://coolify-tweaks-api.techwithanirudh.com/release/latest/?asset=main.css" @@ -91,6 +92,20 @@ getAJoke() { fi } +# Coolify's compose uses APP_PORT in both `ports:` and `expose:`. `expose:` +# only accepts a bare port number, so anything like "0.0.0.0:3000" makes +# docker compose fail with: strconv.ParseUint: parsing "0.0.0.0:3000". +strip_bind_address() { + printf '%s' "${1##*:}" +} + +is_valid_port() { + case "$1" in + '' | *[!0-9]*) return 1 ;; + esac + [ "$1" -ge 1 ] && [ "$1" -le 65535 ] +} + update_env_var() { local key="$1" local value="$2" @@ -181,8 +196,56 @@ else echo " - Skipping .env backup" fi -read -r -p " - APP_PORT [${APP_PORT_DEFAULT}] = " NEW_APP_PORT -NEW_APP_PORT="${NEW_APP_PORT:-$APP_PORT_DEFAULT}" +APP_PORT_DEFAULT="$(strip_bind_address "$APP_PORT_DEFAULT")" +if ! is_valid_port "$APP_PORT_DEFAULT"; then + echo -e "${RED} - APP_PORT_DEFAULT must be a port number between 1 and 65535.${RESET}" + exit 1 +fi + +CURRENT_APP_PORT=$(grep -m1 '^APP_PORT=' "$ENV_FILE" | cut -d= -f2- || true) +if [ -n "$CURRENT_APP_PORT" ]; then + SANITIZED_CURRENT_APP_PORT="$(strip_bind_address "$CURRENT_APP_PORT")" + + if ! is_valid_port "$CURRENT_APP_PORT"; then + echo -e "${YELLOW} - Current APP_PORT is '${CURRENT_APP_PORT}', which Coolify cannot start with." + echo -e " It must be a plain port number, so it will be replaced.${RESET}" + fi + + if [ "$SANITIZED_CURRENT_APP_PORT" = "$TRAEFIK_DASHBOARD_PORT" ]; then + # Stock Coolify ships APP_PORT=8000, the port this installer hands to + # Traefik. Offering it back would suggest a value the loop always rejects. + echo " - Coolify is on ${TRAEFIK_DASHBOARD_PORT}, which Traefik takes over; suggesting ${APP_PORT_DEFAULT} instead." + elif is_valid_port "$SANITIZED_CURRENT_APP_PORT"; then + # Keep an already-configured port so re-running does not silently move it. + APP_PORT_DEFAULT="$SANITIZED_CURRENT_APP_PORT" + fi +fi + +echo " - APP_PORT must be a plain port number (no bind address), e.g. 3000." + +while true; do + read -r -p " - APP_PORT [${APP_PORT_DEFAULT}] = " NEW_APP_PORT || NEW_APP_PORT="" + NEW_APP_PORT="${NEW_APP_PORT:-$APP_PORT_DEFAULT}" + + SANITIZED_APP_PORT="$(strip_bind_address "$NEW_APP_PORT")" + if [ "$SANITIZED_APP_PORT" != "$NEW_APP_PORT" ]; then + echo -e "${YELLOW} - Dropping the bind address: using '${SANITIZED_APP_PORT}' instead of '${NEW_APP_PORT}'.${RESET}" + NEW_APP_PORT="$SANITIZED_APP_PORT" + fi + + if ! is_valid_port "$NEW_APP_PORT"; then + echo -e "${RED} - '${NEW_APP_PORT}' is not a valid port. Enter a number between 1 and 65535.${RESET}" + continue + fi + + if [ "$NEW_APP_PORT" = "$TRAEFIK_DASHBOARD_PORT" ]; then + echo -e "${RED} - Port ${TRAEFIK_DASHBOARD_PORT} is reserved for the Traefik dashboard entrypoint. Pick another port.${RESET}" + continue + fi + + break +done + update_env_var "APP_PORT" "$NEW_APP_PORT" # Step 3: Patch Traefik configuration diff --git a/apps/style/src/components/sidebar/_index.scss b/apps/style/src/components/sidebar/_index.scss index b35875d3..1a12d47f 100644 --- a/apps/style/src/components/sidebar/_index.scss +++ b/apps/style/src/components/sidebar/_index.scss @@ -75,8 +75,12 @@ nav > ul > li > ul > li { display: flex; } - nav[x-data]:has(a[title="Dashboard"]) > div[class*="flex pt-6 pb-4 pl-2"] > div, - nav[x-data]:has(a[title="Dashboard"]) > div[class*="flex pt-4 pb-4 pl-2"] > div { + nav[x-data]:has(a[title="Dashboard"]) + > div[class*="flex pt-6 pb-4 pl-2"] + > div, + nav[x-data]:has(a[title="Dashboard"]) + > div[class*="flex pt-4 pb-4 pl-2"] + > div { display: flex; } } diff --git a/apps/style/src/components/sidebar/_theme-switcher.scss b/apps/style/src/components/sidebar/_theme-switcher.scss index 417ce321..59534755 100644 --- a/apps/style/src/components/sidebar/_theme-switcher.scss +++ b/apps/style/src/components/sidebar/_theme-switcher.scss @@ -5,7 +5,9 @@ scale: 1 !important; translate: 0 !important; - &::after { content: none !important; } + &::after { + content: none !important; + } } & > div { @@ -37,17 +39,31 @@ } & > div button svg { - & > * { display: none; } + & > * { + display: none; + } } - & > div button[title="Light"] svg { @include icon("sun-fill", "fill"); } - & > div button[title="System default"] svg { @include icon("monitor-fill", "fill"); } - & > div button[title="Dark"] svg { @include icon("moon-fill", "fill"); } + & > div button[title="Light"] svg { + @include icon("sun-fill", "fill"); + } + & > div button[title="System default"] svg { + @include icon("monitor-fill", "fill"); + } + & > div button[title="Dark"] svg { + @include icon("moon-fill", "fill"); + } } // Cycle button (collapsed) li:has(div.menu-item[title="Theme"]) button.menu-item { - .menu-item-icon[x-show*="light"] { @include icon("sun-fill", "fill"); } - .menu-item-icon[x-show*="system"] { @include icon("monitor-fill", "fill"); } - .menu-item-icon[x-show*="dark"] { @include icon("moon-fill", "fill"); } + .menu-item-icon[x-show*="light"] { + @include icon("sun-fill", "fill"); + } + .menu-item-icon[x-show*="system"] { + @include icon("monitor-fill", "fill"); + } + .menu-item-icon[x-show*="dark"] { + @include icon("moon-fill", "fill"); + } }