Lific

Upgrade from 2.0 to 2.7

Back up, upgrade, verify, and adopt the changes from 2.1 through 2.7 on an existing Lific 2.0 installation.

This guide covers upgrades from Lific 2.0 through 2.7. It assumes an existing 2.0 instance and deliberately does not describe older migration paths.

Before upgrading

  1. Record the selected configuration and database paths with lific instance info and your service status command.

  2. Make a complete backup while the service is stopped or quiesced:

    lific dump --out ./lific-before-2.7.tar.gz

    The archive includes a consistent database snapshot, attachments, and manifest.json.

  3. Record how clients connect: browser URL, API-key users, OAuth clients, and MCP configuration files. Keep one known-good operator key available for recovery.

  4. If the instance is behind a reverse proxy, review server.trusted_proxies twice: before starting 2.2, which introduced the setting with a default that trusted loopback peers, and again before 2.7, where the default changes to trust no proxies at all. From 2.7, a deployment whose proxy connects from loopback (Tailscale serve or Funnel, or nginx on the same host) must list that proxy explicitly, or every client shares the proxy's rate-limit bucket. Add only isolated proxy networks you operate.

  5. Confirm that lific.toml parses before rolling out 2.6. From 2.6 a config file that exists but cannot be read or parsed stops startup instead of falling back to defaults, and unknown keys are rejected. Run lific doctor, which reports the configuration as one check and continues through the rest.

Stop the background service before replacing the binary or restoring a backup. Keep the same lific.toml, database path, attachments directory, and public URL unless you are intentionally relocating the instance.

Changes to account for

v2.1

  • lific member manages project membership and roles from the CLI.
  • lific user set-password provides an operator password reset and invalidates the user's sessions.
  • --config is honored by init and service installation, and configuration/database discovery follows standard OS directories.
  • Private instances may set [auth] required = false; this is a shell-controlled operator setting and must remain local or firewalled.

v2.2

  • Browser views use credentialed realtime invalidation. Sessions are revalidated, connections are capped per user, and reconnects trigger a resync.
  • server.trusted_proxies controls when forwarded client-IP headers are trusted for rate limiting.
  • MCP output is compact by default. Use the documented opt-in fields such as include_closed, include_comments, or echo_tree when an agent needs the full view.
  • MCP search supports literal mode, comments are searchable, and list/comment operations expose pagination hints.
  • The web UI adds list sub-tabs, sidebar recents, touch page movement, PWA install metadata, and improved command-palette results.
  • Auth-optional mode now reaches the browser through the instance auto-login flow; with no accounts, the signup screen still appears.

v2.2.1

  • The three export MCP tools are now one export tool. It dispatches by identifier: PRO-42 for an issue, PRO-DOC-3 for a page, and bare PRO for a project.
  • The MCP surface is 27 tools. Clients that assumed the older 29-tool count or the removed export tool names must refresh their discovery data.

v2.3

  • The CLI can run data commands against a running server with --backend http, using --url or LIFIC_URL and --api-key or LIFIC_API_KEY. The direct SQLite backend remains the default.
  • OAuth discovery works on an instance with no server.public_url. An explicit public_url is still authoritative.

v2.4

  • One migration runs automatically on first launch. Project groups start empty for every user, so the sidebar renders as before until you create one.

v2.5

  • No migrations. Identifiers render as Markdown links in MCP, CLI, and REST output; server.public_url pins the link base, and Lific otherwise derives it from an allowlisted request host.

v2.6

  • Two migrations run automatically on first start. They add the per-tool identity plumbing behind connected agents, and there are no manual steps.
  • A configuration file that exists but cannot be read or parsed is now a fatal startup error, and unknown keys are rejected. A missing file still starts on the built-in defaults. Check that lific.toml parses before the rollout; lific doctor is the tool for it.
  • A server with web_auto_login enabled refuses to start when [server] host is not loopback. Bind 127.0.0.1 or turn auto-login off.
  • REST authentication failures are uniformly 403 with the message authentication required. Endpoints for profile, password, sessions, API keys, connected tools, and comments previously answered 400 for this case. A client that reads 400 from those endpoints as "signed out" must key on 403 instead.
  • Remove secure_cookies from [auth] if your config sets it. It has never been read from the file, and unknown keys are now an error rather than being ignored.
  • lific init presents an auth-mode choice on a fresh install and creates the first administrator directly. An existing instance with users skips this entirely.
  • Connected AI tools have identities of their own. Audit entries for their writes are attributed to the tool rather than to the operator who connected it.

v2.7

Go to 2.7.1, not 2.7.0. In 2.7.0 every issue page in the web UI failed to render, on every instance. 2.7.1 fixes it and changes nothing else. The two releases are otherwise identical, so everything below applies to both.

The largest upgrade since 2.0. Three things need an operator decision before you roll it out: server.trusted_proxies stops defaulting to loopback, an older binary will refuse to open the database once 2.7 has touched it, and account recovery revokes much more than it used to. Take the pre-upgrade archive described above; on this release it is the only way back.

