Where users find it
- Sign-in screen: a Connect an instance link under the sign-in form, so users can connect without a hosted account.
- Settings → Self-hosted instance: shows the connected origin and opens the same dialog.
views/components/SelfHostModal.vue. It has an Instance URL field, an Access
token field, a Connect button and, while connected, a Use the hosted service button.
Connect and disconnect
Connect (helpers/selfHost.ts) works in four steps:
1
Normalize the URL
Trim it, add
https:// if there is no scheme, and strip trailing slashes. For any host
that isn’t local, http:// becomes https://. Local hosts are localhost,
127.0.0.1, 192.168.*, 10.* and *.local.2
Require a token
The token field can’t be empty.
3
Probe the instance
GET <url>/v1/health with Authorization: Bearer <token> and a 6 s timeout. Only HTTP
200 passes; anything else shows “Couldn’t reach that instance”.4
Save and restart
Store the origin and token in
systemStore.backendIp and systemStore.selfHostToken,
persisted in the app’s encrypted storage. Clear the cached API origin, the account
session and the subscription state; theme and language are kept. Hand the offline
database to the instance (claimLocalData()): data a hosted account left on the device
is wiped first, so it never syncs into the instance. Then restart the app at /. It
opens straight into the app, with no sign-in.VITE_BACKEND_URL, signed out. The instance still owns the offline database until the next
sign-in, which wipes it before anything syncs, so instance data never reaches a hosted account.
Log out also disconnects: it clears the stored system state,
including the instance, and returns to the hosted sign-in.
What changes while connected
The switch lives in
helpers/systemUtils.ts:
isSelfHost()tells you which mode is active.getSelfHostToken()returns the stored token.getAuthToken()returns the token a request should carry in either mode.getAvailableServer()returns the instance first when one is stored.
authenticatedAPI uses these for every signed-in request. When you add a feature that sends a
token or needs a hosted-only service, read the token through getAuthToken() (never
accountStore.accessToken) and branch on isSelfHost(). Mark a page that can’t work against an
instance with meta: { hostedOnly: true } in router/routes.ts.
What the instance must provide
GET /v1/health→ 200 for the connection check.- Bearer auth with the static token on every
/v1route the app calls. The instance resolves the token to the account that owns it. GET /v1/account/mereturning that owner account, so the app can show a profile. Without it the profile stays empty; nothing else depends on it.- The kit’s wire contract: the
/v1prefix, camelCase, ISO-8601 dates and{ code, message }errors. - CORS that allows the web build’s origin. Native schemes are handled as described in Configuration.
Security notes
- The token is a long-lived bearer secret. On the instance side, treat it like a password: long, random and revocable.
- On the device it sits with the session, in the encrypted storage layer (see App → Session storage).
- Plain
http://is kept only for local hosts. On Android release builds, cleartext is further limited to the hosts inandroid/app/src/main/res/xml/network_security_config.xml(localhost,127.0.0.1,10.0.2.2). Serve real instances over HTTPS.
Remove it
Users opt in to this mode one by one. To hide it, remove its entry points:1
Delete the dialog and helpers
Delete
src/views/components/SelfHostModal.vue and src/helpers/selfHost.ts.2
Sign-in screen
In
src/views/pages/_base/Login.vue, remove the .self-host-link element and its
style, the <SelfHostModal> tag and its import, and the selfHostOpen ref.3
Settings
In
src/views/pages/_settings/AppSettings.vue, remove the “Self-hosted instance”
<ion-list>, the <SelfHostModal> tag and its import, selfHostOpen,
selfHostConnected and currentInstance (with the v-if="!selfHostConnected" on the
Notifications and Subscription lists), and the useSystemStore and IonNote imports.4
i18n
Remove the
selfHost block from src/i18n/locales/*/settings.json.isSelfHost() branches in systemUtils.ts, authenticatedAPI.ts, the
router guard, Splash.vue, views/core/App.vue, OneSignalInitializer.vue,
RevenueCatInitializer.vue and subscriptionStore.ts; getAuthToken() in createListSync.ts
and useFileUpload.ts; the hostedOnly route meta in router/routes.ts; plus backendIp and
selfHostToken in systemStore.