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.
This commit is contained in:
2026-09-19 00:01:57 -07:00
parent 14d708ecf8
commit 92bcb58f76
32 changed files with 205 additions and 92 deletions
+23 -15
View File
@@ -7,19 +7,18 @@
The administration interface for the INBUXA mail server: every server setting, The administration interface for the INBUXA mail server: every server setting,
first-boot setup, and recovery, in the browser. first-boot setup, and recovery, in the browser.
It is a fork of [Stalwart WebUI](https://github.com/stalwartlabs/webui). Like It is schema-driven. After signing in it fetches the server's schema and
upstream it is schema-driven. After signing in it fetches the server's schema builds every form, list and menu from it, so it covers every setting the
and builds every form, list and menu from it, so it covers every setting the
server has without hardcoding any of them. server has without hardcoding any of them.
> **Status: in development, not released.** > **Status: in development, not released.**
## What's different from upstream ## Design
- **One edition.** Nothing is hidden or marked as Enterprise-only. INBUXA - **One edition.** Every feature the server has is available here, with
ships every feature to everybody. See the INBUXA server's `docs/spec/`. nothing held back. See the INBUXA server's `docs/spec/`.
- **Runs anywhere, not on the mail server.** Upstream is installed onto the - **Runs anywhere, not on the mail server.** INBUXA Admin is its own
mail server itself. INBUXA Admin is its own deployment, pointed at the server 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: either at build time (`VITE_API_BASE_URL`) or at deploy time:
`<meta name="api-base-url" content="https://mail.example.com">` in `<meta name="api-base-url" content="https://mail.example.com">` in
`index.html`. Hosted like that, it signs in as the OAuth client `index.html`. Hosted like that, it signs in as the OAuth client
@@ -41,8 +40,8 @@ npm run build
## Keeping up with upstream ## Keeping up with upstream
Upstream's history contains no Enterprise-only code, so this is an ordinary The upstream codebase's history contains no code under a proprietary license,
git fork. `upstream` is a fetch-only remote: so this is an ordinary git fork. `upstream` is a fetch-only remote:
```bash ```bash
git fetch upstream --tags git fetch upstream --tags
@@ -52,13 +51,22 @@ git merge v1.0.12 # the next release tag
## Versions ## Versions
INBUXA Admin has its own dated version (`inbuxa-version.json`), shown with the 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. `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 ## License and credits
Free software under the [GNU Affero General Public License, version 3](./LICENSES/AGPL-3.0-only.txt). 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 INBUXA Admin is forked from the upstream AGPL-3.0 web administration codebase
them under the AGPL-3.0 only. Upstream's copyright notices are kept on every originally developed by Stalwart Labs. Their copyright notices are kept on
file. INBUXA isn't affiliated with or endorsed by Stalwart Labs. 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.
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "inbuxa-admin", "name": "inbuxa-admin",
"private": true, "private": true,
"version": "1.0.11", "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", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+70
View File
@@ -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 },
);
}
},
};
}
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
+10 -43
View File
@@ -1,55 +1,22 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * 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 { interface EnterpriseUpsellProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
} }
export function EnterpriseUpsell({ open, onClose }: EnterpriseUpsellProps) { /**
const { t } = useTranslation(); * 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
return ( * never opens. It stays as an empty component so the places upstream calls
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}> * it from merge without conflicts.
<DialogContent className="gap-6"> */
<DialogHeader className="space-y-4"> export function EnterpriseUpsell({ open }: EnterpriseUpsellProps) {
<DialogTitle>{t('enterprise.trialTitle')}</DialogTitle> void open;
<DialogDescription>{t('enterprise.trialDescription')}</DialogDescription> return null;
</DialogHeader>
<DialogFooter className="gap-2 sm:items-center">
<a
href="https://stalw.art/compare#why-isnt-feature-x-open-source"
target="_blank"
rel="noopener noreferrer"
className="text-center text-xs text-muted-foreground underline-offset-4 hover:underline sm:mr-auto sm:text-left"
>
{t('enterprise.whyNotFree')}
</a>
<Button variant="outline" onClick={onClose}>
{t('common.close')}
</Button>
<Button asChild>
<a href="https://license.stalw.art/trial" target="_blank" rel="noopener noreferrer">
{t('enterprise.trialButton')}
</a>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
} }
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
@@ -17,7 +18,7 @@ export function DefaultLogo() {
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="165 35 616 130" viewBox="165 35 616 130"
aria-label={t('logo.stalwartAlt', 'INBUXA')} aria-label={t('logo.inbuxaAlt', 'INBUXA')}
className="h-7 w-auto max-w-[320px]" className="h-7 w-auto max-w-[320px]"
> >
<image x="165.85" y="35.00" width="109.39" height="130.00" href={inbuxaMark} /> <image x="165.85" y="35.00" width="109.39" height="130.00" href={inbuxaMark} />
+21
View File
@@ -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 (
<a href={sourceDownloadUrl()} download className={className}>
{t('source.download', 'Source code of this version ({{id}}), AGPL-3.0', { id: __SOURCE_ID__ })}
</a>
);
}
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
@@ -826,7 +827,7 @@ export function DynamicForm({ viewName, objectId }: DynamicFormProps) {
<div className="opacity-60">{widget}</div> <div className="opacity-60">{widget}</div>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>{t('enterprise.featureDisabled', 'This feature requires an Enterprise license.')}</p> <p>{t('enterprise.featureDisabled', 'This feature isn\'t available on this server.')}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
+1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
+7
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
@@ -11,6 +12,7 @@ const { ChevronDown, Lock } = LucideIcons;
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell'; import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell';
import { SourceLink } from '@/components/common/SourceLink';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { useUIStore } from '@/stores/uiStore'; import { useUIStore } from '@/stores/uiStore';
@@ -399,6 +401,11 @@ export function Sidebar() {
</TooltipProvider> </TooltipProvider>
)} )}
{/* INBUXA: the AGPL's offer, always on screen: the exact source of this version. */}
<div className="border-t px-3 py-2 text-center text-[11px] leading-tight text-muted-foreground">
<SourceLink className="underline-offset-2 hover:text-foreground hover:underline" />
</div>
<EnterpriseUpsell open={upsellOpen} onClose={() => setUpsellOpen(false)} /> <EnterpriseUpsell open={upsellOpen} onClose={() => setUpsellOpen(false)} />
</aside> </aside>
</> </>
+11 -11
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
@@ -7,7 +8,7 @@
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import * as LucideIcons from 'lucide-react'; 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 { Button } from '@/components/ui/button';
import { CommandPalette } from '@/components/common/CommandPalette'; import { CommandPalette } from '@/components/common/CommandPalette';
import { import {
@@ -22,6 +23,7 @@ import {
import Logo from '@/components/common/Logo'; import Logo from '@/components/common/Logo';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell'; import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell';
import { sourceDownloadUrl } from '@/lib/sourceDownload';
import { visibleLayouts } from '@/lib/layout'; import { visibleLayouts } from '@/lib/layout';
import { sectionLandingLink } from '@/lib/lastVisited'; import { sectionLandingLink } from '@/lib/lastVisited';
import { useUIStore } from '@/stores/uiStore'; import { useUIStore } from '@/stores/uiStore';
@@ -88,7 +90,7 @@ export function TopBar() {
</Link> </Link>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="bottom"> <TooltipContent side="bottom">
{t('version.label', 'INBUXA Admin {{version}}', { version: __APP_VERSION__ })} {t('version.label', 'INBUXA Admin {{version}}', { version: __APP_VERSION__ })} · {__SOURCE_ID__}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@@ -176,15 +178,13 @@ export function TopBar() {
</> </>
)} )}
{edition !== 'enterprise' && ( <DropdownMenuItem asChild>
<> <a href={sourceDownloadUrl()} download>
<DropdownMenuItem onClick={() => setUpsellOpen(true)}> <FileCode className="mr-2 h-4 w-4" />
<Sparkles className="mr-2 h-4 w-4" /> {t('source.menu', 'Source code (AGPL-3.0)')}
{t('tryEnterprise', 'Try Enterprise')} </a>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</>
)}
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
+1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
+6 -9
View File
@@ -131,13 +131,7 @@
"seconds": "Seconds" "seconds": "Seconds"
}, },
"enterprise": { "enterprise": {
"featureDisabled": "This feature requires an Enterprise license.", "featureDisabled": "This feature isn't available on this server."
"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?"
}, },
"errorBoundary": { "errorBoundary": {
"title": "Something went wrong", "title": "Something went wrong",
@@ -299,7 +293,7 @@
}, },
"logo": { "logo": {
"alt": "Logo", "alt": "Logo",
"stalwartAlt": "INBUXA" "inbuxaAlt": "INBUXA"
}, },
"logout": "Logout", "logout": "Logout",
"version": { "version": {
@@ -378,12 +372,15 @@
"traceNotFound": "Trace not found", "traceNotFound": "Trace not found",
"valuePlaceholder": "Value..." "valuePlaceholder": "Value..."
}, },
"tryEnterprise": "Try Enterprise",
"userMenu": "User menu", "userMenu": "User menu",
"view": { "view": {
"couldNotResolve": "Could not resolve object", "couldNotResolve": "Could not resolve object",
"failedToLoad": "Failed to load", "failedToLoad": "Failed to load",
"noGetResponse": "No get response", "noGetResponse": "No get response",
"objectNotFound": "Object not found" "objectNotFound": "Object not found"
},
"source": {
"download": "Source code of this version ({{id}}), AGPL-3.0",
"menu": "Source code (AGPL-3.0)"
} }
} }
+1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
@@ -13,7 +14,7 @@ import {
} from '@/lib/layout'; } from '@/lib/layout';
import type { Layout, Schema } from '@/types/schema'; import type { Layout, Schema } from '@/types/schema';
const STORAGE_KEY = 'stalwart-last-visited'; const STORAGE_KEY = 'inbuxa-last-visited';
function readAll(): Record<string, unknown> { function readAll(): Record<string, unknown> {
try { try {
+1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
+1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
+3 -2
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
@@ -924,7 +925,7 @@ const structSchema: Schema = {
allowInvalidCerts: { description: '', type: { type: 'boolean' }, update: 'mutable' }, allowInvalidCerts: { description: '', type: { type: 'boolean' }, update: 'mutable' },
}, },
defaults: { defaults: {
bucket: 'stalwart', bucket: 'mail',
}, },
}, },
}, },
@@ -959,7 +960,7 @@ describe('buildNewObjectValue', () => {
it('seeds the first variant with its @type, defaults and booleans', () => { it('seeds the first variant with its @type, defaults and booleans', () => {
expect(buildNewObjectValue(structSchema, 'x:Store')).toEqual({ expect(buildNewObjectValue(structSchema, 'x:Store')).toEqual({
'@type': 'S3', '@type': 'S3',
bucket: 'stalwart', bucket: 'mail',
allowInvalidCerts: false, allowInvalidCerts: false,
}); });
}); });
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * 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_MAX_NAME_LENGTH = 80;
const SIEVEPAD_MAIN_SCRIPT = 'main'; const SIEVEPAD_MAIN_SCRIPT = 'main';
const BASE64_CHUNK_SIZE = 0x8000; 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<string, string> = { const SIEVE_SCRIPT_FIELDS: Record<string, string> = {
'x:SieveSystemScript': 'contents', 'x:SieveSystemScript': 'contents',
+12
View File
@@ -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`;
}
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
@@ -20,7 +21,7 @@ import { loadLogoOnce } from './lib/logoCache';
(() => { (() => {
try { try {
const persisted = localStorage.getItem('stalwart-ui'); const persisted = localStorage.getItem('inbuxa-ui');
if (persisted) { if (persisted) {
const parsed = JSON.parse(persisted); const parsed = JSON.parse(persisted);
const theme = parsed?.state?.theme; const theme = parsed?.state?.theme;
+5
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * 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 { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader } from '@/components/ui/card'; import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { startAuthFlow } from '@/services/auth/oauth'; 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 * INBUXA: straight to the server's own sign-in page, which asks for the
@@ -72,6 +74,9 @@ export default function LoginPage() {
</> </>
)} )}
</Button> </Button>
<p className="text-center text-xs text-muted-foreground">
<SourceLink className="underline underline-offset-2 hover:text-foreground" />
</p>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
+1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
+1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * 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 SCOPES = import.meta.env.VITE_OAUTH_SCOPES as string | undefined;
const SESSION_PREFIX = 'stalwart-oauth-'; const SESSION_PREFIX = 'inbuxa-oauth-';
interface DiscoveryResponse { interface DiscoveryResponse {
authorization_endpoint: string; authorization_endpoint: string;
+1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
+1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
@@ -117,7 +118,7 @@ export const useAuthStore = create<AuthState>()(
}, },
}), }),
{ {
name: 'stalwart-auth', name: 'inbuxa-auth',
storage: { storage: {
getItem: (name) => { getItem: (name) => {
const value = sessionStorage.getItem(name); const value = sessionStorage.getItem(name);
+2 -1
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
@@ -61,7 +62,7 @@ export const useUIStore = create<UIState>()(
}, },
}), }),
{ {
name: 'stalwart-ui', name: 'inbuxa-ui',
partialize: (state) => ({ partialize: (state) => ({
theme: state.theme, theme: state.theme,
}), }),
+2
View File
@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
@@ -19,3 +20,4 @@ interface ImportMeta {
} }
declare const __APP_VERSION__: string; declare const __APP_VERSION__: string;
declare const __SOURCE_ID__: string;
+1 -1
View File
@@ -21,5 +21,5 @@
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts", "source-archive.ts"]
} }
+7 -2
View File
@@ -8,13 +8,18 @@ import { version } from './package.json' with { type: 'json' }
// INBUXA's own dated version lives apart from package.json, whose version // INBUXA's own dated version lives apart from package.json, whose version
// follows upstream WebUI so its bumps merge without conflicts. // follows upstream WebUI so its bumps merge without conflicts.
import inbuxa from './inbuxa-version.json' with { type: 'json' } import inbuxa from './inbuxa-version.json' with { type: 'json' }
import { sourceArchive, sourceIdentity } from './source-archive'
const source = sourceIdentity(import.meta.dirname)
export default defineConfig({ export default defineConfig({
base: './', base: './',
define: { 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: { resolve: {
alias: { alias: {
'@': path.resolve(import.meta.dirname, './src'), '@': path.resolve(import.meta.dirname, './src'),