Account recovery changed shape. Read this part before rolling out if anything automated holds an API key.

  • API-key callers lose credential-minting permission. POST /api/auth/keys and POST /api/auth/bots now require a browser session token created within the last 15 minutes and answer 403 recent authentication required to anything else, API keys included. This is deliberate: a key that could mint another key outlived the revocation of the key it came from. A script that provisioned keys by presenting an existing key must move to lific key create on the server, which writes to the database directly. lific connect is unaffected for the same reason.

  • A password change or sign-out-everywhere now revokes far more than sessions. For the account and every connected-tool bot it owns: sessions, API keys, OAuth access tokens, unexchanged authorization codes, and uncollected device approvals. lific user set-password does the same. Plan for reconnecting tools and reissuing any API key a script depends on after a reset. Unbound operator keys are not affected.

  • A password change returns a replacement session in token, and it is the only credential left that works. A REST client that changes a password on a user's behalf must store that token before its next request. The web UI does this for you.

  • A running stdio MCP agent stops at its next tool call once its key is revoked. LIFIC_TOKEN is revalidated on every tool call rather than only at startup. Expect agents to need lific connect plus a client restart after any recovery.

  • OAuth access tokens can no longer approve an authorization request or a device code, and the browser session that approves must have signed in within the last 15 minutes. Approve from a signed-in browser, signing in again first if prompted. A tool that scripted its own re-approval through a token it already held will stop working.

  • Every access-expanding admin endpoint requires a recent browser session. POST /api/users, POST /api/users/{id}/promote, POST /api/users/{id}/reactivate, PATCH /api/instance/settings, POST /api/projects/{id}/members, role increases through PATCH /api/projects/{id}/members/{user_id}, PUT /api/projects/{id} when it sets lead_user_id (which grants a lead membership), and POST /api/projects when it names a lead other than the caller. Creating a project with no lead, or with yourself as lead, is unchanged, so automation that creates its own projects keeps working. Each answers 403 recent authentication required to an API key or OAuth token, because each leaves access behind that a lockdown aimed at the calling credential cannot reach. Scripted user provisioning moves to lific user create and lific user promote on the server. Reductions are untouched: demote, deactivate, role downgrades and member removal all still work from an API key, so containment stays fast.

  • Instance settings are gated as a whole, not field by field. A patch that only changes instance_name is refused from an API key just like one that turns on web_auto_login. Classifying each field as expanding or not is a rule that has to be re-derived every time a field is added, and one miss reopens the hole.

  • An OAuth grant that is not bound to an identity can no longer be exchanged. Authorization codes and device approvals stored before 2.1 have no user attached; exchanging one produced an access token that named nobody and was therefore treated as the operator, and which no account recovery could revoke. Both exchange paths now return invalid_grant for these. Codes expire in ten minutes and device codes in fifteen, so in practice this only affects a client mid-flow across the upgrade: have it authorize again. Access tokens already issued are unchanged; revoke any unbound one you find with DELETE FROM oauth_tokens WHERE user_id IS NULL and reconnect the tool. Rolling back is now a restore, not a binary swap.

  • Five migrations run automatically on first start: bot-identity uniqueness (which merges any duplicate agent identities into the oldest, keeping memberships, groups, and saved views), case-insensitive project identifiers, the account active flag, attachment dimensions and alt text, and the attachment search index. The _migrations table also gains a checksum column, backfilled from the migrations this binary carries. No manual steps.

  • An older Lific will refuse to open a 2.7 database. Migrations only run forwards, so an older binary used to find the version already stamped, apply nothing, and serve a schema it was never compiled against. Startup now fails with an error naming the database's schema version and the highest the binary supports. To go back, restore the pre-upgrade archive.

  • A hand-edited migration file is now a startup failure. Applied migrations are checksummed, and a stored hash that no longer matches names the migration and both digests. If you have ever patched a migration in place, restore the original before upgrading and put the correction in a new migration.

  • [backup] retain = 0 and interval_minutes = 0 no longer do what they said. Zero retention deleted every archive seconds after writing it, and a zero interval panicked the backup task on its first tick, silently ending backups while the server kept serving. Both now warn and fall back to the defaults, 24 archives every 60 minutes. Set enabled = false to turn backups off.

