license: relicense to AGPL-3.0-only with commercial dual-license
- LICENSE -> verbatim GNU AGPL-3.0; add LICENSE-COMMERCIAL.md + SECURITY.md - CLA -> exclusive-license grant (contributors keep copyright); add README anti-rugpull covenant + relicense record - NOTICE / README / CONTRIBUTING / CLAUDE.md / package.json x5 updated; contact routed through GitHub (no email placeholders) - AGPL section 13 source offer: operator-configurable BACKSPACE_SOURCE_URL + build-injected commit; sourceCodeUrl+commit on /api/instance/info; SourceCodeLink on login/register/settings/desktop; docs + .env.example updated
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"name": "@backspace/server",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"license": "Elastic-2.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"author": "Jannis Braun",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -48,6 +48,24 @@ if (publicOrigin !== undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
// AGPL-3.0 § 13 "network-use source offer": users interacting over the network
|
||||
// must be able to obtain the Corresponding Source of the *running* version.
|
||||
// Operators who modify Backspace and self-host MUST point this at their own
|
||||
// fork's source so the offer stays accurate. Defaults to the upstream repo for
|
||||
// unmodified deployments.
|
||||
const UPSTREAM_SOURCE_URL = 'https://github.com/TheZwiss/backspace';
|
||||
const sourceCodeUrl = envOptional('BACKSPACE_SOURCE_URL') ?? UPSTREAM_SOURCE_URL;
|
||||
if (!/^https?:\/\//i.test(sourceCodeUrl)) {
|
||||
throw new Error(
|
||||
`BACKSPACE_SOURCE_URL must start with http:// or https:// — got: ${sourceCodeUrl}`
|
||||
);
|
||||
}
|
||||
|
||||
// Short git SHA/tag of the running build, injected at Docker build time via the
|
||||
// BACKSPACE_COMMIT build arg (see Dockerfile / deploy.sh). Null in local dev
|
||||
// (no build step) — the § 13 offer still works via version + sourceCodeUrl.
|
||||
const commit = envOptional('BACKSPACE_COMMIT') ?? null;
|
||||
|
||||
export const config = {
|
||||
port: envInt('PORT', 3000),
|
||||
host: env('HOST', '0.0.0.0'),
|
||||
@@ -55,6 +73,8 @@ export const config = {
|
||||
jwtExpiresIn: env('JWT_EXPIRES_IN', '30d'),
|
||||
domain: envOptional('DOMAIN'),
|
||||
publicOrigin,
|
||||
sourceCodeUrl,
|
||||
commit,
|
||||
|
||||
livekit: {
|
||||
url: envOptional('LIVEKIT_URL'),
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('GET /api/instance/info', () => {
|
||||
expect(body.federatedRegistrationOpen).toBe(false);
|
||||
});
|
||||
|
||||
it('returns the full contract: name, version, registrationOpen, federatedRegistrationOpen', async () => {
|
||||
it('returns the full contract: name, version, registrationOpen, federatedRegistrationOpen, sourceCodeUrl, commit', async () => {
|
||||
const res = await app.inject({ method: 'GET', url: '/api/instance/info' });
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = res.json();
|
||||
@@ -90,5 +90,9 @@ describe('GET /api/instance/info', () => {
|
||||
expect(typeof body.version).toBe('string');
|
||||
expect(typeof body.registrationOpen).toBe('boolean');
|
||||
expect(typeof body.federatedRegistrationOpen).toBe('boolean');
|
||||
// AGPL § 13 source offer — always a URL; commit is a string or null.
|
||||
expect(typeof body.sourceCodeUrl).toBe('string');
|
||||
expect(body.sourceCodeUrl).toMatch(/^https?:\/\//);
|
||||
expect(body.commit === null || typeof body.commit === 'string').toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,10 @@ export async function instanceRoutes(app: FastifyInstance): Promise<void> {
|
||||
version: BACKSPACE_VERSION,
|
||||
registrationOpen,
|
||||
federatedRegistrationOpen: settings?.federatedRegistrationOpen === 1,
|
||||
// AGPL-3.0 § 13: advertise the source of the running version to every
|
||||
// network user (and federated peer) — public/unauthenticated by design.
|
||||
sourceCodeUrl: config.sourceCodeUrl,
|
||||
commit: config.commit,
|
||||
};
|
||||
|
||||
return reply.code(200).send(response);
|
||||
|
||||
Reference in New Issue
Block a user