How CasaOS Implements Its Notification System: Architecture and Code Deep Dive
CasaOS implements its notification system using a service-oriented architecture that combines a GORM-backed SQLite database, a MessageBus pub/sub dispatcher, and WebSocket broadcasting to deliver both persistent alerts and real-time UI updates.
The notification subsystem in CasaOS (IceWhaleTech/CasaOS) provides the backbone for user alerts, file operation progress, and system status messages. Written in Go, this system leverages a clean interface-based design to decouple storage from delivery mechanisms, ensuring reliable message persistence while supporting instantaneous front-end updates.
Core Architecture Overview
CasaOS notification system architecture follows a three-layer pattern: persistence, dispatch, and broadcast. At the foundation, notifications survive as records in the o_notify SQLite table. A central NotifyServer interface abstracts all operations, while the MessageBus handles event publishing and background goroutines manage WebSocket delivery to connected clients.
This design ensures that notifications remain durable across service restarts while allowing the front-end to receive push updates without polling. The system also maintains an in-memory syncmap.Map for transient system data that does not require persistence.
The NotifyServer Interface and Implementation
The NotifyServer interface in service/notify.go defines the contract for all notification operations. This interface exposes methods for CRUD operations, custom ID-based updates, and specialized dispatch functions.
type NotifyServer interface {
GetLog(id string) model.AppNotify
AddLog(log model.AppNotify)
UpdateLog(log model.AppNotify)
UpdateLogByCustomID(log model.AppNotify)
DelLog(id string)
GetList(c int) (list []model.AppNotify)
MarkRead(id string, state int)
SendFileOperateNotify(nowSend bool)
SendNotify(name string, message map[string]interface{})
SettingSystemTempData(message map[string]interface{})
GetSystemTempMap() syncmap.Map
}
The concrete implementation (notifyServer) maintains two critical dependencies: a GORM database handle for persistent storage and a thread-safe syncmap.Map for temporary system data. This structure allows the service to handle both durable notifications and ephemeral status updates within the same unified interface.
Database Persistence and Models
Notifications persist in the o_notify table defined by the AppNotify model in service/model/o_notify.go. The GORM model maps directly to database columns and includes fields for State, Message, Id, Class, and an optional CustomId for external reference tracking.
// Model structure based on source analysis
type AppNotify struct {
Id string
State int
Message string
Class int
CustomId string
Type int
CreatedAt time.Time
}
The Class field categorizes notifications (e.g., types.NOTIFY_APP), while the State field tracks read status using constants like types.NOTIFY_UNREAD and types.NOTIFY_READ. This schema supports efficient querying for unread notification lists and targeted cleanup operations.
Message Dispatch and WebSocket Broadcasting
The CasaOS notification system uses a dual-channel delivery mechanism. For event-driven updates, SendNotify marshals payload values to JSON strings and publishes them through the central MessageBus.
response, err := MyService.MessageBus().
PublishEventWithResponse(context.Background(),
common.SERVICENAME, name, msg)
This pub/sub layer decouples notification generation from delivery, allowing multiple subscribers to react to system events. Errors and non-200 HTTP responses from the message bus are logged for debugging purposes.
For real-time front-end synchronization, the SendMeg background loop (running in service/notify.go) pulls pending notifications via GetList(types.NOTIFY_APP), marshals them to JSON, and pushes the payload to every connection stored in WebSocketConns. After successful transmission, notifications are automatically marked as read to prevent duplicate delivery.
Specialized Notification Types
File Operation Progress
The SendFileOperateNotify method handles real-time updates for copy, move, and delete operations. This method periodically scans an in-memory FileQueue, builds a notify.NotifyModel payload, and publishes it to the message bus under the event name "casaos:file:operate".
go service.MyService.Notify().SendFileOperateNotify(true) // start periodic push
Triggered automatically by file handlers in route/v1/file.go (lines 682 and 811), this mechanism provides users with immediate visual feedback on long-running file operations without blocking the main thread.
System Temporary Data
For non-persistent status information, SettingSystemTempData stores arbitrary key/value pairs in the SystemTempMap. Other components, such as periodic system-status reporters, read this map via GetSystemTempMap. This pattern avoids database writes for rapidly changing metrics like CPU temperature or memory usage.
Notification Lifecycle Management
The CasaOS notification system follows a complete CRUD lifecycle:
- Create:
AddLoginserts a newAppNotifyrow into the database. - Read:
GetListqueries notifications by class (e.g.,types.NOTIFY_APP), returning unread or dynamic entries. - Update:
UpdateLogmodifies existing records by ID, whileUpdateLogByCustomIDallows external systems to update notifications using their own reference identifiers. - Mark Read:
MarkReadupdates thestatecolumn totypes.NOTIFY_READ. - Delete:
DelLogremoves entries bycustom_id, typically called during cleanup operations.
// Adding a new notification
notify := model.AppNotify{
State: types.NOTIFY_UNREAD,
Message: "New backup completed",
Id: uuid.NewString(),
Class: types.NOTIFY_APP,
Type: types.NOTIFY_TYPE_INSTALL_LOG,
}
service.MyService.Notify().AddLog(notify)
// Marking as read
service.MyService.Notify().MarkRead(notificationId, types.NOTIFY_READ)
Integration Examples
Controllers and services interact with the notification system through the MyService.Notify() singleton. To send custom events to the front-end:
payload := map[string]interface{}{
"title": "Upgrade Available",
"desc": "Version 2.3.0 is ready to install",
}
service.MyService.Notify().SendNotify("casaos:system:update", payload)
The HTTP endpoint in route/v1/notify.go forwards client POST requests to Notify().SendNotify, enabling external triggers and third-party integrations to broadcast messages through the CasaOS notification infrastructure.
Summary
- CasaOS notification system uses a service-oriented architecture with clear separation between storage, dispatch, and delivery layers.
- Persistence occurs in SQLite via GORM, using the
o_notifytable andAppNotifymodel defined inservice/model/o_notify.go. - Real-time delivery combines MessageBus pub/sub with WebSocket broadcasting managed by the
SendMegloop inservice/notify.go. - File operations receive special handling through
SendFileOperateNotify, which publishes"casaos:file:operate"events during copy/move/delete actions. - Flexible lifecycle support includes standard CRUD operations plus
CustomId-based updates for external system integration. - Transient data storage via
syncmap.Maphandles system metrics without database overhead.
Frequently Asked Questions
What database does CasaOS use for notifications?
CasaOS uses SQLite as the underlying database for notification persistence, accessed through the GORM ORM framework. The AppNotify model in service/model/o_notify.go maps directly to the o_notify table, storing fields like State, Message, Class, and CustomId.
How does CasaOS handle real-time notifications?
CasaOS implements real-time notifications through a combination of MessageBus pub/sub and WebSocket broadcasting. The SendNotify method publishes events to the message bus, while the background SendMeg loop queries pending notifications and pushes them to all active WebSocket connections stored in WebSocketConns, marking them as read upon successful delivery.
Can third-party apps send notifications through CasaOS?
Yes, third-party applications can send notifications by utilizing the NotifyServer interface available through service.MyService.Notify(). The HTTP endpoint in route/v1/notify.go exposes this functionality to external clients, allowing POST requests to trigger SendNotify with custom event names and payloads.
How are file operation progress updates sent?
File operation notifications are handled by the SendFileOperateNotify method in service/notify.go. This method periodically scans the FileQueue and publishes "casaos:file:operate" events to the MessageBus. File handlers in route/v1/file.go trigger this process during copy, move, or delete operations, ensuring users receive real-time progress updates in the UI.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →