Files in the top-level directory from the latest check-in
- .fossil-settings
- core
- deploy
- examples
- l10n
- ops
- poc1
- ASKING.md
- AUTH.md
- CREATORS.md
- DATABASE.md
- DIGEST.md
- EXTENSIONS.md
- FEATURES.md
- GUARDIAN.md
- HELP.md
- HISTORY.md
- I18N.md
- IMAGES.md
- INTAKE.md
- INVITES.md
- ISSUES.md
- JOURNAL.md
- LICENSE
- LICENSING.md
- LINKS.md
- MAIL.md
- MELANGEJ.md
- MERGE.md
- MIGRATION.md
- MIRRORS.md
- MOBILE.md
- NOTICE
- OPDS.md
- POLICY.md
- README.md
- RELEASES.md
- SBOM.md
- SECURITY.md
- SKINS.md
- SPEC.md
- TEMPLATES.md
- TENANCY.md
- THINK.md
- TRACKERS.md
- VERSIONING.md
- VIEWERS.md
- VITALITY.md
- WEBUI.md
spicemine
spicemine is a self-hosted home for version-controlled work: repositories in Fossil, git, Mercurial and Jujutsu, served over HTTPS and SSH, with spaces, invitations, scoped tokens, cross-engine mirrors, verified backups, and a browser UI in English and Japanese. One JVM, one directory, one thing to back up.
It is written in Common Lisp on the JVM, on a framework of its own called
mélangej (ABCL on Jooby and Netty, no Lisp dependencies). The book that
documents the framework is served by every instance at /melangej, and is
in core/src/dist/book/.
The premise is in the first feature below: you can't look at everyone's code without signing in. It is your data, your code, your choice.
What it does
- Four engines, one host. Fossil, git, Mercurial and Jujutsu behind one
adapter protocol (
core/src/main/lisp/spice-scm.lisp). Your own client talks to the real engine over HTTPS or SSH; nothing here is a dialect. - Spaces and roles. Everyone gets a personal space; a group space has members, writers and admins, and a repository is private, members only or public. Members only shows its showcase to anyone and the rest to members of the space. What you may not see is absent, not refused: its URL answers 404.
- Credentials that do not cross. A session signs a browser into the
pages, a token signs a program into the API and the wire, an SSH key
signs a machine into the transport. A token can be scoped to one
repository or one space; write includes read. See
AUTH.md. - For people who make things, not only programmers. Files go in by
dragging them onto the files view, and each drop is one commit by the
person who dropped it. A text file is edited from its own page, and a
directory of pictures shows as a contact sheet. A repository states its
terms from a catalog, with
a plain sentence beside the formal name. Anyone may save a repository, and
the count is two numbers, never one. See
CREATORS.md. - Editions, and your work when you leave. Name the files as they are now, "first printing", and the showcase offers a zip of exactly that revision for good. "Take your work with you" makes one zip of every repository you administer, each in its engine's own form, with a manifest of what this instance knew about it.
- One search box, and a sounding board. Search finds spaces, projects and issues by what they say, and only what you may see. The sounding board turns what you say into questions, Eliza-style, with no model behind it; a conversation worth keeping is saved into the project.
- A journal beside the work. A project's owners write short posts, 280 characters, from its showcase; they sit in a column to the left or right of the work, public or for members only, and a deleted post is gone, not hidden.
- Issues that travel with the project. An issue is a Markdown file in
the project's own issues repository,
<project>~issues, one commit per action, with a status, a resolution, a severity and a thread; the database only indexes it. Clone the issues repository and the conversation comes along, while the project's branch and its CI never see a comment (TRACKERS.md,ISSUES.md§10). - Mirrors and subs. A repository can be served in another engine's
format (
/space/repo.git,/space/repo.hg), rebuilt on every push; a sub is a read-only copy of a repository hosted elsewhere. SeeMIRRORS.md. - Fetch a project in. An existing project at an address is fetched into an empty repository from its setup page or the API, project code and all for Fossil, which refuses a push from a different project code.
- Backups that prove themselves. A backup is restored into a throwaway
instance before it counts, encrypted with
ageto any rclone remote, and the audit fails loudly when anything is missing. Seedeploy/README.md. - A showcase per repository. Title, byline, hero image, README, and a
public page that works without JavaScript. Ten skins; every string in two
languages. See
WEBUI.md,SKINS.md,I18N.md. - The AI/crawler Guardian (designed, not built). The plan: repository
content readable by a person who signed in, or by an agent carrying that
person's token, named, rate-limited, logged and revocable, with anonymous
bulk reading refused. Today tokens are scoped and revocable; nothing is
rate-limited. See
GUARDIAN.mdand FEATURES.md.
Dependencies
On the host, found on PATH or named with a -D property:
- a JDK, 17 or newer, to run (the build provisions its own)
fossil2.25 or newer,git,hg(Mercurial),jj(Jujutsu): one per engine you host; an instance that hosts no Mercurial needs nohgsqlite3is not needed: SQLite is embedded; PostgreSQL is optional and configured withspice.db.url(credentials come from the environment, never from that URL). SeeDATABASE.md.
In the distribution, 29 jars, 39 MB, all from Maven Central:
| what | which | why |
|---|---|---|
| ABCL 1.9.2 | org.abcl:abcl, abcl-contrib |
the Common Lisp; contrib is on the classpath and loaded by nothing |
| Jooby 3.9.1 | jooby, jooby-netty |
routing and the HTTP server; Netty 4.2 under it |
| JGit 7.3 | org.eclipse.jgit |
the git engine's smart-HTTP, in-process |
| sqlite-jdbc 3.49, PostgreSQL 42.7, HikariCP 5.1 | metadata: users, spaces, repositories, tokens, events | |
| minimal-json 0.9.5 | JSON where Java reads it | |
| slf4j-simple 2.0 | the logging the libraries above expect |
No Lisp dependencies. No Quicklisp, no ASDF system, nothing vendored:
twenty-five Lisp files loaded in order by boot.lisp. The Markdown
renderer, HTML emitter, template compiler, JSON codec and migration machinery
are in the tree, a few hundred lines each, and you can read all of it.
MELANGEJ.md says which files are the framework and which are this service.
Building
core/gradlew -p core installDist # -> core/build/install/spicemine/
That is the whole build: Gradle fetches the jars, compiles the seven Java
files, copies the Lisp, and compiles it to fasls with warnings fatal
(compile-all.lisp). The result is a directory with bin/spicemine-server,
bin/spicemine-admin, bin/spicemine-ssh-keys, bin/spicemine-ssh-serve,
lib/ and lisp/. A tarball of it is a release.
Run it from the checkout:
JAVA_HOME=/path/to/jdk17+ core/build/install/spicemine/bin/spicemine-server
# or, loading .lisp sources without the compile step:
core/gradlew -p core runCore -Pport=8643
First boot prints the instance admin's token once; spicemine-admin is how
an operator with a shell gets back in. Everything is a -Dspice.* property
or an environment variable; deploy/nixos/spicemine.nix lists them all with
their meaning.
The gate that every change has to pass:
JAVA_HOME=/path/to/jdk17+ core/run-tests-all.sh
It builds, proves the build fails on an unreadable source, evaluates the
NixOS module, runs the deploy script suites, the Lisp suites, and then
starts a real instance and runs the setup instructions, the wire-mount
authorisation, the scope form, self-hosting, and the SSH transport against
it with the real clients. The browser suites run too when PLAYWRIGHT is
set. core/run-tests-pg.sh repeats the suites on PostgreSQL.
Deployment
A release is a directory; the service is one JVM behind your TLS
terminator; the state is one directory (spice.home), which is the one
thing to back up.
- NixOS is the supported shape:
deploy/nixos/spicemine.nixis a module with the service, the user, the SSH transport, backups, offsite copies and their timers, and assertions that refuse a configuration with a secret in the store.deploy/README.mdis the runbook, from first boot to restore. - Anything else that can run a JVM: unpack the release, point
-Dspice.homeat a directory, put traefik or nginx in front, and runbin/spicemine-serverunder your supervisor.deploy/deploy.shanddeploy/upgrade.share the reference for what a deploy does, in forty lines of shell each.
No address, hostname or credential appears in this repository; every script takes them from the environment.
Documents
SPEC.md |
the system, section by section |
AUTH.md |
sessions, tokens, scopes, SSH keys |
MIRRORS.md |
mirrors, subs, what converts to what |
DATABASE.md |
SQLite and PostgreSQL, migrations, the pool |
WEBUI.md, SKINS.md, I18N.md, TEMPLATES.md |
the pages |
GUARDIAN.md, TENANCY.md, MOBILE.md, POLICY.md, LICENSING.md, LINKS.md, TRACKERS.md, MERGE.md, ISSUES.md, JOURNAL.md, VERSIONING.md |
the rules and what is planned |
MELANGEJ.md |
the framework's version and the split |
SECURITY.md |
the monthly audit checklist, the pinned bill of materials, the findings; deploy/security-audit.sh runs the half a script can |
deploy/README.md |
the runbook |
Support
There is no commercial release and there will not be one: the platform is the platform, and everyone gets every feature. What may be paid for is services around it. Fork it, host it; that is what we do.
License
Copyright 2026 IoTone, Inc.
spicemine is licensed under the Apache License, Version 2.0: see
LICENSE and NOTICE. You may use, change and
redistribute it, in source or built, under that licence's terms; it comes
with no warranty.
Each source file says so in two lines (Copyright 2026 IoTone, Inc.,
SPDX-License-Identifier: Apache-2.0), and the gate fails when one does
not (core/src/test/sh/license-headers.sh). What spicemine ships that
others wrote keeps its own licence: SBOM.md lists every component with
it. The licence of a project hosted on an instance is its owner's
choice and a different subject: LICENSING.md.
Inspirations
- Forgejo's published API (
https://codeberg.org/api/swagger) - gitblit, for what a self-hosted git server got right a decade earlier
- Fossil, for being one file