CasaOS Notify Service: Architecture, Storage, and Real-Time Event Broadcasting

The CasaOS notify service is a centralized event manager implemented in service/notify.go that persists notifications to SQLite or PostgreSQL via GORM, broadcasts real-time updates through the Message Bus, and maintains temporary in-memory state for ephemeral system data.

CasaOS is an open-source home cloud operating system that consolidates file management, app hosting, and system monitoring into a unified interface. At the heart of its event architecture lies the CasaOS notify service, a dedicated component that handles everything from application installation alerts to file-operation progress indicators. This service centralizes all user-visible events, manages their persistence in relational databases, and ensures real-time delivery to connected front-end clients through WebSocket connections.

Core Architecture of the CasaOS Notify Service

The notify service is built around a clean interface-based design that separates concerns between data persistence, real-time messaging, and transient state management.

The NotifyServer Interface

At the top of service/notify.go (lines 25–41), the service declares the NotifyServer interface, which defines the contract for notification operations. The concrete notifyServer type implements this interface, exposing methods such as AddLog, GetList, MarkRead, SendNotify, and SendFileOperateNotify. This abstraction allows other CasaOS components to interact with notifications without coupling to specific storage implementations.

Database Persistence via GORM

The service persists notifications using GORM, an Object-Relational Mapper for Go. The notifyServer struct embeds a *gorm.DB connection (the db field) that interacts with the model.AppNotify struct defined in service/model/o_notify.go.

When creating a notification, the AddLog method executes i.db.Create(&log), inserting a new row into the database. Updates use i.db.Save(&log) for full-record overwrites or targeted Updates calls for specific fields like custom_id. The DelLog method removes entries by custom_id (lines 78–81), while GetList queries filter records by class (notification category) and state (read status).

Real-Time Broadcasting

Beyond persistence, the service broadcasts events to connected clients via the Message Bus. The SendNotify method marshals payloads to JSON and invokes MyService.MessageBus().PublishEventWithResponse (lines 55–71), distributing messages to WebSocket subscribers. For file operations, SendFileOperateNotify constructs a notify.NotifyModel and publishes it on the "casaos:file:operate" channel (lines 73–118), enabling progress bars and status updates in the UI.

Temporary In-Memory Storage

Not all system state requires durability. The notifyServer maintains a SystemTempMap (a syncmap.Map) for ephemeral data that disappears on restart. Methods SettingSystemTempData and GetSystemTempMap provide thread-safe access to this cache (lines 48–53), useful for flags like maintenance mode or transient operation locks.

How the CasaOS Notify Service Stores Notifications

Understanding the storage layer reveals how CasaOS balances query performance with data integrity.

The AppNotify Model Structure

Notifications map to the AppNotify struct (defined in service/model/o_notify.go), which includes fields such as Id, CustomId, Class, State, Title, Content, and timestamps. GORM automatically manages table schema migrations based on this struct, eliminating raw SQL manipulation.

Query Patterns and State Management

The service frequently filters notifications by two columns: class and state. The GetList implementation at lines 44–46 demonstrates a complex query pattern that retrieves active notifications:

i.db.Where("class = ?", c).
    Where(i.db.Where("state = ?", types.NOTIFY_DYNAMICE).Or("state = ?", types.NOTIFY_UNREAD)).
    Find(&list)

This query fetches records matching a specific category (class) that are either unread (NOTIFY_UNREAD) or dynamic (NOTIFY_DYNAMICE). The MarkRead method updates the state column to NOTIFY_READ using i.db.Save(&log), transitioning notifications from active to archived status.

Practical Implementation Examples

The following patterns demonstrate how CasaOS components interact with the notify service in production scenarios.

Creating and Retrieving Notifications

To log a new event, instantiate an AppNotify struct and invoke AddLog:

notif := model.AppNotify{
    Class:   types.NOTIFY_APP,
    State:   types.NOTIFY_UNREAD,
    Title:   "Backup started",
    Content: "Backing up /data to Google Drive",
}
MyService.Notify().AddLog(notif)

Retrieve unread notifications for a specific category using GetList:

unread := MyService.Notify().GetList(types.NOTIFY_APP)
for _, n := range unread {
    fmt.Printf("%s – %s\n", n.Title, n.Content)
}

Managing Notification Lifecycle

Mark specific notifications as read using their ID:

if len(unread) > 0 {
    MyService.Notify().MarkRead(unread[0].Id, types.NOTIFY_READ)
}

Delete obsolete notifications by custom_id through the DelLog method.

Broadcasting Custom Events

Push real-time alerts to the front-end via the Message Bus:

payload := map[string]interface{}{
    "title":   "System reboot",
    "message": "CasaOS will restart in 1 minute",
}
MyService.Notify().SendNotify("casaos:system:info", payload)

Leveraging Temporary Storage

Store short-lived system flags that persist only until the next restart:

MyService.Notify().SettingSystemTempData(map[string]interface{}{
    "maintenance": true,
})

Key Source Files and Their Responsibilities

  • service/notify.go: Core implementation containing the NotifyServer interface, notifyServer struct, and all persistence and broadcasting logic (lines 25–118).
  • service/model/o_notify.go: GORM model definition for AppNotify, specifying database schema and field mappings.
  • model/notify.go: Lightweight struct definitions for WebSocket message payloads (NotifyMssage).
  • types/notify.go: Enumeration constants for notification states (NOTIFY_UNREAD, NOTIFY_READ, NOTIFY_DYNAMICE) and classes.
  • common/message.go: Service-wide constants such as SERVICENAME used when publishing to the Message Bus.

Summary

  • The CasaOS notify service centralizes event management through the NotifyServer interface implemented in service/notify.go.
  • Notifications persist to SQLite or PostgreSQL via GORM using the model.AppNotify struct, with operations abstracted through methods like AddLog, GetList, and MarkRead.
  • Real-time delivery occurs through the Message Bus, with SendNotify and SendFileOperateNotify publishing JSON payloads to WebSocket subscribers on channels such as "casaos:file:operate".
  • Ephemeral system state resides in SystemTempMap, a thread-safe in-memory cache for data that does not survive restarts.
  • The storage layer optimizes queries by filtering on class and state columns, supporting efficient retrieval of active versus archived notifications.

Frequently Asked Questions

What database does CasaOS use for notification storage?

CasaOS supports both SQLite and PostgreSQL backends for the notify service. The implementation in service/notify.go uses GORM as the database abstraction layer, allowing the same model.AppNotify struct and query logic to work across either database engine without modification to the service code.

How does CasaOS handle real-time notification updates in the browser?

The service pushes real-time updates via WebSocket connections managed by the Message Bus. When SendNotify or SendFileOperateNotify is called, the service marshals the notification to JSON and publishes it using MyService.MessageBus().PublishEventWithResponse, which broadcasts to all connected front-end clients subscribed to specific channels.

What is the difference between notification states in CasaOS?

CasaOS defines three primary states in types/notify.go: NOTIFY_UNREAD for new alerts requiring user attention, NOTIFY_READ for acknowledged notifications, and NOTIFY_DYNAMICE for transient progress indicators that update frequently (such as file upload progress). The GetList method specifically queries for unread or dynamic notifications to populate active alert panels while excluding archived items.

Where does CasaOS store temporary notification data that should not persist?

Temporary data resides in SystemTempMap, a syncmap.Map field within the notifyServer struct defined at lines 45–46 of service/notify.go. This in-memory cache stores ephemeral flags and state via SettingSystemTempData and GetSystemTempMap, ensuring that short-lived operational data (like maintenance mode indicators) automatically clears when the service restarts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →