LogVault¶
OpenSearch-backed log management: sources, search, stats, install/uninstall.
All endpoints below are mounted under /api. Authentication: Authorization: Bearer <token> unless noted as public.
Endpoint count: 15
Endpoints¶
| Method | Path | Handler | Guards |
|---|---|---|---|
POST |
/api/logvault/install |
installLogVault |
RequireSuperuser(), RequirePlugin(db, "logvault") |
GET |
/api/logvault/preserved-count |
getLogVaultPreservedCount |
RequireSuperuser(), RequirePlugin(db, "logvault") |
GET |
/api/logvault/saved-searches |
listLogVaultSavedSearches |
RequireSuperuser(), RequirePlugin(db, "logvault") |
POST |
/api/logvault/saved-searches |
createLogVaultSavedSearch |
RequireSuperuser(), RequirePlugin(db, "logvault") |
DELETE |
/api/logvault/saved-searches/:id |
deleteLogVaultSavedSearch |
RequireSuperuser(), RequirePlugin(db, "logvault") |
POST |
/api/logvault/search |
searchLogVault |
RequireSuperuser(), RequirePlugin(db, "logvault") |
GET |
/api/logvault/sources |
listLogVaultSources |
RequireSuperuser(), RequirePlugin(db, "logvault") |
POST |
/api/logvault/sources |
createLogVaultSource |
RequireSuperuser(), RequirePlugin(db, "logvault") |
DELETE |
/api/logvault/sources/:id |
deleteLogVaultSource |
RequireSuperuser(), RequirePlugin(db, "logvault") |
PUT |
/api/logvault/sources/:id |
updateLogVaultSource |
RequireSuperuser(), RequirePlugin(db, "logvault") |
POST |
/api/logvault/sources/:id/apply |
applyLogVaultSource |
RequireSuperuser(), RequirePlugin(db, "logvault") |
POST |
/api/logvault/sources/:id/toggle |
toggleLogVaultSource |
RequireSuperuser(), RequirePlugin(db, "logvault") |
GET |
/api/logvault/stats |
getLogVaultStats |
RequireSuperuser(), RequirePlugin(db, "logvault") |
GET |
/api/logvault/status |
getLogVaultStatus |
RequireSuperuser(), RequirePlugin(db, "logvault") |
POST |
/api/logvault/uninstall |
uninstallLogVault |
RequireSuperuser(), RequirePlugin(db, "logvault") |
Source¶
Handlers for this section live in api/routes/handlers_*.go in the StarkOne source. Each handler binds its request body with gin.ShouldBindJSON(&req) and responds with the standard envelope:
On error:
Reference¶
POST /api/logvault/install¶
Installs OpenSearch (the LogVault backend) by adding the vendor package repository, installing the opensearch package, writing single-node configuration, starting the service, and waiting up to 60 seconds for it to become ready.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
already_installed |
OpenSearch is already installed and running. |
500 |
install_failed |
The package manager failed to install the opensearch package; the message includes command output. |
500 |
start_failed |
OpenSearch was installed but did not come up within 60 seconds. |
Side effects
- On Debian/Ubuntu: imports the OpenSearch GPG key to
/usr/share/keyrings/opensearch-keyring, writes/etc/apt/sources.list.d/opensearch-2.x.list, runsapt-get updateandapt-get install -y opensearch, and installsrsyslog-elasticsearch. - On RHEL/AlmaLinux: writes
/etc/yum.repos.d/opensearch-2.x.repoand runsdnf install -y opensearch(plusrsyslog-elasticsearch). - Writes
/etc/opensearch/opensearch.yml(single-node, security disabled, bound to 127.0.0.1:9200) and/etc/opensearch/jvm.options.d/heap.options(heap sized to ~25% of RAM, clamped to 512m-4g). - Runs
systemctl daemon-reload,systemctl enable opensearch,systemctl start opensearch. - Sets the
default_zero_replicasindex template so the single-node cluster stays green. - Sets the
OPENSEARCH_INITIAL_ADMIN_PASSWORDenvironment variable during install.
Example
curl -X POST https://panel.example.com:2087/api/logvault/install \
-H "Authorization: Bearer $TOKEN"
GET /api/logvault/preserved-count¶
Returns the number of documents flagged _preserved in the logvault OpenSearch index; returns 0 if OpenSearch is unreachable.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Response - 200 OK
Errors
This endpoint does not return error responses. If the OpenSearch request or response decode fails, it returns 200 with { "data": { "count": 0 } }.
Example
curl https://panel.example.com:2087/api/logvault/preserved-count \
-H "Authorization: Bearer $TOKEN"
GET /api/logvault/saved-searches¶
Lists all saved log searches stored in the logvault_saved_searches table, most recently created first.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Response - 200 OK
{
"data": [
{
"id": 1,
"name": "Auth failures",
"query": "sshd AND failed",
"severity": "err",
"time_range": "24h",
"expires_at": "2028-06-17 12:00:00",
"created_at": "2026-06-17 12:00:00",
"preserve_data": 1
}
]
}
Errors
This endpoint does not return error responses. On a query error it returns 200 with an empty data array.
Example
POST /api/logvault/saved-searches¶
Creates a saved log search, computing an expiry timestamp from the expiry keyword and inserting a row into logvault_saved_searches.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Request body
{
"name": "string (required)",
"query": "string (optional)",
"severity": "string (optional)",
"time_range": "string (optional)",
"expiry": "string (optional)",
"preserve_data": "int (optional)"
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Display name for the saved search. |
query |
string | no | Search query text. |
severity |
string | no | Severity filter to store with the search. |
time_range |
string | no | Time range string to store with the search. |
expiry |
string | no | Retention keyword: 6m, 1y, never, or any other value (default) which means 2 years. never stores a null expiry. |
preserve_data |
int | no | Flag indicating whether matching data should be preserved. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
invalid_body |
Request body could not be parsed as JSON. |
400 |
missing_name |
name was empty. |
500 |
db_error |
The insert into logvault_saved_searches failed. |
Side effects
- Inserts a row into the
logvault_saved_searchestable.
Example
curl -X POST https://panel.example.com:2087/api/logvault/saved-searches \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Auth failures", "query": "sshd AND failed", "severity": "err", "time_range": "24h", "expiry": "1y", "preserve_data": 1}'
DELETE /api/logvault/saved-searches/:id¶
Deletes the saved search with the given id from logvault_saved_searches.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
string | The id of the saved search to delete. |
Response - 200 OK
Errors
This endpoint does not return error responses; the delete result is not checked.
Side effects
- Deletes the matching row from the
logvault_saved_searchestable.
Example
curl -X DELETE https://panel.example.com:2087/api/logvault/saved-searches/1 \
-H "Authorization: Bearer $TOKEN"
POST /api/logvault/search¶
Runs a query against the logvault* OpenSearch indices, building a bool query from the supplied query string, sources, severities, and time range, and returns matching log documents.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Request body
{
"query": "string (optional)",
"timeFrom": "string (optional)",
"timeTo": "string (optional)",
"sources": "[]string (optional)",
"severity": "[]string (optional)",
"limit": "int (optional)",
"from": "int (optional)"
}
| Field | Type | Required | Description |
|---|---|---|---|
query |
string | no | Lucene query string matched against the message field (wildcards analyzed). |
timeFrom |
string | no | Lower bound for the @timestamp range filter (e.g. now-24h or an RFC3339 timestamp). Defaults to now-24h when both bounds are empty. |
timeTo |
string | no | Upper bound for the @timestamp range filter. |
sources |
array of string | no | List of source_ip values to filter by. |
severity |
array of string | no | List of severity values to filter by. |
limit |
int | no | Page size. Values <= 0 or > 500 are coerced to 100. |
from |
int | no | Result offset for pagination. |
Response - 200 OK
{
"data": {
"hits": [
{
"@timestamp": "2026-06-17T12:00:00Z",
"host": "host1",
"severity": "err",
"message": "example log line",
"source_ip": "203.0.113.10"
}
],
"total": 1
}
}
hits contains the _source of each matching OpenSearch document; field shape depends on the ingested log format.
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Request body could not be parsed as JSON. |
502 |
opensearch_error |
The request to OpenSearch failed (e.g. service down). |
500 |
parse_error |
The OpenSearch response could not be parsed. |
Example
curl -X POST https://panel.example.com:2087/api/logvault/search \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "sshd AND failed", "timeFrom": "now-24h", "severity": ["err"], "limit": 100, "from": 0}'
GET /api/logvault/sources¶
Lists configured log sources from the logvault_sources table, ordered by id.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Response - 200 OK
{
"data": [
{
"id": 1,
"name": "edge-syslog",
"type": "syslog",
"protocol": "udp",
"port": 514,
"description": "Edge firewall syslog",
"enabled": true,
"created_at": "2026-06-17 12:00:00"
}
]
}
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
The query against logvault_sources failed. |
Example
POST /api/logvault/sources¶
Creates a log source row in logvault_sources, applying defaults for type, protocol, and port when omitted.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Request body
{
"name": "string (required)",
"type": "string (optional)",
"protocol": "string (optional)",
"port": "int (optional)",
"description": "string (optional)",
"include_local": "bool (optional)",
"allowed_ips": "string (optional)",
"replicate_to": "string (optional)"
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Source name. |
type |
string | no | Source type. Defaults to syslog. |
protocol |
string | no | Transport protocol. Defaults to udp. |
port |
int | no | Listen port. Defaults to 514. |
description |
string | no | Free-text description. |
include_local |
bool | no | Whether to include locally-originated messages (affects the rsyslog filter at apply time). |
allowed_ips |
string | no | Comma-separated list of IPs/CIDRs allowed to send (used at apply time). |
replicate_to |
string | no | Remote host or host:port to replicate logs to (used at apply time). |
Response - 201 Created
{
"data": {
"id": 1,
"name": "edge-syslog",
"type": "syslog",
"protocol": "udp",
"port": 514,
"description": "Edge firewall syslog",
"enabled": true
}
}
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body could not be parsed, or name was empty. |
500 |
db_error |
The insert into logvault_sources failed. |
Side effects
- Inserts a row into the
logvault_sourcestable. No rsyslog config is written untilapplyis called.
Example
curl -X POST https://panel.example.com:2087/api/logvault/sources \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "edge-syslog", "type": "syslog", "protocol": "udp", "port": 514, "description": "Edge firewall syslog", "allowed_ips": "203.0.113.0/24"}'
DELETE /api/logvault/sources/:id¶
Deletes a log source by id, also removing its rsyslog config file and reloading rsyslog when a port is found.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
string | The id of the source to delete. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
The delete from logvault_sources failed. |
Side effects
- If the source has a port, removes
/etc/rsyslog.d/logvault-<port>.confand runssystemctl restart rsyslog. - Deletes the matching row from
logvault_sources.
Example
curl -X DELETE https://panel.example.com:2087/api/logvault/sources/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/logvault/sources/:id¶
Updates the name, type, protocol, port, and description of a log source.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
string | The id of the source to update. |
Request body
{
"name": "string (optional)",
"type": "string (optional)",
"protocol": "string (optional)",
"port": "int (optional)",
"description": "string (optional)"
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | New source name. |
type |
string | no | New source type. |
protocol |
string | no | New transport protocol. |
port |
int | no | New listen port. |
description |
string | no | New description. |
No field carries a binding:"required" tag; all are written as-is to the row, so omitted fields are stored as their zero values.
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Request body could not be parsed as JSON. |
500 |
db_error |
The update of logvault_sources failed. |
Side effects
- Updates the matching row in
logvault_sources. Does not rewrite rsyslog config; callapplyto regenerate it.
Example
curl -X PUT https://panel.example.com:2087/api/logvault/sources/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "edge-syslog", "type": "syslog", "protocol": "tcp", "port": 1514, "description": "Edge firewall syslog"}'
POST /api/logvault/sources/:id/apply¶
Generates and writes the rsyslog receiver config for a source (input module, JSON template, optional IP/local filters and remote replication) and restarts rsyslog so it takes effect.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
string | The id of the source whose rsyslog config should be applied. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
404 |
not_found |
No source with the given id exists. |
500 |
write_failed |
The rsyslog config file could not be written. |
500 |
rsyslog_failed |
systemctl restart rsyslog failed; the message includes command output. |
Side effects
- Writes
/etc/rsyslog.d/logvault-<port>.conf(0644). The config loadsomelasticsearchplus theimudporimtcpinput module depending on the source protocol, defines a JSON template, applies an optional IP allow-list and local-message filter, ships logs to the local OpenSearch (127.0.0.1:9200, indexlogvault), and optionally adds a second action replicating to the configured remote host. - Runs
systemctl restart rsyslog.
Example
curl -X POST https://panel.example.com:2087/api/logvault/sources/1/apply \
-H "Authorization: Bearer $TOKEN"
POST /api/logvault/sources/:id/toggle¶
Toggles a source's enabled flag between on and off in the logvault_sources table.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
string | The id of the source to toggle. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
The update of logvault_sources failed. |
Side effects
- Flips the
enabledcolumn for the matching row inlogvault_sources. Does not rewrite rsyslog config.
Example
curl -X POST https://panel.example.com:2087/api/logvault/sources/1/toggle \
-H "Authorization: Bearer $TOKEN"
GET /api/logvault/stats¶
Returns aggregate statistics for the logvault* indices: total document count, on-disk index size, logs ingested in the last minute, and an hourly histogram for the last 24 hours.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Response - 200 OK
{
"data": {
"doc_count": 12345,
"index_size": "1.5 GB",
"logs_per_min": 60,
"histogram": [
{ "key": "2026-06-17T11:00:00.000Z", "count": 1200 }
]
}
}
Fields default to 0 / empty / empty array when OpenSearch is unreachable or returns no data.
Errors
This endpoint does not return error responses. OpenSearch failures result in zeroed/empty fields rather than an error status.
Example
GET /api/logvault/status¶
Reports whether OpenSearch is installed and running, plus its version and cluster health when reachable.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Response - 200 OK
installed is true if the opensearch binary is on PATH or /usr/share/opensearch exists. running reflects systemctl is-active opensearch. version and health are populated from the OpenSearch root and /_cluster/health endpoints only when running, and are empty strings otherwise.
Errors
This endpoint does not return error responses.
Example
POST /api/logvault/uninstall¶
Stops and disables OpenSearch and removes the opensearch package via the system package manager.
Guards: RequireSuperuser(), RequirePlugin(db, "logvault")
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
uninstall_failed |
The package manager failed to remove opensearch; the message includes command output. |
Side effects
- Runs
systemctl stop opensearchandsystemctl disable opensearch. - On Debian/Ubuntu runs
apt-get remove -y opensearch; on RHEL/AlmaLinux runsdnf remove -y opensearch.
Example