Bounded responses. Every surface that could return an unbounded result is now paged or capped. A client that relied on getting everything in one call needs to page.

  • Comment lists default to 50 and cap at 500. GET /api/issues/{id}/comments keeps its order=asc default, so an unqualified read is now the oldest 50; page with offset, or pass order=desc. The MCP list_comments tool and lific comment list default to the 50 newest instead, so a script that expected lific comment list to print a whole thread oldest-first needs --order asc and its own offset loop. get_issue with include_comments='all' returns at most the 500 most recent.
  • A comment body is capped at 256 KiB, measured after newline and tab normalization. Existing longer comments stay readable.
  • Search is bounded and scoped. Page size defaults to 20 and caps at 500, offsets clamp at 100,000, a full-text query over 4 KiB or a literal query over 256 bytes returns 400, and a literal search stops at 10,000 matches. Results from projects the caller cannot see are gone, so counts may legitimately drop; filtering by an invisible project now returns empty rather than an error that revealed it exists.
  • Exports are bounded. 10,000 files, 1,000 comments per issue, 100,000 per project, 50,000 metadata items, 8 MiB per file and 128 MiB total. Previously an export asked for at most 10,000 issues and then loaded every comment on each with no limit, so a large project's export was bounded only by available memory; one past the new ceilings now fails with an explicit error. Two run at a time; the rest get 429 with Retry-After: 30, which an export client should honour. The HTTP download is dropped after 30 seconds idle or 30 minutes in total.
  • Websocket clients are capped at 16 sockets per user and 1,024 per instance, with 16 KiB messages, 4 KiB frames, 64 messages per 10 seconds, and a 5-second send timeout. Clients that only listen are better off: the server now sends protocol pings every 30 seconds, so they are no longer dropped after 120 seconds for skipping an application heartbeat.

Other behavior changes.

  • OAuth access tokens are refused on every credential-management route, which covers API key create/list/revoke, connected-tool management, password change, session revocation, profile edits, user administration, and OAuth client and token management. A connected tool that called those endpoints gets a 403 and needs a browser session or a local CLI command. Ordinary reads and writes through MCP are unchanged.
  • Editing or deleting a comment now requires access to its project, not just authorship, on both REST and MCP. Someone removed from a project loses the ability to mutate the comments they left in it.
  • Project identifiers resolve case-insensitively across CLI, REST, and MCP, matching modules, folders, and usernames. Creating abc when ABC exists is now rejected, and a database that already holds a case collision (only reachable by raw SQL) has them renamed deterministically on upgrade.
  • web_auto_login cannot be enabled on an instance that declares a public_url. The guard used to look only at the bind host, which passed a loopback bind published through Tailscale Funnel or a same-host proxy. The request is refused with a 400 and not persisted.
  • The CLI refuses to send an API key over plaintext HTTP to a remote host. What was a warning is an error. Use https:// or a loopback target. LIFIC_TOKEN is now sent only to the origin named in LIFIC_URL, so a token plus a --url override, or a directory holding a lific.toml for a different server, falls back to stored credentials for that host.
  • Uploads are validated by magic bytes rather than the declared content type, capped at 10 MiB by default, and rate-limited to 30 per user per 10 minutes. A client uploading a type outside the allowlist is refused.
  • MCP tool errors are generic. Raw database text, which could carry table and column names and the database path, no longer reaches the client; it stays in the server log. A script matching on that text needs another signal.

Upgrade and verify

  1. Install the v2.7 binary and restart the existing service.

  2. Run lific doctor with the operator credential. Resolve configuration, database, backup, server, OAuth, and MCP failures before opening the instance to users.

  3. Check lific instance info and confirm the expected database, public URL, signup policy, and authz_enforced state.

  4. Review membership and operator keys:

    lific key list
    lific member list --project <PROJECT>

    Unbound API keys are operator-trusted/admin-equivalent because they can only be minted with shell access. Audit or rotate any key that is no longer needed. Use user-owned bot keys when an agent should inherit project membership.

  5. Test the browser login and one representative project workflow. Confirm issue, page, plan, comment, attachment, and saved-view data before testing writes.

  6. Test one API and one MCP client. For MCP, refresh tool discovery and replace calls to export_issue, export_page, or export_project with export.

  7. If the instance uses a proxy, confirm the server logs the expected client IP and that rate limits do not trust arbitrary forwarded headers.

  8. Keep the pre-upgrade archive until the browser, API, MCP, backups, and service restart have all been verified.

Authorization rollout

An existing 2.0 instance keeps its persisted authorization setting during the upgrade. Inspect it before changing anything. With enforcement on, viewers can read and comment, maintainers can mutate project content and structure, and leads manage settings, membership, and deletion. Cross-project relations and plan-step links require the appropriate role in every project involved.

To change the setting deliberately:

lific instance set --authz-enforced true

Before enabling it, ensure every human and bot that needs access has a project membership, and keep an operator-trusted key available for recovery. To roll back the setting on a private instance, use --authz-enforced false and then investigate the missing membership or role rather than distributing a broader key.

For a private local deployment, [auth] required = false is a separate setting from project authorization. It grants credential-less operator access and is unsafe anywhere others can reach it; Lific refuses to start when it is set and [server] host is not loopback. Treat both settings as operator changes that require shell access and a verification step.

If the upgrade fails

Stop the service, preserve its logs and the failed database state, and do not repeatedly rerun migrations against the only copy. Restore the pre-upgrade archive to a separate data directory or use lific restore <ARCHIVE> --force only after confirming the archive and intended target. Re-run lific doctor, then compare the restored instance's configuration and client credentials before switching traffic back.

On this page