Skip to main content
Admin Modules

Service Registry

Service catalog, per-user URL resolution, multi-network service definitions, and the admin UI for managing the service inventory

February 25, 2026

Service Registry

The service registry is a catalog of all homelab services across both networks. It maps services to URLs, widgets, and categories. User assignment is handled separately on the user management page — the registry itself is a pure catalog of what services exist.

Architecture

The registry lives at src/lib/service-registry.ts with KV persistence at key data:service-registry. Built-in service definitions ship with the code and merge with any custom entries stored in KV. An in-memory cache with a 60-second TTL prevents repeated KV reads.

Service Definition Type

interface ServiceDef {
  id: string;             // 'plex-bogie', 'grafana-mm'
  label: string;          // 'Plex (bogie)'
  icon: string;           // Font Awesome class
  category: 'media' | 'storage' | 'monitoring' | 'tools';
  network: NetworkId;     // 'milky-way' | 'andromeda'
  url: string;            // Primary access URL (CF tunnel)
  widgetId: string;       // Maps to widget-registry ID
  users: string[];        // Emails granted access (empty = admin-only)
  restartable?: boolean;  // User can trigger container restart
  containerId?: string;   // Docker container name for restart API
  embedUrl?: string;      // For iframe-embeddable services (Grafana kiosk mode)
}

Resolution Logic

  1. Admin — gets all services, no filtering
  2. Member — gets services where:
    • Their email appears in the service’s users[] array, OR
    • Their auth.services[] includes the service widgetId or id
  3. Demo — gets no services (no URLs resolved)

Key functions:

FunctionReturnsPurpose
getServicesForUser(auth)ServiceDef[]All services the user can access
buildServiceUrlMap(auth)Record<string, string>Widget ID → URL map for dashboard
getRestartableServices(auth)ServiceDef[]Services the user can restart
getServiceUrl(auth, widgetId)string | nullSingle URL lookup by widget

Admin UI (/admin/services)

The service registry admin page at /admin/services displays all services in a sortable table.

Table Columns

ColumnDescription
LabelService display name
Widget IDMaps to widget-registry entry
Categorymedia, monitoring, storage, or tools
Networkmilky-way or andromeda
URLPrimary access URL
ActionsEdit / Delete buttons

Category Color Coding

Table rows have a color-coded left border by category:

  • Media — pink border
  • Monitoring — cyan border
  • Storage — green border
  • Tools — purple border

Add/Edit Service

The modal form collects:

  • Label — display name
  • Service ID — unique identifier (auto-suggested from label on create)
  • Widget ID — select populated from the widget registry at src/lib/widget-registry.ts
  • Category — media, monitoring, storage, or tools
  • Network — milky-way or andromeda
  • URL — primary access URL
  • Icon — Font Awesome icon class
  • Embed URL — optional iframe URL
  • Container ID — optional Docker container name for restart
  • Restartable — toggle for user-initiated restarts
  • Helper text — descriptions explain each field’s purpose

User Assignment

User-to-service assignment is done on the user edit form at /admin/users, not on the service form. When editing a user, the Services & Features section shows checkboxes for all available services (deduplicated by widget ID from the registry). This keeps the service registry as a pure catalog.

Built-in Services

Twelve services ship by default across two networks:

Andromeda Network (Andromeda)

Service IDWidgetURLRestartable
plex-danielplexplex.Arcturus-Prime.comNo
plex-bogieplexplex-bogie.Arcturus-Prime.comYes
plex-mauveplexplex.Arcturus-Prime.comNo
rutorrent-bogierutorrentbogie.Arcturus-Prime.comYes
audiobookshelfaudiobookshelfaudiobooks.Arcturus-Prime.comNo
tautulli-danieltautullitautulli.Arcturus-Prime.comNo
tautulli-bogietautullitautulli-bogie.Arcturus-Prime.comNo
grafana-mmgrafana-embedgrafana-mm.Arcturus-Prime.comNo
speedtest-mmspeedtestspeedtest-mm.Arcturus-Prime.comNo
unraid-dashboardstorage-overview(proxy)No

Milky Way Network (Milky Way)

Service IDWidgetURLRestartable
rutorrent-danielrutorrentargonaut.Arcturus-Prime.comNo
rutorrent-mauverutorrentmauve.Arcturus-Prime.comNo

Admin API

Manage services at /api/admin/services. All endpoints require admin authentication.

MethodActionBody
GETList all services (built-in + custom)
POSTCreate or update a serviceServiceDef JSON
DELETERemove a custom service{ "id": "service-id" }

Built-in services cannot be deleted but can be overridden by posting a service with the same ID.

Service Restart

Members can restart services marked restartable: true through the /api/user/service-restart endpoint.

Safety rules:

  • User must have access to the service (registry check)
  • Service must have restartable: true and a containerId
  • For non-admins: the container must be currently running — if an admin deliberately stopped a container, the restart is blocked with “Service is currently disabled by admin”
  • Admins bypass the running-state check

The restart calls the mm-Arcturus-Prime proxy to issue POST /api/containers/{containerId}/restart on Meridian-Host.

Dashboard Integration

The dashboard frontmatter resolves URLs at render time:

const { serviceUrls, serviceEmbedUrls } = await buildServiceUrlMap(auth);
const restartableServices = await getRestartableServices(auth);

Widget links use serviceUrls[widgetId] instead of hardcoded URLs. Restart buttons appear next to restartable services with visual feedback (spinner → checkmark/error).

adminservicesfederationmulti-userservice-registrycatalog