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/ .
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)
FROM --platform=$BUILDPLATFORM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /build
@@ -34,7 +25,6 @@ FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S cameleer && adduser -S cameleer -G cameleer
COPY --from=build /build/target/*.jar app.jar
COPY --from=sign-in-frontend /ui/dist/ /app/sign-in-dist/
USER cameleer
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

View File

@@ -58,16 +58,15 @@ services:
- cameleer
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
depends_on:
postgres:
condition: service_healthy
sign-in-ui:
condition: service_completed_successfully
entrypoint: ["sh", "-c", "npm run cli db seed -- --swe && npm start"]
volumes:
- signinui:/etc/logto/packages/experience/dist
environment:
DB_URL: postgres://${POSTGRES_USER:-cameleer}:${POSTGRES_PASSWORD:-cameleer_dev}@postgres:5432/logto
ENDPOINT: ${PUBLIC_PROTOCOL:-https}://${PUBLIC_HOST:-localhost}
@@ -122,14 +121,6 @@ services:
networks:
- 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:
image: ${CAMELEER_IMAGE:-gitea.siegeln.net/cameleer/cameleer-saas}:${VERSION:-latest}
restart: unless-stopped
@@ -250,4 +241,3 @@ volumes:
certs:
jardata:
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 { useNavigate } from 'react-router';
import { Button, Spinner } from '@cameleer/design-system';
import { Spinner } from '@cameleer/design-system';
export function LoginPage() {
const { signIn, isAuthenticated, isLoading } = useLogto();
const navigate = useNavigate();
const redirected = useRef(false);
useEffect(() => {
if (isAuthenticated) {
@@ -13,27 +14,16 @@ export function LoginPage() {
}
}, [isAuthenticated, navigate]);
if (isLoading) {
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}>
<Spinner />
</div>
);
}
const handleLogin = () => {
signIn(`${window.location.origin}/platform/callback`);
};
useEffect(() => {
if (!isLoading && !isAuthenticated && !redirected.current) {
redirected.current = true;
signIn(`${window.location.origin}/platform/callback`);
}
}, [isLoading, isAuthenticated, signIn]);
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}>
<div style={{ textAlign: 'center' }}>
<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>
<Spinner />
</div>
);
}