xyOps Backup Format
December 13, 2025 · View on GitHub
Overview
This document describes the xyOps Backup Format (XYBK) v1.0, used to bulk export and import data from a xyOps system. The format supports selecting categories of data (lists, database indexes, and extras), or including everything. Files are NDJSON with support for comment lines and blank lines, and are typically wrapped in Gzip for transport.
- Title: xyOps Backup Format
- ID: XYBK
- Version: 1.0
- Date: December 12, 2025
- Authors: Joseph Huckaby (PixlCore)
XYBK is primarily consumed by the Admin "Export Data" and "Import Data" features. The exporter streams a Gzip-compressed NDJSON file to the client, and the importer accepts either plain NDJSON or Gzip-wrapped NDJSON.
File Structure
An XYBK file is a sequence of UTF-8 text lines. Three types of lines are allowed:
- Comment: Any line beginning with
#is a comment and ignored by the importer. - Blank: Empty or whitespace-only lines are allowed and ignored.
- Record: A single JSON object on one line (NDJSON). These are processed in order.
The file begins with a short header emitted as a comment block (for human readability only):
# xyOps Data Export v1.0
# Hostname: [host]
# Date/Time: [string]
# Format: NDJSON
Following the header, the file contains one or more labeled sections (comment lines) and NDJSON records. Section headers are advisory and ignored by the importer. The importer only processes JSON lines that start with {.
Record Types
Each NDJSON record must be exactly one of the following forms:
Storage Put
{ "key": "<storage_key>", "value": /* json_or_base64 */ }
- Writes directly to pixl-server-storage as a key/value "put".
- For binary keys,
valuecontains a Base64 string. On import, binary detection is automatic via key pattern, and the value is decoded back to raw bytes. - For JSON keys,
valueis a JSON object which is stored as-is.
Storage Command
{ "cmd": "<method>", "args": [ /* arg1, arg2, ... */ ] }
- Invokes a storage API on pixl-server-storage, e.g.
listDelete. - Arguments are passed as-is. The importer appends its own callback internally.
- Used by exports to prepare state for re-population (e.g. delete list pages before re-creating them).
Database Record
{ "index": "<index_id>", "id": "<record_id>", "record": { /* ... */ } }
- Inserts a database record into Unbase via
unbase.insert(index, id, record). - Semantics are "create or replace" by ID.
Sections
The exporter adds comment section headers to group related lines. These are informational only and ignored during import. You may encounter the following section headers:
# List: <key># Database Index: <index> (<query>)# User Data:# Bucket Data# Bucket Files# Encrypted Secret Data# Job Files (<query>)# Ticket Files (<query>)# Monitor Timeline Data (<query>)
Lists
Many xyOps subsystems are modeled as storage "Lists" (paged arrays). See Lists for list internals. Exports include list metadata and all pages. The format is:
Pre-delete list to make way for the incoming one:
{ "cmd": "listDelete", "args": [ "<key>", false ] }
Add the list header (key/value):
{ "key": "<key>", "value": { "page_size": 100, "first_page": 1, "last_page": 5, "length": 500, "type": "list" } }
Add the list pages (key/value):
{ "key": "<key>/<page>", "value": { "type": "list_page", "items": [ /* ... */ ] } }
The items array contains the actual list items. Pages are emitted from first_page to last_page inclusive.
Typical list keys exported under global/ include:
alerts,api_keys,buckets,categories,channels,events,groups,monitors,plugins,secrets,tags,users,roles,web_hooks
Note: User account records themselves are not stored in global/users (that list holds the roster). Actual user records are exported under users/<username> (see "User Data" below).
Database Indexes
Exports can include full Unbase indexes, optionally filtered by a query. Each record is emitted as:
{ "index": "<index_id>", "id": "<record_id>", "record": { /* ... */ } }
Common index IDs include: alerts, jobs, servers, snapshots, activity, tickets.
See Unbase for more details.
User Data
User account records are exported as storage keys:
users/<normalized_username>→{ ...user record... }
If the "User Avatars" extra is selected, the following binary keys may also be included (Base64 values):
users/<normalized_username>/avatar/64.pngusers/<normalized_username>/avatar/256.png
Passwords in user records are stored as salted bcrypt hashes and are exported as stored.
Buckets
If the Buckets list is selected, the exporter also includes bucket data and may include file payloads, depending on extras:
key: buckets/<bucket_id>/data(JSON) containing the per-bucket metadata/data object.key: buckets/<bucket_id>/files(JSON) containing each file payload keyed by its storage path. File payloads are Base64.
Secrets
Secret vault metadata lives in the global/secrets list (exported like any list). The secret payloads themselves are exported under:
key: secrets/<secret_id>→ Value is the encrypted blob (as stored). Contents are Base64-encoded encrypted data; secrets are not exported in plaintext.
Job and Ticket Files/Logs
If selected via extras, job and ticket attachments are exported by key with Base64 payloads. For jobs, the compressed log may also be exported:
- Job files:
key: <file_path>for each file in a job’sfiles[]list (subject to max size). - Job log:
key: logs/jobs/<job_id>/log.txt.gzif present and under size limit. - Ticket files:
key: <file_path>for each file in a ticket’sfiles[]list (subject to max size).
Monitor Timeline Data
Server monitor time-series are stored as lists under timeline/<server_id>/<system_id>. When included, the exporter emits each timeline as a normal List (see Lists).
Data Selection
The UI exposes three selection groups which map to exported item types:
- Lists: One or more of the standard lists under
global/(see above). Choosingusersalso triggers "User Data" export forusers/<username>records. Choosingbucketstriggers "Bucket Data". Choosingsecretstriggers "Encrypted Secret Data". - Indexes: One or more Unbase indexes by ID (optionally filtered by query).
- Extras: Optional payloads and time-series:
job_filesjob_logsbucket_filesticket_filesmonitor_datastat_datauser_avatars
The exporter may be instructed to include "all" in any group. Internally, these selections are expanded into a stream of the record types described above.
Compression
Exports are streamed as Gzip files with a filename like xyops-data-export-YYYY-MM-DD-<id>.txt.gz. The importer accepts either a plain .txt NDJSON file or a Gzip-compressed .txt.gz file.
Security Characteristics
- API Keys: Only salted hashes are exported; plaintext API key material is never emitted. The
keyfield is a salted SHA-256 digest stored at creation time. - Secrets: Secret payloads are exported as encrypted blobs (Base64); plaintext is never exported.
- Users: Passwords are stored and exported as salted bcrypt hashes. No plaintext passwords are exported.
Example
Snippet showing list export, API keys, and a database record:
# xyOps Data Export v1.0
# Hostname: joemax.xyops.io
# Date/Time: Tue Nov 18 2025 12:01:27 GMT-0800 (Pacific Standard Time)
# Format: NDJSON
# List: global/alerts
{"cmd":"listDelete","args":["global/alerts",false]}
{"key":"global/alerts","value":{"page_size":100,"first_page":0,"last_page":0,"length":5,"type":"list"}}
{"key":"global/alerts/0","value":{"type":"list_page","items":[{"id":"load_avg_high","title":"High CPU Load", ...}]}}
# List: global/api_keys
{"cmd":"listDelete","args":["global/api_keys",false]}
{"key":"global/api_keys","value":{"page_size":100,"first_page":0,"last_page":0,"length":2,"type":"list"}}
{"key":"global/api_keys/0","value":{"type":"list_page","items":[{"key":"<salted_sha256>","active":1, ...}]}}
# Database Index: tickets (*)
{"index":"tickets","id":"tmhzbmbagig","record":{"subject":"Alert: High Active Jobs on raspberrypi", ...}}
Parsing Rules
- Comments and blank lines are ignored. Only lines that begin with
{are parsed. - Lines are processed in order. Commands may prepare state (e.g. list deletion) before subsequent puts.
- Key/value records are written via
storage.put(key, value). Binary keys are automatically Base64-decoded on import. - Database records are inserted via
unbase.insert(index, id, record). - Storage commands call the named method on the storage engine with provided args.
- The importer streams and validates line-by-line, collecting up to 100 errors for reporting, and continues past non-fatal errors.
Lists and Storage Notes
xyOps stores most configuration objects as Lists. Useful references:
Versioning
This document specifies XYBK v1.0. The exporter emits # xyOps Data Export v1.0 in the header. Future versions may add new section headers and record shapes, but importers ignore comments and only rely on the three NDJSON record types defined here.