Maisnie

User guide · Software preview

Install

Read this first: Maisnie is in early access, and you cannot download it today without asking. There is no public download. To get it, request early access with the form. We reply by email with your personal install link, which works for 7 days. Then you paste one line into a terminal (below) and Maisnie installs. If you already have your link, carry on.

During early access we email you a personal install link. You need no Python and no other tools: one line per computer installs everything Maisnie needs, including its own private copy of Python, the time zone database and the piece that locks away your saved passwords, and leaves your other software alone. In the lines below, replace <your install link> with the link from the email, pasted whole and left inside the quotation marks. It ends in .whl.

Windows. Open PowerShell (press the Windows key, type PowerShell, press Enter), paste this one line, and press Enter:

& { $ErrorActionPreference = "Stop"; irm https://astral.sh/uv/0.13.1/install.ps1 | iex; $env:Path = "$HOME\.local\bin;$env:Path"; uv tool install --managed-python --python 3.12 --with tzdata --with cryptography "<your install link>"; if ($LASTEXITCODE -ne 0) { throw "Maisnie did not install. Check that you pasted the whole link from your email, inside the quotation marks. If it still fails, the link may have expired: ask us for a new one." }; uv tool update-shell; if ($LASTEXITCODE -ne 0) { throw "Maisnie installed, but this computer could not be told where to find it. Close PowerShell, open it again and type maisnie --help." } }

macOS and Linux. Open Terminal, paste this one line, and press Enter. On a Mac, press Command+Space (that opens Spotlight search), type Terminal, and press Enter. A plain window with a line waiting for you to type opens: that is Terminal. Paste with Command+V. The line asks for no password and needs no administrator rights, and the first run takes about a minute, so give it time before you worry.

t=$(mktemp) && curl -fLsS https://astral.sh/uv/0.13.1/install.sh -o "$t" && sh "$t" && export PATH="$HOME/.local/bin:$PATH" && uv tool install --managed-python --python 3.12 --with tzdata --with cryptography "<your install link>" && uv tool update-shell

What the line does: it installs uv, a small program that installs other programs, then asks uv to install Maisnie from your link with a Python 3.12 that uv downloads for it (--managed-python means a Python already on your computer, including the Microsoft Store one, is never used). The same line adds a time zone database and the small piece Maisnie uses to seal your saved passwords and keys. If a download or an install step fails (an expired link, for example), the line stops there and says so in plain words instead of carrying on.

You may wonder about astral.sh. Astral is the company that makes uv, and astral.sh is where it publishes uv. Astral also looks after the Python builds that uv downloads for the line, so the Python comes from the same makers. The time zone database and the password-sealing piece come from PyPI, the public home of Python add-ons. The line names an exact uv version (0.13.1 in the lines above), so every install gets the one we tested and a newer release cannot change what runs on your computer without us seeing it first. We raise that number on this page when we test a newer uv.

The first run downloads a few tens of megabytes and takes about a minute. When it finishes you see a line starting Installed and listing maisnie. Nothing here needs administrator rights.

Now start Maisnie. In the same window:

maisnie init
maisnie up

maisnie init writes your settings file and prints a line starting wrote with its location. maisnie up starts Maisnie and prints:

maisnie: Front door: started (pid ...)
maisnie: Setup: started (pid ...)
maisnie: Home: started (pid ...)
Maisnie is up: http://127.0.0.1:8080/

On the first run, when you have not chosen your passphrase yet, Maisnie opens that address (http://127.0.0.1:8080/) in your browser by itself: it shows the claim page, where you choose your passphrase. If no browser opens (or you started with maisnie up --no-browser), open the address yourself. Leave the window open while you use Maisnie; press Ctrl+C in it to stop. First boot walks through what comes next.

If you close the window and open a new one, maisnie still works: the last step of the install line (uv tool update-shell) adds uv’s folder to your path for every new window. If a new window still says it cannot find maisnie (or uv), run uv tool update-shell once more, then close and reopen the window. On Windows, if uv itself is not found, close and reopen PowerShell once and paste the line again; it picks up where it left off.

To check the install at any time: maisnie --help lists the subcommands (init, up, status, stop, start-at-login, doctor, unclaim, unlock, relocate, storage, vault, backup, restore, run, pause, resume, secrets, remote, card), and maisnie doctor checks your setup. See troubleshooting if something is off.

Updating later

Maisnie checks once a day whether a newer version exists. When one does, Settings shows “A new version is ready” under About, with what is new in plain sentences, and Home shows one quiet line pointing there. Nothing updates by itself. Tap Ask for the update and your mail program opens a ready-written email to us (nothing is sent until you send it); we reply with your new install link.

The check asks maisnie.com for the latest version number and nothing else: it sends nothing about you, your notes or your computer. To turn it off, clear Check once a day for a new version under About in Settings and press Save. Off means Maisnie makes no request at all, and you can still update whenever we send you a link.

When we send you a newer link, do these in order. A running box keeps the code it started with, so it has to be stopped and started again. Your data is untouched.

  1. Stop Maisnie: maisnie stop.
  2. Install the new version, with the new link in place of <your new install link> (the same line works in PowerShell, Terminal and Linux shells):

    uv tool install --force --managed-python --python 3.12 --with tzdata --with cryptography "<your new install link>"
    
  3. Start it again: maisnie up. If you set Maisnie to start when you sign in, sign out and back in instead (or start it from there again).

To remove Maisnie later, run uv tool uninstall maisnie; your data folder stays until you delete it yourself (see Where things go).

For developers

The rest of this page is the longer route, for people comfortable with a terminal and Python: a virtual environment with pip, or Docker.

Requirements

  • Python 3.9 or later, on Linux, macOS, or Windows. On Windows, prefer the installer from python.org over the Microsoft Store version: the Store build silently redirects %LOCALAPPDATA% (where Maisnie’s default data directory lives) into its own sandboxed package folder, so files Maisnie writes there won’t be where you expect to find them.
  • On Windows, also run pip install tzdata in the same virtual environment right after installing Maisnie. python.org’s own Windows build of Python ships no time zone database at all, and without one, calendar events can only be approximated from the feed’s own fixed offset (no daylight-saving adjustment) instead of resolved properly: see troubleshooting for the full story. Linux and macOS normally already have one from the OS.
  • No other required dependency: Maisnie’s core needs only the Python standard library. To seal stored passwords and keys on a pip install, also run pip install "maisnie[secrets]" (see security); the Docker image already has it.
  • Well under 5 MB for Maisnie’s own source; a fresh virtual environment with it installed is roughly 30 MB all in. Your notes, mail, and calendar data will need more over time, but plain text stays small for a long while.

Install with pip

You’ll be given a wheel file (a filename ending in .whl) for the preview build. Its name starts with maisnie. Install it into a fresh virtual environment so it doesn’t collide with anything else on your system.

Linux/macOS:

python3 -m venv ~/maisnie-env
source ~/maisnie-env/bin/activate
pip install /path/to/maisnie-<version>-py3-none-any.whl

Windows (PowerShell):

py -m venv $HOME\maisnie-env
& $HOME\maisnie-env\Scripts\Activate.ps1
pip install C:\Users\<you>\path\to\maisnie-<version>-py3-none-any.whl

If PowerShell refuses to run Activate.ps1 (“running scripts is disabled on this system”), that’s the default execution policy blocking it: run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned once, then try activating again.

Either OS: you have to reactivate the virtual environment every time you open a new terminal (run the source .../activate or Activate.ps1 line again): it isn’t a one-time step, and maisnie: command not found after closing and reopening your terminal usually just means this.

Confirm it installed:

maisnie --help

If that prints a list of subcommands (init, up, status, stop, start-at-login, doctor, unclaim, unlock, relocate, storage, vault, backup, restore, run, pause, resume, secrets, remote, card), you have a working install. See troubleshooting if it doesn’t. The same install also provides the command under its older name, majordomo, for anything already scripted against it; this guide uses maisnie throughout.

Where things go

Maisnie keeps everything it writes under one data directory. You don’t have to choose one: if you don’t, it picks a sensible per-OS default the first time you run maisnie init:

OS Default data directory
Linux $XDG_DATA_HOME/maisnie, or ~/.local/share/maisnie if that variable isn’t set
macOS ~/Library/Application Support/Maisnie
Windows %LOCALAPPDATA%\Maisnie

You can override this for any command with --data-dir <path>: placed before the subcommand, e.g. maisnie --data-dir <path> doctor (maisnie doctor --data-dir <path>, with the flag after, is refused as an unrecognized argument), or by setting the environment variable MAISNIE_HOME. Nothing is ever written to the default location unless you actually run maisnie init or maisnie up: checking what the default would be never creates it.

If you set Maisnie up before it had this name, nothing moves: the older variable MAJORDOMO_HOME is still honoured when MAISNIE_HOME isn’t set, and when the default folder above doesn’t exist yet but the same place under the older name (majordomo) holds your configuration document, Maisnie keeps using that folder. Inside the data directory, the configuration document keeps its file name, config/majordomo.json.

Updating a developer install

Install the new version the way you installed the first one, then stop maisnie up and start it again. A running maisnie up keeps the code it started with (its scheduler and the pages it serves), and maisnie pause and maisnie resume only hold and release its jobs: they never load new code. The sources’ own sync jobs start fresh each time, so they already run the new code; what waits for the restart is anything the scheduler itself does once at start (it tidies the saved feed addresses that way).

With Docker, a new source does nothing until the image is rebuilt, and a container keeps the image it was created from. First get the new source: git pull in the checkout, or unpack the newer archive you were given over the old folder, so data/, backups/, vault/ (if you added the ./vault:/vault mount from the setup guide) and .env stay where they are. The archive carries its own compose.yml and replaces yours: keep a copy of an edited one first and put your extra mount back, or keep the extra mount in a compose.override.yml, which the archive does not touch. If a new folder is unavoidable, stop the old box first (docker compose down, or docker rm -f maisnie) and move data/, backups/, vault/, .env and your edited compose.yml (or its override file) into the new folder before you start anything there: Compose and -v "$(pwd)/data" both read ./data from the folder you run them in, so a new folder without your data starts an empty box. Then, from the source folder:

  • Compose: docker compose up -d --build. docker compose up -d alone does not rebuild an image that already exists; with --build it rebuilds the image and recreates the container, which is the restart.
  • Plain Docker: docker build -t maisnie:local ., then docker rm -f maisnie, then the same docker run -d --name maisnie ... line you used the first time (see Install with Docker). docker restart maisnie would keep the old image, and running docker run --name maisnie again without the docker rm -f fails because the name is taken.

Your data in ./data is untouched either way.

Install with Docker

A Dockerfile and a compose.yml ship at the repository root: this needs the source checkout (or whatever archive of it you were given), not just the wheel. If a wheel file is all you have, use Install with pip instead. You don’t need Python, pip, or a virtual environment for the Docker path itself, only Docker.

With Docker Compose (the simpler path: it builds the image for you):

docker compose up -d
docker compose up -d

(Compose’s own syntax is the same on every OS; only the environment-variable and path forms below differ between a POSIX shell and PowerShell.)

This builds an image (tagged maisnie:local), starts one container, writes a config document inside it on first boot if one isn’t there yet (the same thing maisnie init does for a plain install, done for you), and publishes the gatekeeper on 127.0.0.1:8080 on your own machine: loopback, the same default a plain install starts with; nothing on your network can reach it unless you deliberately change compose.yml’s own port line. Your data lives in ./data next to compose.yml (a mounted folder, not something sealed inside the container) so it survives docker compose down and a later rebuild.

Skip ahead to claiming the box: the maisnie init/doctor/ up commands in first boot are for a plain install; Compose already did their job for you. To use a different port, set MAISNIE_PORT before bringing it up:

MAISNIE_PORT=18080 docker compose up -d
$env:MAISNIE_PORT=18080; docker compose up -d

One variable moves both the container’s own bind and the host-side port compose.yml publishes together, so the two can’t drift apart.

Set your own time zone, or Today and every time Maisnie shows will read in UTC instead of yours. Every time and date on the page follows the clock of the machine Maisnie is actually running on. For a plain install, that’s your own computer’s clock already, nothing to set. A container’s own clock defaults to UTC regardless of your host’s time zone, so compose.yml passes through a TZ environment variable (an IANA name, such as Europe/Lisbon or America/Los_Angeles), defaulting to UTC when you don’t set one:

TZ=America/Los_Angeles docker compose up -d
$env:TZ="America/Los_Angeles"; docker compose up -d

(or set it once in an .env file beside compose.yml, the same way you might set MAISNIE_PORT there). This is a one-time setting the container reads at startup, not something the setup wizard asks for.

docker compose logs -f follows the box’s own log lines (the same ones a plain maisnie up prints to its terminal); docker compose down stops and removes the container without touching ./data. A log line warning the box is UNCLAIMED prints on every start, even after you’ve claimed it: that check is currently a known bug reading a config key nothing ever writes, not a real signal; go by the claim page itself (or status, below) instead.

If you ran this compose.yml before the product was called Maisnie, its service had the older name, and Compose leaves that old container running beside the new one, still holding the port. Bring the box up once with docker compose up -d --remove-orphans (or run docker compose down before you update the checkout); ./data is untouched either way. The image sets MAISNIE_HOME=/data itself, so the older MAJORDOMO_HOME variable has no effect in a container: to move the data directory there, set MAISNIE_HOME.

With plain Docker, build and run the image yourself instead of going through Compose. The image’s own user is a fixed non-root account, and a Linux Docker Engine creates a bind-mounted host folder that doesn’t exist yet as root, which that non-root user then can’t write into at all: docker run (unlike compose.yml, which starts as root for exactly this reason and drops privilege itself) has nothing to fix that ownership for you, so maisnie init fails outright unless you either create the folder yourself with the right owner first, or add --user root so the image’s own entrypoint fixes it:

docker build -t maisnie:local .
mkdir -p data && sudo chown 1000:1000 data
docker run -d --name maisnie -p 127.0.0.1:8080:8080 \
  --add-host=host.docker.internal:host-gateway \
  -v "$(pwd)/data:/data" maisnie:local

or, letting the entrypoint do it instead of chown yourself:

docker run -d --name maisnie --user root -p 127.0.0.1:8080:8080 \
  --add-host=host.docker.internal:host-gateway \
  -v "$(pwd)/data:/data" maisnie:local
docker build -t maisnie:local .
docker run -d --name maisnie --user root -p 127.0.0.1:8080:8080 `
  --add-host=host.docker.internal:host-gateway `
  -v "${PWD}\data:/data" maisnie:local

(--add-host matters on Linux; Docker Desktop on Windows and macOS already resolves host.docker.internal without it: see below.)

Either way, if you’re pointing a connection at a model server running on your own machine (Ollama, llama.cpp, LM Studio: see choose a model), reach it from inside the container as http://host.docker.internal:<port>/v1, never 127.0.0.1:<port>: inside a container, 127.0.0.1 means the container itself, not the machine it’s running on.

Running commands in the container

Anywhere this guide says to run a maisnie command “from a terminal” (run, pause, resume, unlock, and, see below, unclaim), the host running Docker has no maisnie on its own PATH; run it inside the container instead:

docker compose exec --user majordomo maisnie maisnie pause

(the repeated maisnie: the compose service name, then the actual CLI command. The image’s own default user is root under compose.yml, so --user majordomo (the image’s fixed non-root account, which keeps Maisnie’s internal codename) avoids leaving root-owned files behind for a command that writes anything). unclaim additionally needs the instance stopped first (it refuses while the lock is held), so run it as its own one-off container instead of exec-ing into the running one:

docker compose stop
docker compose run --rm maisnie unclaim --yes
docker compose up -d

A backup works the same way, with one extra rule: the one-off container is removed when the command ends, so the backup has to be saved on a folder shared with your computer. compose.yml shares ./backups as /backups for exactly this, and maisnie backup refuses any other folder inside a container:

docker compose stop
docker compose run --rm maisnie backup /backups
docker compose up -d

Restoring is the same, into an empty ./data: docker compose run --rm maisnie restore /backups/maisnie-backup-<date>-<time> --vault-dir /data/vault. See backup and restore.

A container’s port publish can reach past the claim gate that protects a plain install. Internally, the box always sets its own bind to loopback and refuses --lan until you’ve claimed it, but Docker’s port publishing connects to the container’s own network address, which is not the same as loopback from Docker’s point of view, so the container is set up to answer on every interface inside itself the moment it starts, claimed or not. compose.yml’s default (127.0.0.1:8080 on the host side) keeps that contained to your own machine; publishing wider than that (-p 8080:8080 instead of -p 127.0.0.1:8080:8080, or -P) exposes an unclaimed box to whoever reaches it first. Keep the default until you’ve claimed the box.

Docker equivalents for the rest of this guide

The rest of this guide (starting with first boot) is written for a plain install; two things map differently under Docker:

  • A CLI command (run, pause, resume, unlock, unclaim): see running commands in the container, above.
  • --lan, to reach the box from another device: there is no --lan flag to pass under Docker: the container already answers on every interface inside itself (see just above). Reaching it from elsewhere on your network, once claimed, means changing compose.yml’s host-side port line from 127.0.0.1:${MAISNIE_PORT:-8080}:... to 0.0.0.0:... (or your LAN address) and re-running docker compose up -d, never before you’ve claimed the box.

Next

Continue to first boot to bring the box up for the first time.