How Notifications Are Sent and Managed Within CasaOS: A Technical Deep Dive
CasaOS persists notifications to a SQLite database via the AppNotify model, publishes events through an internal MessageBus, and delivers them to front-end clients using WebSocket or Socket.io connections.
The notification subsystem in IceWhaleTech/CasaOS provides a lightweight, decoupled architecture for alerting users about system events, app installations, and file operations. Understanding how notifications are sent and managed within CasaOS requires examining the interplay between the persistence layer, the core service implementation, and the real-time transport mechanisms.
Notification Data Model and Persistence
All notifications are stored in a local SQLite database using GORM for object-relational mapping.
The AppNotify Model
The data structure is defined in service/model/app_notify.go as model.AppNotify. This struct maps directly to the app_notify table and contains fields for Id, Class, State, Title, Content, CustomId, and CreatedAt.
The database schema is initialized in pkg/sqlite/db.go through GORM auto-migration:
db.AutoMigrate(&model2.AppNotify{}, …)
Database State Management
Notifications maintain a state lifecycle through the State field, utilizing constants defined in types/notify.go such as NOTIFY_UNREAD and NOTIFY_READ. The Class field categorizes notifications (e.g., types.NOTIFY_APP for application-related alerts), allowing the UI to filter messages by type.
Core Notification Service
The business logic resides in service/notify.go, which implements the NotifyServer interface through the notifyServer struct. This service holds a GORM database instance and a concurrent map (SystemTempMap) for transient system-wide data.
Key Service Methods
The notifyServer provides several atomic operations for notification management:
AddLog– Persists a newAppNotifyrecord to the SQLite database.UpdateLog/UpdateLogByCustomID– Modifies existing notifications by primary key or custom identifier.GetList(c int)– Retrieves unread notifications filtered by class (e.g.,types.NOTIFY_APP).MarkRead(id, state)– Updates the notification state from unread to read.SendNotify(name, message)– Publishes a JSON-encoded event to the internal MessageBus.SendFileOperateNotify– Broadcasts periodic updates about file-operation queue status.
MessageBus Integration
When SendNotify is invoked, the service publishes to common.SERVICENAME using the MessageBus client:
response, err := MyService.MessageBus().
PublishEventWithResponse(context.Background(),
common.SERVICENAME, name, msg)
If the HTTP response status is not 200 OK, the service logs an error (lines 62–68 in service/notify.go). This decouples notification creation from delivery, allowing the front end to subscribe to specific event channels without polling the database directly.
HTTP API Layer
The REST API façade is implemented in route/v1/notify.go (current API) and route/v1/notify_old.go (legacy WebSocket fallback).
REST Endpoints
The Gin-based handlers delegate directly to the NotifyServer implementation:
POST /v1/notify/:path–PostNotifyMessageforwards the request body toNotify().SendNotify(), triggering a MessageBus event.POST /v1/notify/system_status–PostSystemStatusNotifystores transient system metrics viaSettingSystemTempData.PUT /v1/notify/:id–PutNotifyReadmarks a specific notification as read by callingMarkRead.
Legacy WebSocket Fallback
route/v1/notify_old.go handles raw WebSocket connections via the NotifyWS handler. The handler upgrades HTTP requests and stores connections in service.WebSocketConns. A background goroutine (SendMeg()) continuously pulls unread notifications, marshals them to JSON, and pushes them to each connected client, marking messages as read after successful transmission.
Real-Time Delivery Mechanisms
CasaOS supports dual transport protocols for real-time updates:
- Socket.io via MessageBus – The preferred method for modern clients, where
SendNotifypublishes to named channels likecasaos:system:utilization. - Raw WebSocket – Legacy clients connect through
NotifyWSand receive batched unread notifications via theSendMeg()background worker.
Periodic System Status Notifications
The route/periodical.go task retrieves temporary system metrics and broadcasts utilization data:
systemTempMap := service.MyService.Notify().GetSystemTempMap()
service.MyService.Notify().SendNotify("casaos:system:utilization", body)
This feeds dashboard widgets displaying CPU and memory usage without requiring persistent database entries.
Code Examples
Creating a Notification Programmatically
notif := model.AppNotify{
Class: types.NOTIFY_APP,
State: types.NOTIFY_UNREAD,
Title: "Backup finished",
Content: "Your backup completed successfully.",
}
service.MyService.Notify().AddLog(notif)
Sending a Custom Event via MessageBus
payload := map[string]interface{}{
"title": "Disk space low",
"message": "Only 5GB left on /dev/sda1",
}
service.MyService.Notify().SendNotify("casaos:disk:alert", payload)
Retrieving Unread Notifications
unread := service.MyService.Notify().GetList(types.NOTIFY_APP)
for _, n := range unread {
fmt.Printf("🔔 %s – %s\n", n.Title, n.Content)
}
Triggering via REST API
curl -X POST http://localhost:80/api/v1/notify/app \
-H "Content-Type: application/json" \
-d '{"title":"New version","content":"CasaOS 2.0 is now available"}'
Summary
- Persistence layer: Notifications are stored as
AppNotifyrecords in SQLite, auto-migrated viapkg/sqlite/db.go. - Service layer:
service/notify.goimplementsNotifyServerwith methods for CRUD operations and MessageBus publishing. - Transport layer: Events are delivered via Socket.io (through MessageBus) or legacy WebSocket connections managed in
route/v1/notify_old.go. - API layer: REST endpoints in
route/v1/notify.goprovide external triggers for creating notifications and marking them read. - Transient data: System metrics use
SystemTempMapand are broadcast periodically fromroute/periodical.go.
Frequently Asked Questions
How does CasaOS store notification data permanently?
CasaOS uses GORM to persist notifications in a SQLite database. The AppNotify struct in service/model/app_notify.go defines the schema, and pkg/sqlite/db.go executes AutoMigrate to create the app_notify table during initialization. Each notification includes metadata fields like Class, State, and CustomId to support filtering and lifecycle management.
What is the difference between MessageBus and WebSocket delivery?
The MessageBus (common.SERVICENAME) is the primary event broker used by SendNotify() to publish JSON payloads to Socket.io channels, enabling decoupled, channel-based subscriptions. The WebSocket implementation in route/v1/notify_old.go is a legacy fallback that maintains persistent connections and pushes batched unread notifications via the SendMeg() goroutine. Modern implementations should prefer the MessageBus approach.
How can external services trigger notifications in CasaOS?
External services can POST to /v1/notify/:path to trigger notifications. The PostNotifyMessage handler in route/v1/notify.go validates the request and invokes Notify().SendNotify(), which publishes the event to the MessageBus and persists the record if configured. This allows CLI tools, scripts, or third-party apps to integrate with the CasaOS notification system without direct database access.
Where are temporary system metrics like CPU usage stored before being broadcast?
Transient system metrics are stored in the SystemTempMap concurrent map within the notifyServer struct. The SettingSystemTempData method stores values, and GetSystemTempMap retrieves them. The periodic task in route/periodical.go reads this map and broadcasts utilization data via SendNotify("casaos:system:utilization", body), ensuring the dashboard displays real-time statistics without database writes.
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 →