From 92bcb58f76d5b8518cf0a248a83a931659ddc1ad Mon Sep 17 00:00:00 2001 From: John Coffey Date: Sat, 19 Sep 2026 00:01:57 -0700 Subject: [PATCH] AGPL source offer, notices and name cleanup Every build writes the exact tree it was built from, uncommitted work included, as source.tar.gz next to the app, and names that tree. The sidebar, user menu, sign-in card and version tooltip link to it. Coffey Labs' copyright line is added below Stalwart Labs' in every inherited file changed, and the new files carry Coffey Labs' alone. The upgrade prompt and its links are gone, the edition tooltip is neutral, storage keys and the package description are INBUXA's own, and the README states the lineage once, in the fine print. --- README.md | 38 ++++++----- package.json | 2 +- source-archive.ts | 70 ++++++++++++++++++++ src/components/bootstrap/BootstrapWizard.tsx | 1 + src/components/common/EnterpriseUpsell.tsx | 53 +++------------ src/components/common/Logo.tsx | 3 +- src/components/common/SourceLink.tsx | 21 ++++++ src/components/forms/DynamicForm.tsx | 3 +- src/components/forms/OtpAuthField.tsx | 1 + src/components/layout/Sidebar.tsx | 7 ++ src/components/layout/TopBar.tsx | 22 +++--- src/hooks/useDocumentTitle.ts | 1 + src/i18n/en.json | 15 ++--- src/index.css | 1 + src/lib/lastVisited.ts | 3 +- src/lib/oauthClientId.test.ts | 1 + src/lib/oauthClientId.ts | 1 + src/lib/schemaResolver.test.ts | 5 +- src/lib/sievepad.ts | 3 +- src/lib/sourceDownload.ts | 12 ++++ src/main.tsx | 3 +- src/pages/LoginPage.tsx | 5 ++ src/services/api.ts | 1 + src/services/auth/oauth.test.ts | 1 + src/services/auth/oauth.ts | 3 +- src/stores/accountStore.test.ts | 1 + src/stores/accountStore.ts | 1 + src/stores/authStore.ts | 3 +- src/stores/uiStore.ts | 3 +- src/vite-env.d.ts | 2 + tsconfig.node.json | 2 +- vite.config.ts | 9 ++- 32 files changed, 205 insertions(+), 92 deletions(-) create mode 100644 source-archive.ts create mode 100644 src/components/common/SourceLink.tsx create mode 100644 src/lib/sourceDownload.ts diff --git a/README.md b/README.md index d081570..6e9ffdc 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,18 @@ The administration interface for the INBUXA mail server: every server setting, first-boot setup, and recovery, in the browser. -It is a fork of [Stalwart WebUI](https://github.com/stalwartlabs/webui). Like -upstream it is schema-driven. After signing in it fetches the server's schema -and builds every form, list and menu from it, so it covers every setting the +It is schema-driven. After signing in it fetches the server's schema and +builds every form, list and menu from it, so it covers every setting the server has without hardcoding any of them. > **Status: in development, not released.** -## What's different from upstream +## Design -- **One edition.** Nothing is hidden or marked as Enterprise-only. INBUXA - ships every feature to everybody. See the INBUXA server's `docs/spec/`. -- **Runs anywhere, not on the mail server.** Upstream is installed onto the - mail server itself. INBUXA Admin is its own deployment, pointed at the server +- **One edition.** Every feature the server has is available here, with + nothing held back. See the INBUXA server's `docs/spec/`. +- **Runs anywhere, not on the mail server.** INBUXA Admin is its own + deployment, never installed onto the mail server. It's pointed at the server either at build time (`VITE_API_BASE_URL`) or at deploy time: `` in `index.html`. Hosted like that, it signs in as the OAuth client @@ -41,8 +40,8 @@ npm run build ## Keeping up with upstream -Upstream's history contains no Enterprise-only code, so this is an ordinary -git fork. `upstream` is a fetch-only remote: +The upstream codebase's history contains no code under a proprietary license, +so this is an ordinary git fork. `upstream` is a fetch-only remote: ```bash git fetch upstream --tags @@ -52,13 +51,22 @@ git merge v1.0.12 # the next release tag ## Versions INBUXA Admin has its own dated version (`inbuxa-version.json`), shown with the -WebUI release it's based on: `INBUXA Admin 2026.9.18 (WebUI 1.0.11)`. +upstream release it's based on: `INBUXA Admin 2026.9.18 (base 1.0.11)`. `package.json` keeps upstream's version, so upstream's bumps merge cleanly. +## Source code + +Every build carries its own source. The interface links to it (the user menu +and the sign-in page), and the build writes it next to the app as +`source.tar.gz`: the exact tree the running version was built from. + ## License and credits Free software under the [GNU Affero General Public License, version 3](./LICENSES/AGPL-3.0-only.txt). -A fork of Stalwart WebUI, copyright © Stalwart Labs LLC. Upstream's files are -dual-licensed AGPL-3.0-only or Stalwart's Enterprise License, and INBUXA takes -them under the AGPL-3.0 only. Upstream's copyright notices are kept on every -file. INBUXA isn't affiliated with or endorsed by Stalwart Labs. + +INBUXA Admin is forked from the upstream AGPL-3.0 web administration codebase +originally developed by Stalwart Labs. Their copyright notices are kept on +every file inherited from it, and INBUXA's own notice is added to the files it +changes. Those files are offered upstream under the AGPL-3.0-only or a +proprietary license. INBUXA uses them under the AGPL-3.0 only. INBUXA isn't +affiliated with or endorsed by Stalwart Labs. diff --git a/package.json b/package.json index ccb2280..8701466 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "inbuxa-admin", "private": true, "version": "1.0.11", - "description": "INBUXA Admin, a fork of Stalwart WebUI", + "description": "INBUXA Admin, the administration interface for the INBUXA mail server", "type": "module", "scripts": { "dev": "vite", diff --git a/source-archive.ts b/source-archive.ts new file mode 100644 index 0000000..0ef6d4b --- /dev/null +++ b/source-archive.ts @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2026 Coffey Labs + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* + * The AGPL's offer, for this build: the exact source it was built from, + * written next to the app as `source.tar.gz`, and an identity for it that the + * interface shows with the download link. + * + * "Exact" includes uncommitted work, new files too: every file git doesn't + * ignore is written into a throwaway index, never the real one, and the tree + * that makes is what gets archived. A clean tree is HEAD's tree. Outside a git + * checkout (a release tarball, say), the project files are packed as they are. + */ +import { execFileSync } from 'node:child_process'; +import { existsSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import type { Plugin } from 'vite'; + +const EXCLUDE = ['node_modules', 'dist', '.git', '.ignore', 'coverage']; + +function git(args: string[], cwd: string, env?: NodeJS.ProcessEnv): string { + return execFileSync('git', args, { cwd, env: env ?? process.env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); +} + +/** The tree this build is made from: its git tree id, and a name that says whether it has uncommitted work. */ +export function sourceIdentity(root: string): { ref: string | null; id: string } { + try { + git(['rev-parse', '--is-inside-work-tree'], root); + } catch { + return { ref: null, id: 'unversioned' }; + } + const dir = mkdtempSync(path.join(tmpdir(), 'inbuxa-source-')); + try { + const env = { ...process.env, GIT_INDEX_FILE: path.join(dir, 'index') }; + git(['read-tree', 'HEAD'], root, env); + git(['add', '--all', '.'], root, env); + const tree = git(['write-tree'], root, env); + const headTree = git(['rev-parse', 'HEAD^{tree}'], root); + const head = git(['rev-parse', '--short=12', 'HEAD'], root); + return tree === headTree ? { ref: tree, id: head } : { ref: tree, id: `${head}+local-${tree.slice(0, 12)}` }; + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +export function sourceArchive(root: string, name: string, identity: { ref: string | null; id: string }): Plugin { + return { + name: 'inbuxa-source-archive', + apply: 'build', + closeBundle() { + const outDir = path.join(root, 'dist'); + if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true }); + const out = path.join(outDir, 'source.tar.gz'); + const prefix = `${name}-${identity.id}/`; + if (identity.ref) { + execFileSync('git', ['archive', '--format=tar.gz', `--prefix=${prefix}`, '-o', out, identity.ref], { cwd: root }); + } else { + execFileSync( + 'tar', + [...EXCLUDE.map((e) => `--exclude=./${e}`), `--transform=s,^\\.,${prefix.slice(0, -1)},`, '-czf', out, '.'], + { cwd: root }, + ); + } + }, + }; +} diff --git a/src/components/bootstrap/BootstrapWizard.tsx b/src/components/bootstrap/BootstrapWizard.tsx index 3403ee6..5e1f24d 100644 --- a/src/components/bootstrap/BootstrapWizard.tsx +++ b/src/components/bootstrap/BootstrapWizard.tsx @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ diff --git a/src/components/common/EnterpriseUpsell.tsx b/src/components/common/EnterpriseUpsell.tsx index 513d941..ac020e9 100644 --- a/src/components/common/EnterpriseUpsell.tsx +++ b/src/components/common/EnterpriseUpsell.tsx @@ -1,55 +1,22 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -import { useTranslation } from 'react-i18next'; - -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, - DialogFooter, -} from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; - interface EnterpriseUpsellProps { open: boolean; onClose: () => void; } -export function EnterpriseUpsell({ open, onClose }: EnterpriseUpsellProps) { - const { t } = useTranslation(); - - return ( - !isOpen && onClose()}> - - - {t('enterprise.trialTitle')} - {t('enterprise.trialDescription')} - - - - {t('enterprise.whyNotFree')} - - - - - - - ); +/** + * INBUXA: nothing to sell. There is one edition, every feature is in it, and + * the edition is never anything but complete (see accountStore), so this + * never opens. It stays as an empty component so the places upstream calls + * it from merge without conflicts. + */ +export function EnterpriseUpsell({ open }: EnterpriseUpsellProps) { + void open; + return null; } diff --git a/src/components/common/Logo.tsx b/src/components/common/Logo.tsx index e277ab1..d6fccb5 100644 --- a/src/components/common/Logo.tsx +++ b/src/components/common/Logo.tsx @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -17,7 +18,7 @@ export function DefaultLogo() { diff --git a/src/components/common/SourceLink.tsx b/src/components/common/SourceLink.tsx new file mode 100644 index 0000000..686d446 --- /dev/null +++ b/src/components/common/SourceLink.tsx @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2026 Coffey Labs + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useTranslation } from 'react-i18next'; +import { sourceDownloadUrl } from '@/lib/sourceDownload'; + +/** + * The AGPL's offer to everyone using this interface over the network: the + * exact source of the version running, with that version named. + */ +export function SourceLink({ className }: { className?: string }) { + const { t } = useTranslation(); + return ( + + {t('source.download', 'Source code of this version ({{id}}), AGPL-3.0', { id: __SOURCE_ID__ })} + + ); +} diff --git a/src/components/forms/DynamicForm.tsx b/src/components/forms/DynamicForm.tsx index 0acf12e..d3e0d48 100644 --- a/src/components/forms/DynamicForm.tsx +++ b/src/components/forms/DynamicForm.tsx @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -826,7 +827,7 @@ export function DynamicForm({ viewName, objectId }: DynamicFormProps) {
{widget}
-

{t('enterprise.featureDisabled', 'This feature requires an Enterprise license.')}

+

{t('enterprise.featureDisabled', 'This feature isn\'t available on this server.')}

diff --git a/src/components/forms/OtpAuthField.tsx b/src/components/forms/OtpAuthField.tsx index e1647d7..1405dc1 100644 --- a/src/components/forms/OtpAuthField.tsx +++ b/src/components/forms/OtpAuthField.tsx @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 300df08..9f359eb 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -11,6 +12,7 @@ const { ChevronDown, Lock } = LucideIcons; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell'; +import { SourceLink } from '@/components/common/SourceLink'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { useUIStore } from '@/stores/uiStore'; @@ -399,6 +401,11 @@ export function Sidebar() { )} + {/* INBUXA: the AGPL's offer, always on screen: the exact source of this version. */} +
+ +
+ setUpsellOpen(false)} /> diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx index 77e9d6d..0730326 100644 --- a/src/components/layout/TopBar.tsx +++ b/src/components/layout/TopBar.tsx @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -7,7 +8,7 @@ import { Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import * as LucideIcons from 'lucide-react'; -const { Sun, Moon, User, LogOut, Check, Menu, Sparkles, Search } = LucideIcons; +const { Sun, Moon, User, LogOut, Check, Menu, Search, FileCode } = LucideIcons; import { Button } from '@/components/ui/button'; import { CommandPalette } from '@/components/common/CommandPalette'; import { @@ -22,6 +23,7 @@ import { import Logo from '@/components/common/Logo'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell'; +import { sourceDownloadUrl } from '@/lib/sourceDownload'; import { visibleLayouts } from '@/lib/layout'; import { sectionLandingLink } from '@/lib/lastVisited'; import { useUIStore } from '@/stores/uiStore'; @@ -88,7 +90,7 @@ export function TopBar() { - {t('version.label', 'INBUXA Admin {{version}}', { version: __APP_VERSION__ })} + {t('version.label', 'INBUXA Admin {{version}}', { version: __APP_VERSION__ })} · {__SOURCE_ID__} @@ -176,15 +178,13 @@ export function TopBar() { )} - {edition !== 'enterprise' && ( - <> - setUpsellOpen(true)}> - - {t('tryEnterprise', 'Try Enterprise')} - - - - )} + + + + {t('source.menu', 'Source code (AGPL-3.0)')} + + + { diff --git a/src/hooks/useDocumentTitle.ts b/src/hooks/useDocumentTitle.ts index 43cc42e..3073176 100644 --- a/src/hooks/useDocumentTitle.ts +++ b/src/hooks/useDocumentTitle.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ diff --git a/src/i18n/en.json b/src/i18n/en.json index c9777fb..b5dd2fd 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -131,13 +131,7 @@ "seconds": "Seconds" }, "enterprise": { - "featureDisabled": "This feature requires an Enterprise license.", - "trialTitle": "Unlock Enterprise Features", - "trialDescription": "Get access to advanced features including multi-tenancy, AI-powered spam filtering, alerts, and more.", - "trialButton": "Start 30-Day Free Trial", - "requestTrial": "Request a free trial to unlock this feature.", - "ossHidden": "This feature is not available in the open-source edition.", - "whyNotFree": "Why is this not free?" + "featureDisabled": "This feature isn't available on this server." }, "errorBoundary": { "title": "Something went wrong", @@ -299,7 +293,7 @@ }, "logo": { "alt": "Logo", - "stalwartAlt": "INBUXA" + "inbuxaAlt": "INBUXA" }, "logout": "Logout", "version": { @@ -378,12 +372,15 @@ "traceNotFound": "Trace not found", "valuePlaceholder": "Value..." }, - "tryEnterprise": "Try Enterprise", "userMenu": "User menu", "view": { "couldNotResolve": "Could not resolve object", "failedToLoad": "Failed to load", "noGetResponse": "No get response", "objectNotFound": "Object not found" + }, + "source": { + "download": "Source code of this version ({{id}}), AGPL-3.0", + "menu": "Source code (AGPL-3.0)" } } diff --git a/src/index.css b/src/index.css index 9daf4c1..846e2c9 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ diff --git a/src/lib/lastVisited.ts b/src/lib/lastVisited.ts index 871e4d4..afa43bd 100644 --- a/src/lib/lastVisited.ts +++ b/src/lib/lastVisited.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -13,7 +14,7 @@ import { } from '@/lib/layout'; import type { Layout, Schema } from '@/types/schema'; -const STORAGE_KEY = 'stalwart-last-visited'; +const STORAGE_KEY = 'inbuxa-last-visited'; function readAll(): Record { try { diff --git a/src/lib/oauthClientId.test.ts b/src/lib/oauthClientId.test.ts index 922de26..637edc1 100644 --- a/src/lib/oauthClientId.test.ts +++ b/src/lib/oauthClientId.test.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ diff --git a/src/lib/oauthClientId.ts b/src/lib/oauthClientId.ts index efe2144..2a9e602 100644 --- a/src/lib/oauthClientId.ts +++ b/src/lib/oauthClientId.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ diff --git a/src/lib/schemaResolver.test.ts b/src/lib/schemaResolver.test.ts index 5c9781e..094c0b8 100644 --- a/src/lib/schemaResolver.test.ts +++ b/src/lib/schemaResolver.test.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -924,7 +925,7 @@ const structSchema: Schema = { allowInvalidCerts: { description: '', type: { type: 'boolean' }, update: 'mutable' }, }, defaults: { - bucket: 'stalwart', + bucket: 'mail', }, }, }, @@ -959,7 +960,7 @@ describe('buildNewObjectValue', () => { it('seeds the first variant with its @type, defaults and booleans', () => { expect(buildNewObjectValue(structSchema, 'x:Store')).toEqual({ '@type': 'S3', - bucket: 'stalwart', + bucket: 'mail', allowInvalidCerts: false, }); }); diff --git a/src/lib/sievepad.ts b/src/lib/sievepad.ts index 6b4fcd7..57abab2 100644 --- a/src/lib/sievepad.ts +++ b/src/lib/sievepad.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -10,7 +11,7 @@ const SIEVEPAD_FORMAT_VERSION = 1; const SIEVEPAD_MAX_NAME_LENGTH = 80; const SIEVEPAD_MAIN_SCRIPT = 'main'; const BASE64_CHUNK_SIZE = 0x8000; -const WARNING_DISMISSED_KEY = 'stalwart-sievepad-warning-dismissed'; +const WARNING_DISMISSED_KEY = 'inbuxa-sievepad-warning-dismissed'; const SIEVE_SCRIPT_FIELDS: Record = { 'x:SieveSystemScript': 'contents', diff --git a/src/lib/sourceDownload.ts b/src/lib/sourceDownload.ts new file mode 100644 index 0000000..93ad322 --- /dev/null +++ b/src/lib/sourceDownload.ts @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2026 Coffey Labs + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { getBasePath } from '@/lib/basePath'; + +/** Where this build's own source is: written next to the app at build time (see source-archive.ts). */ +export function sourceDownloadUrl(): string { + return `${getBasePath()}/source.tar.gz`; +} diff --git a/src/main.tsx b/src/main.tsx index 71936da..f7fc0d6 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -20,7 +21,7 @@ import { loadLogoOnce } from './lib/logoCache'; (() => { try { - const persisted = localStorage.getItem('stalwart-ui'); + const persisted = localStorage.getItem('inbuxa-ui'); if (persisted) { const parsed = JSON.parse(persisted); const theme = parsed?.state?.theme; diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 24f4182..8ee4c09 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -14,6 +15,7 @@ import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader } from '@/components/ui/card'; import { startAuthFlow } from '@/services/auth/oauth'; +import { SourceLink } from '@/components/common/SourceLink'; /** * INBUXA: straight to the server's own sign-in page, which asks for the @@ -72,6 +74,9 @@ export default function LoginPage() { )} +

+ +

diff --git a/src/services/api.ts b/src/services/api.ts index db8df65..4ad1cde 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ diff --git a/src/services/auth/oauth.test.ts b/src/services/auth/oauth.test.ts index 1563435..a1d9a4b 100644 --- a/src/services/auth/oauth.test.ts +++ b/src/services/auth/oauth.test.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ diff --git a/src/services/auth/oauth.ts b/src/services/auth/oauth.ts index 67f9fa2..b0b9c46 100644 --- a/src/services/auth/oauth.ts +++ b/src/services/auth/oauth.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -11,7 +12,7 @@ import i18n from '@/i18n'; const SCOPES = import.meta.env.VITE_OAUTH_SCOPES as string | undefined; -const SESSION_PREFIX = 'stalwart-oauth-'; +const SESSION_PREFIX = 'inbuxa-oauth-'; interface DiscoveryResponse { authorization_endpoint: string; diff --git a/src/stores/accountStore.test.ts b/src/stores/accountStore.test.ts index 0865243..aa9f60c 100644 --- a/src/stores/accountStore.test.ts +++ b/src/stores/accountStore.test.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ diff --git a/src/stores/accountStore.ts b/src/stores/accountStore.ts index d29891d..c93e24f 100644 --- a/src/stores/accountStore.ts +++ b/src/stores/accountStore.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ diff --git a/src/stores/authStore.ts b/src/stores/authStore.ts index c036264..34d15a3 100644 --- a/src/stores/authStore.ts +++ b/src/stores/authStore.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -117,7 +118,7 @@ export const useAuthStore = create()( }, }), { - name: 'stalwart-auth', + name: 'inbuxa-auth', storage: { getItem: (name) => { const value = sessionStorage.getItem(name); diff --git a/src/stores/uiStore.ts b/src/stores/uiStore.ts index 8bfd068..f6344f9 100644 --- a/src/stores/uiStore.ts +++ b/src/stores/uiStore.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -61,7 +62,7 @@ export const useUIStore = create()( }, }), { - name: 'stalwart-ui', + name: 'inbuxa-ui', partialize: (state) => ({ theme: state.theme, }), diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 37a9a68..0f4f895 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ @@ -19,3 +20,4 @@ interface ImportMeta { } declare const __APP_VERSION__: string; +declare const __SOURCE_ID__: string; diff --git a/tsconfig.node.json b/tsconfig.node.json index 5cb1aa1..5d9fcab 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -21,5 +21,5 @@ "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true }, - "include": ["vite.config.ts"] + "include": ["vite.config.ts", "source-archive.ts"] } diff --git a/vite.config.ts b/vite.config.ts index 92ce537..3ee60ba 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,13 +8,18 @@ import { version } from './package.json' with { type: 'json' } // INBUXA's own dated version lives apart from package.json, whose version // follows upstream WebUI so its bumps merge without conflicts. import inbuxa from './inbuxa-version.json' with { type: 'json' } +import { sourceArchive, sourceIdentity } from './source-archive' + +const source = sourceIdentity(import.meta.dirname) export default defineConfig({ base: './', define: { - __APP_VERSION__: JSON.stringify(`${inbuxa.version} (WebUI ${version})`), + __APP_VERSION__: JSON.stringify(`${inbuxa.version} (base ${version})`), + // The tree this build came from; source.tar.gz next to the app holds it. + __SOURCE_ID__: JSON.stringify(source.id), }, - plugins: [react(), tailwindcss()], + plugins: [react(), tailwindcss(), sourceArchive(import.meta.dirname, 'inbuxa-admin', source)], resolve: { alias: { '@': path.resolve(import.meta.dirname, './src'),