Homelable could only own the root of an origin. Behind an existing proxy at `https://home.example/homelab/` the built `index.html` still asked for `/assets/*`, which fell through to whatever owned the root — and when that answered `text/html` for a `<script>` under `nosniff`, the browser failed the load on an HTTP 200. The only workaround was patching the checkout and rebuilding on every upgrade. One build-time knob, `VITE_BASE_PATH`, becomes Vite's `base`. Vite rewrites the asset URLs it emits; everything the app builds by hand goes through the new `utils/basePath.ts` — the axios instances, the WebSocket URL, the live-view route, the local brand icons, and the OIDC login href. `resolveServerPath` covers what the *backend* hands back, which is always root-absolute because it cannot know where the SPA is mounted: uploaded floor-plan URLs already stored in a canvas are resolved at render time, so plans predating the move keep loading. The OIDC callback used to redirect to `/`, dropping subpath users at the origin root after login; it now reads the prefix back out of `OIDC_REDIRECT_URI`. The default is `/`, and stays a no-op there by construction: every helper returns the string it returned before, the root build output is unchanged and both nginx sites are byte-identical to what shipped — the Docker image copies `docker/nginx.conf` verbatim and the installer keeps its original heredoc. Only a non-default prefix takes the generated config. Those generated configs use `root`, never `alias`, since `alias` plus `try_files` mis-resolves `$uri` — the Docker build lands the bundle in the matching subdirectory, and the installer symlinks it under `/var/www/homelable`. Both accept either reverse-proxy style, prefix forwarded intact or already stripped, with no redirect loop between them, and `absolute_redirect off` stops the no-slash 301 from eating the port. Closes #334. ha-relevant: maybe
55 lines
2.2 KiB
Docker
55 lines
2.2 KiB
Docker
# Stage 1: build
|
|
# Use the native build platform so npm ci never runs under QEMU emulation.
|
|
# The build output (static HTML/JS/CSS) is platform-independent.
|
|
# node:20-slim (Debian/glibc) avoids lightningcss musl binary resolution issues on Alpine.
|
|
FROM --platform=$BUILDPLATFORM node:20-slim AS builder
|
|
|
|
ARG VITE_STANDALONE=false
|
|
ENV VITE_STANDALONE=$VITE_STANDALONE
|
|
|
|
# Subpath support: build with VITE_BASE_PATH=/homelab/ to serve Homelable under a
|
|
# prefix instead of the root of an origin. The default '/' produces the same build
|
|
# as before the option existed.
|
|
ARG VITE_BASE_PATH=/
|
|
ENV VITE_BASE_PATH=$VITE_BASE_PATH
|
|
|
|
WORKDIR /app
|
|
COPY frontend/package*.json ./
|
|
RUN npm ci
|
|
|
|
COPY frontend/ .
|
|
COPY VERSION ../VERSION
|
|
RUN npm run build
|
|
|
|
# Stage 2: serve
|
|
FROM nginx:alpine
|
|
|
|
COPY --from=builder /app/dist /tmp/dist
|
|
|
|
# Nginx config: standalone (no backend proxy) or full, served at the root or under the
|
|
# VITE_BASE_PATH prefix. At the root the build lands in /usr/share/nginx/html and the
|
|
# shipped .conf is copied verbatim, so an existing deployment gets exactly what it had
|
|
# before this option existed. Under a prefix the build lands in the matching
|
|
# subdirectory, which keeps the generated config on plain `root` semantics.
|
|
ARG VITE_STANDALONE=false
|
|
ARG VITE_BASE_PATH=/
|
|
COPY docker/nginx.conf docker/nginx.standalone.conf /tmp/nginx-conf/
|
|
COPY docker/nginx.subpath.conf.template docker/nginx.standalone.subpath.conf.template /tmp/nginx-conf/
|
|
# ash with pipefail: the base-path normalization below pipes printf into sed.
|
|
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
|
RUN set -eu; \
|
|
if [ "$VITE_STANDALONE" = "true" ]; then variant=nginx.standalone; else variant=nginx; fi; \
|
|
base=$(printf '%s' "${VITE_BASE_PATH:-/}" | sed -e 's#^/*#/#' -e 's#/*$#/#' -e 's#//*#/#g'); \
|
|
[ -n "$base" ] || base=/; \
|
|
mkdir -p "/usr/share/nginx/html${base}"; \
|
|
cp -a /tmp/dist/. "/usr/share/nginx/html${base}"; \
|
|
if [ "$base" = "/" ]; then \
|
|
cp "/tmp/nginx-conf/${variant}.conf" /etc/nginx/conf.d/default.conf; \
|
|
else \
|
|
sed -e "s#__BASE_NO_SLASH__#${base%/}#g" -e "s#__BASE__#${base}#g" \
|
|
"/tmp/nginx-conf/${variant}.subpath.conf.template" > /etc/nginx/conf.d/default.conf; \
|
|
fi; \
|
|
rm -rf /tmp/dist /tmp/nginx-conf
|
|
|
|
EXPOSE 80
|