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'),