diff --git a/.env.example b/.env.example
index 60c77c5d..31ee2b4b 100644
--- a/.env.example
+++ b/.env.example
@@ -76,8 +76,16 @@ JMAP_SERVER_URL=https://your-jmap-server.com
# Login Page Customization (all optional)
# =============================================================================
+# Custom logo images for the login page (PNG, SVG, etc.)
+# Can be absolute URLs or paths relative to the public/ directory.
+# Light mode logo (shown on light backgrounds), defaults to Bulwark logo.
+LOGIN_LOGO_LIGHT_URL=/branding/Bulwark_Logo_Color.svg
+#
+# Dark mode logo (shown on dark backgrounds), defaults to Bulwark white logo.
+LOGIN_LOGO_DARK_URL=/branding/Bulwark_Logo_Color.svg
+
# Company or organization name displayed above the version on the login page
-# LOGIN_COMPANY_NAME=My Company
+LOGIN_COMPANY_NAME=Bulwark Webmail
# URL for the imprint/legal notice link on the login page
# LOGIN_IMPRINT_URL=https://example.com/imprint
@@ -86,7 +94,7 @@ JMAP_SERVER_URL=https://your-jmap-server.com
# LOGIN_PRIVACY_POLICY_URL=https://example.com/privacy
# URL for the company website link on the login page
-# LOGIN_WEBSITE_URL=https://example.com
+LOGIN_WEBSITE_URL=https://bulwarkmail.org
# =============================================================================
# Legacy Build-time Variables (still supported as fallback)
diff --git a/app/[locale]/login/page.tsx b/app/[locale]/login/page.tsx
index 99433236..75fab314 100644
--- a/app/[locale]/login/page.tsx
+++ b/app/[locale]/login/page.tsx
@@ -28,8 +28,9 @@ export default function LoginPage() {
const t = useTranslations("login");
const params = useParams();
const { login, isLoading, error, clearError, isAuthenticated } = useAuthStore();
- const { theme, setTheme, initializeTheme } = useThemeStore();
- const { appName, jmapServerUrl: serverUrl, oauthEnabled, oauthOnly, oauthClientId, oauthIssuerUrl, rememberMeEnabled, devMode, loginCompanyName, loginImprintUrl, loginPrivacyPolicyUrl, loginWebsiteUrl, isLoading: configLoading, error: configError } = useConfig();
+ const { theme, setTheme, initializeTheme } = useThemeStore((s) => ({ theme: s.theme, setTheme: s.setTheme, initializeTheme: s.initializeTheme }));
+ const { appName, jmapServerUrl: serverUrl, oauthEnabled, oauthOnly, oauthClientId, oauthIssuerUrl, rememberMeEnabled, devMode, loginLogoLightUrl, loginLogoDarkUrl, loginCompanyName, loginImprintUrl, loginPrivacyPolicyUrl, loginWebsiteUrl, isLoading: configLoading, error: configError } = useConfig();
+ const resolvedTheme = useThemeStore((s) => s.resolvedTheme);
const [formData, setFormData] = useState({
username: "",
@@ -416,8 +417,12 @@ export default function LoginPage() {
{/* Header section with logo */}
-
-
+
+
{appName}
diff --git a/app/api/config/route.ts b/app/api/config/route.ts
index c5bde963..92cb4b1d 100644
--- a/app/api/config/route.ts
+++ b/app/api/config/route.ts
@@ -26,6 +26,8 @@ export async function GET() {
settingsSyncEnabled: process.env.SETTINGS_SYNC_ENABLED === 'true' && !!process.env.SESSION_SECRET,
stalwartFeaturesEnabled: process.env.STALWART_FEATURES !== 'false',
devMode: process.env.DEV_MOCK_JMAP === 'true',
+ loginLogoLightUrl: process.env.LOGIN_LOGO_LIGHT_URL || '/branding/Bulwark_Logo_Color.svg',
+ loginLogoDarkUrl: process.env.LOGIN_LOGO_DARK_URL || '/branding/Bulwark_Logo_White.svg',
loginCompanyName: process.env.LOGIN_COMPANY_NAME || '',
loginImprintUrl: process.env.LOGIN_IMPRINT_URL || '',
loginPrivacyPolicyUrl: process.env.LOGIN_PRIVACY_POLICY_URL || '',
diff --git a/hooks/use-config.ts b/hooks/use-config.ts
index 31fc5394..1e3bf653 100644
--- a/hooks/use-config.ts
+++ b/hooks/use-config.ts
@@ -13,6 +13,8 @@ interface ConfigData {
settingsSyncEnabled: boolean;
stalwartFeaturesEnabled: boolean;
devMode: boolean;
+ loginLogoLightUrl: string;
+ loginLogoDarkUrl: string;
loginCompanyName: string;
loginImprintUrl: string;
loginPrivacyPolicyUrl: string;
@@ -77,6 +79,8 @@ export function useConfig(): AppConfig {
settingsSyncEnabled: configCache?.settingsSyncEnabled || false,
stalwartFeaturesEnabled: configCache?.stalwartFeaturesEnabled ?? true,
devMode: configCache?.devMode || false,
+ loginLogoLightUrl: configCache?.loginLogoLightUrl || '/branding/Bulwark_Logo_Color.svg',
+ loginLogoDarkUrl: configCache?.loginLogoDarkUrl || '/branding/Bulwark_Logo_White.svg',
loginCompanyName: configCache?.loginCompanyName || '',
loginImprintUrl: configCache?.loginImprintUrl || '',
loginPrivacyPolicyUrl: configCache?.loginPrivacyPolicyUrl || '',
@@ -99,6 +103,8 @@ export function useConfig(): AppConfig {
settingsSyncEnabled: configCache.settingsSyncEnabled,
stalwartFeaturesEnabled: configCache.stalwartFeaturesEnabled,
devMode: configCache.devMode,
+ loginLogoLightUrl: configCache.loginLogoLightUrl,
+ loginLogoDarkUrl: configCache.loginLogoDarkUrl,
loginCompanyName: configCache.loginCompanyName,
loginImprintUrl: configCache.loginImprintUrl,
loginPrivacyPolicyUrl: configCache.loginPrivacyPolicyUrl,
@@ -122,6 +128,8 @@ export function useConfig(): AppConfig {
settingsSyncEnabled: data.settingsSyncEnabled,
stalwartFeaturesEnabled: data.stalwartFeaturesEnabled,
devMode: data.devMode,
+ loginLogoLightUrl: data.loginLogoLightUrl,
+ loginLogoDarkUrl: data.loginLogoDarkUrl,
loginCompanyName: data.loginCompanyName,
loginImprintUrl: data.loginImprintUrl,
loginPrivacyPolicyUrl: data.loginPrivacyPolicyUrl,
diff --git a/public/branding/Bulwark Favicon.png b/public/branding/Bulwark_Favicon.png
similarity index 100%
rename from public/branding/Bulwark Favicon.png
rename to public/branding/Bulwark_Favicon.png
diff --git a/public/branding/Bulwark Favicon.svg b/public/branding/Bulwark_Favicon.svg
similarity index 100%
rename from public/branding/Bulwark Favicon.svg
rename to public/branding/Bulwark_Favicon.svg
diff --git a/public/branding/Bulwark Logo Color.png b/public/branding/Bulwark_Logo_Color.png
similarity index 100%
rename from public/branding/Bulwark Logo Color.png
rename to public/branding/Bulwark_Logo_Color.png
diff --git a/public/branding/Bulwark Logo Color.svg b/public/branding/Bulwark_Logo_Color.svg
similarity index 100%
rename from public/branding/Bulwark Logo Color.svg
rename to public/branding/Bulwark_Logo_Color.svg
diff --git a/public/branding/Bulwark Logo Dark.png b/public/branding/Bulwark_Logo_Dark.png
similarity index 100%
rename from public/branding/Bulwark Logo Dark.png
rename to public/branding/Bulwark_Logo_Dark.png
diff --git a/public/branding/Bulwark Logo Dark.svg b/public/branding/Bulwark_Logo_Dark.svg
similarity index 100%
rename from public/branding/Bulwark Logo Dark.svg
rename to public/branding/Bulwark_Logo_Dark.svg
diff --git a/public/branding/Bulwark Logo White.png b/public/branding/Bulwark_Logo_White.png
similarity index 100%
rename from public/branding/Bulwark Logo White.png
rename to public/branding/Bulwark_Logo_White.png
diff --git a/public/branding/Bulwark Logo White.svg b/public/branding/Bulwark_Logo_White.svg
similarity index 100%
rename from public/branding/Bulwark Logo White.svg
rename to public/branding/Bulwark_Logo_White.svg
diff --git a/public/branding/Bulwark Logo with Lettering Dark Color.svg b/public/branding/Bulwark_Logo_with_Lettering_Dark_Color.svg
similarity index 100%
rename from public/branding/Bulwark Logo with Lettering Dark Color.svg
rename to public/branding/Bulwark_Logo_with_Lettering_Dark_Color.svg
diff --git a/public/branding/Bulwark Logo with Lettering Dark and Color.png b/public/branding/Bulwark_Logo_with_Lettering_Dark_and_Color.png
similarity index 100%
rename from public/branding/Bulwark Logo with Lettering Dark and Color.png
rename to public/branding/Bulwark_Logo_with_Lettering_Dark_and_Color.png
diff --git a/public/branding/Bulwark Logo with Lettering White and Color.png b/public/branding/Bulwark_Logo_with_Lettering_White_and_Color.png
similarity index 100%
rename from public/branding/Bulwark Logo with Lettering White and Color.png
rename to public/branding/Bulwark_Logo_with_Lettering_White_and_Color.png
diff --git a/public/branding/Bulwark Logo with Lettering White and Color.svg b/public/branding/Bulwark_Logo_with_Lettering_White_and_Color.svg
similarity index 100%
rename from public/branding/Bulwark Logo with Lettering White and Color.svg
rename to public/branding/Bulwark_Logo_with_Lettering_White_and_Color.svg
diff --git a/setup.sh b/setup.sh
index 891c7a13..952e51eb 100644
--- a/setup.sh
+++ b/setup.sh
@@ -65,6 +65,8 @@ CFG_SETTINGS_DATA_DIR="./data/settings"
CFG_LOG_FORMAT="text"
CFG_LOG_LEVEL="info"
CFG_LOGIN_COMPANY_NAME=""
+CFG_LOGIN_LOGO_LIGHT_URL=""
+CFG_LOGIN_LOGO_DARK_URL=""
CFG_LOGIN_IMPRINT_URL=""
CFG_LOGIN_PRIVACY_POLICY_URL=""
CFG_LOGIN_WEBSITE_URL=""
@@ -768,13 +770,19 @@ screen_login_customization() {
echo -e " ${DIM}Shown on the login page footer. Example: Acme Corp${RESET}"
prompt_value "Company / organization name" "$CFG_LOGIN_COMPANY_NAME" "CFG_LOGIN_COMPANY_NAME"
echo ""
+ echo -e " ${DIM}Custom logo URLs for the login page (PNG, SVG, etc.)${RESET}"
+ echo -e " ${DIM}Leave blank to use the default Bulwark logo.${RESET}"
+ prompt_value "Logo URL (light mode)" "$CFG_LOGIN_LOGO_LIGHT_URL" "CFG_LOGIN_LOGO_LIGHT_URL"
+ echo ""
+ prompt_value "Logo URL (dark mode)" "$CFG_LOGIN_LOGO_DARK_URL" "CFG_LOGIN_LOGO_DARK_URL"
+ echo ""
prompt_value "Website URL" "$CFG_LOGIN_WEBSITE_URL" "CFG_LOGIN_WEBSITE_URL"
echo ""
prompt_value "Imprint / legal notice URL" "$CFG_LOGIN_IMPRINT_URL" "CFG_LOGIN_IMPRINT_URL"
echo ""
prompt_value "Privacy policy URL" "$CFG_LOGIN_PRIVACY_POLICY_URL" "CFG_LOGIN_PRIVACY_POLICY_URL"
- if [[ -z "$CFG_LOGIN_COMPANY_NAME" && -z "$CFG_LOGIN_WEBSITE_URL" && -z "$CFG_LOGIN_IMPRINT_URL" && -z "$CFG_LOGIN_PRIVACY_POLICY_URL" ]]; then
+ if [[ -z "$CFG_LOGIN_COMPANY_NAME" && -z "$CFG_LOGIN_LOGO_LIGHT_URL" && -z "$CFG_LOGIN_LOGO_DARK_URL" && -z "$CFG_LOGIN_WEBSITE_URL" && -z "$CFG_LOGIN_IMPRINT_URL" && -z "$CFG_LOGIN_PRIVACY_POLICY_URL" ]]; then
echo ""
note "No branding configured. The login page will show defaults."
fi
@@ -884,9 +892,13 @@ screen_summary() {
# Login page
echo -e " ${CYAN}${BOLD}BRANDING${RESET}"
- if [[ -n "$CFG_LOGIN_COMPANY_NAME" || -n "$CFG_LOGIN_WEBSITE_URL" || -n "$CFG_LOGIN_IMPRINT_URL" || -n "$CFG_LOGIN_PRIVACY_POLICY_URL" ]]; then
+ if [[ -n "$CFG_LOGIN_COMPANY_NAME" || -n "$CFG_LOGIN_LOGO_LIGHT_URL" || -n "$CFG_LOGIN_LOGO_DARK_URL" || -n "$CFG_LOGIN_WEBSITE_URL" || -n "$CFG_LOGIN_IMPRINT_URL" || -n "$CFG_LOGIN_PRIVACY_POLICY_URL" ]]; then
[[ -n "$CFG_LOGIN_COMPANY_NAME" ]] && \
echo -e " Company Name .......... ${BOLD}${CFG_LOGIN_COMPANY_NAME}${RESET}"
+ [[ -n "$CFG_LOGIN_LOGO_LIGHT_URL" ]] && \
+ echo -e " Logo (light mode) ..... ${BOLD}${CFG_LOGIN_LOGO_LIGHT_URL}${RESET}"
+ [[ -n "$CFG_LOGIN_LOGO_DARK_URL" ]] && \
+ echo -e " Logo (dark mode) ...... ${BOLD}${CFG_LOGIN_LOGO_DARK_URL}${RESET}"
[[ -n "$CFG_LOGIN_WEBSITE_URL" ]] && \
echo -e " Website URL ........... ${BOLD}${CFG_LOGIN_WEBSITE_URL}${RESET}"
[[ -n "$CFG_LOGIN_IMPRINT_URL" ]] && \
@@ -995,6 +1007,8 @@ LOG_LEVEL=${CFG_LOG_LEVEL}
ENVEOF
[[ -n "$CFG_LOGIN_COMPANY_NAME" ]] && echo "LOGIN_COMPANY_NAME=${CFG_LOGIN_COMPANY_NAME}" >> "$ENV_FILE"
+ [[ -n "$CFG_LOGIN_LOGO_LIGHT_URL" ]] && echo "LOGIN_LOGO_LIGHT_URL=${CFG_LOGIN_LOGO_LIGHT_URL}" >> "$ENV_FILE"
+ [[ -n "$CFG_LOGIN_LOGO_DARK_URL" ]] && echo "LOGIN_LOGO_DARK_URL=${CFG_LOGIN_LOGO_DARK_URL}" >> "$ENV_FILE"
[[ -n "$CFG_LOGIN_IMPRINT_URL" ]] && echo "LOGIN_IMPRINT_URL=${CFG_LOGIN_IMPRINT_URL}" >> "$ENV_FILE"
[[ -n "$CFG_LOGIN_PRIVACY_POLICY_URL" ]] && echo "LOGIN_PRIVACY_POLICY_URL=${CFG_LOGIN_PRIVACY_POLICY_URL}" >> "$ENV_FILE"
[[ -n "$CFG_LOGIN_WEBSITE_URL" ]] && echo "LOGIN_WEBSITE_URL=${CFG_LOGIN_WEBSITE_URL}" >> "$ENV_FILE"
@@ -1204,6 +1218,8 @@ load_existing_config() {
get_env_val "LOG_FORMAT"; [[ -n "$val" ]] && CFG_LOG_FORMAT="$val"
get_env_val "LOG_LEVEL"; [[ -n "$val" ]] && CFG_LOG_LEVEL="$val"
get_env_val "LOGIN_COMPANY_NAME"; [[ -n "$val" ]] && CFG_LOGIN_COMPANY_NAME="$val"
+ get_env_val "LOGIN_LOGO_LIGHT_URL"; [[ -n "$val" ]] && CFG_LOGIN_LOGO_LIGHT_URL="$val"
+ get_env_val "LOGIN_LOGO_DARK_URL"; [[ -n "$val" ]] && CFG_LOGIN_LOGO_DARK_URL="$val"
get_env_val "LOGIN_IMPRINT_URL"; [[ -n "$val" ]] && CFG_LOGIN_IMPRINT_URL="$val"
get_env_val "LOGIN_PRIVACY_POLICY_URL"; [[ -n "$val" ]] && CFG_LOGIN_PRIVACY_POLICY_URL="$val"
get_env_val "LOGIN_WEBSITE_URL"; [[ -n "$val" ]] && CFG_LOGIN_WEBSITE_URL="$val"