Skip to content

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:

{ "data": { ... } }

On error:

{ "error": { "code": "string", "message": "string" } }

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

{ "data": { "success": true, "message": "OpenSearch installed successfully" } }

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, runs apt-get update and apt-get install -y opensearch, and installs rsyslog-elasticsearch.
  • On RHEL/AlmaLinux: writes /etc/yum.repos.d/opensearch-2.x.repo and runs dnf install -y opensearch (plus rsyslog-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_replicas index template so the single-node cluster stays green.
  • Sets the OPENSEARCH_INITIAL_ADMIN_PASSWORD environment 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

{ "data": { "count": 42 } }

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

curl https://panel.example.com:2087/api/logvault/saved-searches \
  -H "Authorization: Bearer $TOKEN"

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

{ "data": { "message": "saved" } }

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_searches table.

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

{ "data": { "message": "deleted" } }

Errors

This endpoint does not return error responses; the delete result is not checked.

Side effects

  • Deletes the matching row from the logvault_saved_searches table.

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

curl https://panel.example.com:2087/api/logvault/sources \
  -H "Authorization: Bearer $TOKEN"

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_sources table. No rsyslog config is written until apply is 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

{ "data": { "success": true } }

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>.conf and runs systemctl 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

{ "data": { "success": true } }

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; call apply to 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

{ "data": { "success": true, "message": "Rsyslog config applied for port 514 (UDP)" } }

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 loads omelasticsearch plus the imudp or imtcp input 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, index logvault), 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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 db_error The update of logvault_sources failed.

Side effects

  • Flips the enabled column for the matching row in logvault_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

curl https://panel.example.com:2087/api/logvault/stats \
  -H "Authorization: Bearer $TOKEN"

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

{
  "data": {
    "installed": true,
    "running": true,
    "version": "2.11.0",
    "health": "green"
  }
}

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

curl https://panel.example.com:2087/api/logvault/status \
  -H "Authorization: Bearer $TOKEN"

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

{ "data": { "success": true, "message": "OpenSearch removed successfully" } }

Errors

Status Code Meaning
500 uninstall_failed The package manager failed to remove opensearch; the message includes command output.

Side effects

  • Runs systemctl stop opensearch and systemctl disable opensearch.
  • On Debian/Ubuntu runs apt-get remove -y opensearch; on RHEL/AlmaLinux runs dnf remove -y opensearch.

Example

curl -X POST https://panel.example.com:2087/api/logvault/uninstall \
  -H "Authorization: Bearer $TOKEN"