feat: custom Logto image + auto-redirect to sign-in
All checks were successful
CI / build (push) Successful in 39s
CI / docker (push) Successful in 40s

- Add docker/logto.Dockerfile: builds custom Logto image with sign-in
  UI baked into /etc/logto/packages/experience/dist/
- Remove sign-in-ui init container, signinui volume, CUSTOM_UI_PATH
  (CUSTOM_UI_PATH is Logto Cloud only, not available in OSS)
- Remove sign-in build stage from SaaS Dockerfile (now in logto.Dockerfile)
- Remove docker/saas-entrypoint.sh (no longer needed)
- LoginPage auto-redirects to Logto OIDC on mount instead of showing
  "Sign in with Logto" button — seamless sign-in experience

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-06 15:12:11 +02:00
parent 9013740b83
commit 972f9b5f38
5 changed files with 28 additions and 51 deletions

View File

@@ -9,15 +9,6 @@ RUN echo "//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_T
COPY ui/ . COPY ui/ .
RUN npm run build RUN npm run build
# Sign-in UI: custom Logto sign-in experience
FROM --platform=$BUILDPLATFORM node:22-alpine AS sign-in-frontend
ARG REGISTRY_TOKEN
WORKDIR /ui
COPY ui/sign-in/package.json ui/sign-in/package-lock.json ui/sign-in/.npmrc ./
RUN echo "//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}" >> .npmrc && npm ci
COPY ui/sign-in/ .
RUN npm run build
# Maven build: runs natively on build host (no QEMU emulation) # Maven build: runs natively on build host (no QEMU emulation)
FROM --platform=$BUILDPLATFORM eclipse-temurin:21-jdk-alpine AS build FROM --platform=$BUILDPLATFORM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /build WORKDIR /build
@@ -34,7 +25,6 @@ FROM eclipse-temurin:21-jre-alpine
WORKDIR /app WORKDIR /app
RUN addgroup -S cameleer && adduser -S cameleer -G cameleer RUN addgroup -S cameleer && adduser -S cameleer -G cameleer
COPY --from=build /build/target/*.jar app.jar COPY --from=build /build/target/*.jar app.jar
COPY --from=sign-in-frontend /ui/dist/ /app/sign-in-dist/
USER cameleer USER cameleer
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"] ENTRYPOINT ["java", "-jar", "app.jar"]

View File

@@ -58,16 +58,15 @@ services:
- cameleer - cameleer
logto: logto:
image: ghcr.io/logto-io/logto:latest image: ${LOGTO_IMAGE:-gitea.siegeln.net/cameleer/cameleer-logto}:${VERSION:-latest}
build:
context: .
dockerfile: docker/logto.Dockerfile
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
sign-in-ui:
condition: service_completed_successfully
entrypoint: ["sh", "-c", "npm run cli db seed -- --swe && npm start"] entrypoint: ["sh", "-c", "npm run cli db seed -- --swe && npm start"]
volumes:
- signinui:/etc/logto/packages/experience/dist
environment: environment:
DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD:-cameleer_dev}@postgres:5432/logto DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD:-cameleer_dev}@postgres:5432/logto
ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost} ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
@@ -122,14 +121,6 @@ services:
networks: networks:
- cameleer - cameleer
sign-in-ui:
image: ${CAMELEER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-saas}:${VERSION:-latest}
restart: "no"
user: root
entrypoint: ["sh", "-c", "cp -r /app/sign-in-dist/* /data/sign-in-ui/ && echo '[sign-in-ui] Copied custom UI to shared volume'"]
volumes:
- signinui:/data/sign-in-ui
cameleer-saas: cameleer-saas:
image: ${CAMELEER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-saas}:${VERSION:-latest} image: ${CAMELEER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-saas}:${VERSION:-latest}
restart: unless-stopped restart: unless-stopped
@@ -250,4 +241,3 @@ volumes:
certs: certs:
jardata: jardata:
bootstrapdata: bootstrapdata:
signinui:

14
docker/logto.Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
# syntax=docker/dockerfile:1
# Build custom sign-in UI
FROM --platform=$BUILDPLATFORM node:22-alpine AS sign-in
ARG REGISTRY_TOKEN
WORKDIR /ui
COPY ui/sign-in/package.json ui/sign-in/package-lock.json ui/sign-in/.npmrc ./
RUN echo "//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}" >> .npmrc && npm ci
COPY ui/sign-in/ .
RUN npm run build
# Custom Logto with baked-in sign-in UI
FROM ghcr.io/logto-io/logto:latest
COPY --from=sign-in /ui/dist/ /etc/logto/packages/experience/dist/

View File

@@ -1,7 +0,0 @@
#!/bin/sh
# Copy sign-in UI dist to shared volume for Logto's CUSTOM_UI_PATH
if [ -d /app/sign-in-dist ] && [ -d /data/sign-in-ui ]; then
cp -r /app/sign-in-dist/* /data/sign-in-ui/
echo "[saas] Copied sign-in UI to shared volume"
fi
exec java -jar /app/app.jar "$@"

View File

@@ -1,11 +1,12 @@
import { useEffect } from 'react'; import { useEffect, useRef } from 'react';
import { useLogto } from '@logto/react'; import { useLogto } from '@logto/react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { Button, Spinner } from '@cameleer/design-system'; import { Spinner } from '@cameleer/design-system';
export function LoginPage() { export function LoginPage() {
const { signIn, isAuthenticated, isLoading } = useLogto(); const { signIn, isAuthenticated, isLoading } = useLogto();
const navigate = useNavigate(); const navigate = useNavigate();
const redirected = useRef(false);
useEffect(() => { useEffect(() => {
if (isAuthenticated) { if (isAuthenticated) {
@@ -13,27 +14,16 @@ export function LoginPage() {
} }
}, [isAuthenticated, navigate]); }, [isAuthenticated, navigate]);
if (isLoading) { useEffect(() => {
return ( if (!isLoading && !isAuthenticated && !redirected.current) {
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}> redirected.current = true;
<Spinner /> signIn(`${window.location.origin}/platform/callback`);
</div> }
); }, [isLoading, isAuthenticated, signIn]);
}
const handleLogin = () => {
signIn(`${window.location.origin}/platform/callback`);
};
return ( return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}>
<div style={{ textAlign: 'center' }}> <Spinner />
<h1>Cameleer SaaS</h1>
<p style={{ marginBottom: '2rem', color: 'var(--color-text-secondary)' }}>
Managed Apache Camel Runtime
</p>
<Button onClick={handleLogin}>Sign in with Logto</Button>
</div>
</div> </div>
); );
} }