The source offer is a link to this fork, not a tarball in the image (#2)
INBUXA's webmail built its own source into every image: the whole tree,
web and server, packed as dist/source.tar.gz with an identity string
beside the link naming the exact tree it came from. The sign-in page and
Settings > About offered that download.
It answered the AGPL precisely -- the source of *this* build, uncommitted
work and all -- but it paid for that precision by carrying 2.5 MB of
source into production on every deploy, to a repository that is public
and already has it. The fork is at github.com/inbuxa/ihasmail-inbuxa;
the version shown directly above the link already names the commit the
build came from, so the link and the version together say the same
thing the archive said.
Both links now go there, through the mechanism upstream ihasmail already
has and this fork had replaced: the server's SOURCE_URL, read from
/api/config on the sign-in page and from the session in About, with
web/src/lib/source.ts as the fallback before either answers. That
mechanism is better than a hardcoded URL for the deployer who patches
this tree -- they set SOURCE_URL and both links follow -- which is the
case the AGPL is actually about. The defaults in config.ts, the compose
file and .env.example move from the upstream repo to this one, since a
build from this tree is a modified ihasmail and its offer is ours.
Removed with it: scripts/source-archive.mjs and its type stub, the Vite
plugin that ran it, __SOURCE_ID__, and SOURCE_ARCHIVE/SOURCE_ID. The
build no longer shells out to git or tar, and nothing is written next
to the app.
Links to Coffey-Labs/ihasmail that are credit rather than a source
offer -- the README's "built on", the translation issue link -- are
left alone.
No new strings: "AGPL-3.0 source" is unchanged, and the About line keeps
its existing {source} placeholder, now filled with the host and path
instead of a file name.
This commit is contained in:
Vendored
-2
@@ -6,5 +6,3 @@
|
||||
* `scripts/version.mjs`.
|
||||
*/
|
||||
declare const __IHASMAIL_VERSION__: string;
|
||||
/** ihasmail-inbuxa: the identity of the source this build was made from (scripts/source-archive.mjs). */
|
||||
declare const __SOURCE_ID__: string;
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
* The AGPL asks whoever runs a modified version to offer *that* version's
|
||||
* source. The server says where its own lives, via SOURCE_URL; this is only the
|
||||
* fallback for when it has not been asked yet, or has nothing to say.
|
||||
*
|
||||
* ihasmail-inbuxa: INBUXA runs a modified ihasmail, so the offer is INBUXA's
|
||||
* fork and not the project it came from.
|
||||
*/
|
||||
export const DEFAULT_SOURCE_URL = "https://github.com/Coffey-Labs/ihasmail";
|
||||
export const DEFAULT_SOURCE_URL = "https://github.com/inbuxa/ihasmail-inbuxa";
|
||||
|
||||
@@ -6,10 +6,3 @@
|
||||
* parts are what they are.
|
||||
*/
|
||||
export const APP_VERSION = __IHASMAIL_VERSION__;
|
||||
|
||||
/**
|
||||
* ihasmail-inbuxa: the source this build was made from, which the build writes
|
||||
* next to the app as `source.tar.gz`. The AGPL's offer links there.
|
||||
*/
|
||||
export const SOURCE_ID = __SOURCE_ID__;
|
||||
export const SOURCE_ARCHIVE = "/source.tar.gz";
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Eye, EyeOff, LogIn } from "lucide-react";
|
||||
import { useSession } from "@/store/session";
|
||||
import { ApiError } from "@/jmap/client";
|
||||
import { withBase } from "@/lib/basePath";
|
||||
import { APP_VERSION, SOURCE_ARCHIVE, SOURCE_ID } from "@/lib/version";
|
||||
import { APP_VERSION } from "@/lib/version";
|
||||
import { DEFAULT_SOURCE_URL } from "@/lib/source";
|
||||
import { DEFAULT_APP_NAME } from "@/lib/brand";
|
||||
import { t } from "@/lib/i18n";
|
||||
import { InbuxaWordmark } from "@/ui/InbuxaWordmark";
|
||||
@@ -11,9 +12,8 @@ import { InbuxaWordmark } from "@/ui/InbuxaWordmark";
|
||||
export function LoginPage() {
|
||||
const login = useSession((s) => s.login);
|
||||
// The AGPL's offer has to reach everyone who interacts with the app over the
|
||||
// network, and that includes whoever is looking at this form. ihasmail-inbuxa
|
||||
// offers the exact source of this build, which the build writes next to the
|
||||
// app (see SOURCE_ARCHIVE), rather than a repository link that can drift.
|
||||
// network, and that includes whoever is looking at this form.
|
||||
const [sourceUrl, setSourceUrl] = useState(DEFAULT_SOURCE_URL);
|
||||
/*
|
||||
* What this instance calls itself.
|
||||
*
|
||||
@@ -41,6 +41,7 @@ export function LoginPage() {
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((c) => {
|
||||
if (!live || !c) return;
|
||||
if (c.sourceUrl) setSourceUrl(c.sourceUrl as string);
|
||||
if (typeof c.appName === "string" && c.appName.trim()) setAppName(c.appName.trim());
|
||||
setSignIn(c.signIn === "oauth" ? "oauth" : "password");
|
||||
setDirect(c.signIn === "oauth" && c.signInDirect === true);
|
||||
@@ -156,9 +157,7 @@ export function LoginPage() {
|
||||
<br />
|
||||
<a href="https://inbuxa.org" target="_blank" rel="noopener noreferrer" className="notranslate" translate="no">inbuxa.org</a>
|
||||
{" · "}
|
||||
<a href={withBase(SOURCE_ARCHIVE)} target="_blank" rel="noopener noreferrer">{t("AGPL-3.0 source")}</a>
|
||||
{" "}
|
||||
<span className="notranslate" translate="no">({SOURCE_ID})</span>
|
||||
<a href={sourceUrl} target="_blank" rel="noopener noreferrer">{t("AGPL-3.0 source")}</a>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useSession } from "@/store/session";
|
||||
import { useAppName } from "@/lib/brand";
|
||||
import { client } from "@/jmap/client";
|
||||
import { APP_VERSION, SOURCE_ARCHIVE, SOURCE_ID } from "@/lib/version";
|
||||
import { APP_VERSION } from "@/lib/version";
|
||||
import { DEFAULT_SOURCE_URL } from "@/lib/source";
|
||||
import { withBase } from "@/lib/basePath";
|
||||
import { t, tNode } from "@/lib/i18n";
|
||||
import { InbuxaWordmark } from "@/ui/InbuxaWordmark";
|
||||
@@ -10,7 +11,8 @@ export function AboutSettings() {
|
||||
const appName = useAppName();
|
||||
const session = useSession((s) => s.session);
|
||||
const caps = Object.keys(session?.capabilities ?? {});
|
||||
// The exact source of this build, written next to the app by the build.
|
||||
// A deployment running modified code should offer its own source, not ours.
|
||||
const sourceUrl = session?.ihasmail?.sourceUrl ?? DEFAULT_SOURCE_URL;
|
||||
return (
|
||||
<div>
|
||||
{/* ihasmail-inbuxa: INBUXA's webmail, built on ihasmail. The version and
|
||||
@@ -26,7 +28,7 @@ export function AboutSettings() {
|
||||
{/* A product name and a version string: neither is a word to translate. */}
|
||||
<div style={{ fontWeight: 700 }} className="notranslate" translate="no">{appName} webmail v{APP_VERSION}</div>
|
||||
<div className="hint">{tNode("Built on {project}", { project: <a href="https://ihasmail.org" target="_blank" rel="noopener noreferrer" className="notranslate" translate="no">ihasmail</a> })}</div>
|
||||
<div className="hint">{tNode("AGPL-3.0-or-later · {source}", { source: <a href={withBase(SOURCE_ARCHIVE)} target="_blank" rel="noopener noreferrer" className="notranslate" translate="no">source.tar.gz ({SOURCE_ID})</a> })}</div>
|
||||
<div className="hint">{tNode("AGPL-3.0-or-later · {source}", { source: <a href={sourceUrl} target="_blank" rel="noopener noreferrer" className="notranslate" translate="no">{sourceUrl.replace(/^https?:\/\//, "")}</a> })}</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>{t("Server")}</h2>
|
||||
|
||||
+2
-22
@@ -3,31 +3,11 @@ import react from "@vitejs/plugin-react";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
import { resolveVersion } from "../scripts/version.mjs";
|
||||
import { baseUrlOf } from "../scripts/basePath.mjs";
|
||||
import { sourceIdentity, writeSourceArchive } from "../scripts/source-archive.mjs";
|
||||
|
||||
// Resolved here, at build time: the browser has no git to ask, and neither does
|
||||
// the Docker build, which is handed the answer as IHASMAIL_VERSION instead.
|
||||
const version = resolveVersion();
|
||||
|
||||
/*
|
||||
* ihasmail-inbuxa: the AGPL's offer for this build. The whole project's source
|
||||
* (web and server), exactly as built, goes into dist/source.tar.gz, and its
|
||||
* identity into the app so the download link can name it. See
|
||||
* scripts/source-archive.mjs.
|
||||
*/
|
||||
const projectRoot = fileURLToPath(new URL("..", import.meta.url));
|
||||
const source = sourceIdentity(projectRoot);
|
||||
|
||||
function sourceArchive(): Plugin {
|
||||
return {
|
||||
name: "inbuxa-source-archive",
|
||||
apply: "build",
|
||||
closeBundle() {
|
||||
writeSourceArchive(projectRoot, fileURLToPath(new URL("./dist/source.tar.gz", import.meta.url)), "ihasmail-inbuxa", source);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Where the app is mounted. Unlike everything else ihasmail is told, this one
|
||||
* cannot wait until the process starts: the hashed asset URLs are written into
|
||||
@@ -79,8 +59,8 @@ function assetList(): Plugin {
|
||||
|
||||
export default defineConfig({
|
||||
base,
|
||||
plugins: [react(), assetList(), sourceArchive()],
|
||||
define: { __IHASMAIL_VERSION__: JSON.stringify(version), __SOURCE_ID__: JSON.stringify(source.id) },
|
||||
plugins: [react(), assetList()],
|
||||
define: { __IHASMAIL_VERSION__: JSON.stringify(version) },
|
||||
resolve: {
|
||||
alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) },
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user