# On-disk formats

> Every file the system crate and the core crate read or write for other programs, with exact paths, keys, fields and permissions, and the format-version rule.

These files are contracts: an app, the root helper and a newer or older release all read them. Every file an app or a service writes for another to read carries its format version.

## The format-version rule

- `"format": 1` on each line of `history.jsonl` and `events.jsonl` (`history::FORMAT`, `events::FORMAT`). `version` in those lines is the OS version, so the format has its own name.
- `Format=1` under `[Atlas]` in settings files (`settings::FORMAT`).
- Readers accept a missing format (written before 1.3.0) and a higher one (written by a newer program; unknown fields are ignored) forever.
- The number is raised only for a change an old reader would misread.
- `crates/*/tests/fixtures/` holds files written by older versions. Tests read them, and they are never edited, only added to.

## Settings file

`$XDG_CONFIG_HOME/atlas-<app>rc` (default `~/.config`), KConfig INI. Written by [`settings`](https://atlasos.eterneon.net/framework/atlas-framework-core/settings.md) and by Atlas.Ui's [AtlasSettings](https://atlasos.eterneon.net/framework/atlas-ui/atlas-settings.md), under the lock file `.atlas-<app>rc.lock` beside it.

```text
[Atlas]
Format=1

[Restart]
ScheduledAt=1700000000
```

## History

`/var/lib/atlas-core/history.jsonl`, mode 0644, written by `atlas-system-helper record-boot` as root. One JSON object per line, appended when the booted digest differs from the newest line. The path keeps the old atlas-core name because existing systems hold data there. Read by [`history`](https://atlasos.eterneon.net/framework/atlas-framework-system/history.md).

```json
{"format":1,"version":"44.20261003","digest":"sha256:...","image":"ghcr.io/eternalcoder454/atlasos:stable","timestamp":"2026-10-03T04:00:00Z","first_booted":"2026-10-04T08:12:00Z"}
```

| Field | Description |
|---|---|
| `format` | `1`. Absent on lines from before 1.3.0 |
| `version` | OS version; may be `null` or absent |
| `digest` | Image digest |
| `image` | Image reference; may be `null` or absent |
| `timestamp` | Image build time, RFC 3339; may be `null` or absent |
| `first_booted` | When this machine first booted it, RFC 3339 UTC |

## Events

`/var/lib/atlas-core/events.jsonl`, mode 0644, with the lock file `events.jsonl.lock` (0600) beside it. One JSON object per line. When the file passes 512 KiB it is cut to the newest lines (at most 1000 lines and 256 KiB). Read by [`events`](https://atlasos.eterneon.net/framework/atlas-framework-system/events.md) and by `crash::collect_events`.

```json
{"format":1,"event":"update-failed","version":"44.20261003","error":"...","time":"2026-10-04T08:12:00Z"}
```

| Field | Description |
|---|---|
| `format` | `1`. Absent on older lines |
| `event` | `update-staged`, `update-failed`, `rollback-requested`, `rollback-failed`, `channel-switched`, `channel-switch-failed`, `update-applied`, `rollback-applied`, `automatic-rollback`, `health-check-failed` or `health-check-passed` |
| `version` | OS version. Left out when unknown |
| `error` | Scrubbed error text, at most 300 characters. Left out when none |
| `time` | RFC 3339 UTC |

## Boot health

`/var/lib/atlasos/bad-image-digests` (`bootc::BAD_IMAGE_DIGESTS`): digests of images that failed their boot health checks and were rolled back by greenboot, one per line, newest last, at most 20. The AtlasOS image's greenboot `red.d` script adds a digest and its `green.d` script removes one that later boots healthy. Readable without root.

## Crash reporting

All user files except `crash-reporting.toml` (0644) are under XDG directories and written with mode 0600 in directories of mode 0700, without following symlinks.

| Path | Contents |
|---|---|
| `~/.config/atlas/crash-reporting.toml` | `enabled = true` or `false`, after a comment line. Off unless `true`. Mode 0644 |
| `/etc/atlas/crash-reporting.toml` | System endpoint: `dsn = "https://<key>@<host>/<project>"`. An empty `dsn` turns sending off |
| `/usr/share/atlas/crash-reporting.toml` | The shipped default endpoint; used only when `/etc` has no `dsn` key |
| `$XDG_STATE_HOME/atlas/crash-reports/pending/<time>-<nn>.json` | Reports waiting for the user's decision. `<time>` is the report's RFC 3339 time and `<nn>` a counter from `00` for reports in the same second |
| `$XDG_STATE_HOME/atlas/crash-reports/sent/<time>-<nn>.json` | Sent reports, with `sent_event_id` and `issue_url` filled in. Removed after 90 days |
| `$XDG_STATE_HOME/atlas/crash-id` | The rotating anonymous ID: line 1 is 32 hex characters, line 2 the creation time in Unix seconds. Replaced after 30 days |
| `$XDG_STATE_HOME/atlas/coredump-last` | The coredump marker: a Unix time in microseconds |
| `$XDG_STATE_HOME/atlas/events-last` | The event marker: an RFC 3339 time, a space, and how many events at that time were already taken |
| `$XDG_STATE_HOME/atlas/send-*.json` | A transient curl body file during a send; stale ones (older than 10 minutes) are swept |

`$XDG_STATE_HOME` defaults to `~/.local/state`.

### Crash reports

A report file is the pretty-printed JSON of `crash::Report` (schema 2). A report without `event_id` (schema 1) is deleted when found. `path` is never stored.

```json
{
  "schema": 2,
  "event_id": "<32 hex>",
  "report_type": "panic",
  "time": "2026-10-04T08:12:00Z",
  "crash_id": "<32 hex>",
  "atlasos_version": "44.20261003",
  "channel": "stable",
  "previous_version": null,
  "app_name": "net.eterneon.atlas.notepad",
  "app_version": "1.0.0",
  "category": "Atlas app",
  "message": "...",
  "stacktrace": "...",
  "kernel": "...",
  "gpu": null,
  "gpu_driver": null,
  "uptime_secs": 0,
  "cpu_model": null,
  "ram_total_kb": 0,
  "mem_used_kb": 0,
  "sent_event_id": null,
  "issue_url": null
}
```

What `crash::send` posts is not this file but `Report::payload()`, a Sentry event: `event_id`, `timestamp`, `platform` (`native`), `level` (`fatal`), `logger` (`atlas-core`), `release` (`atlasos@<version>`), `environment` (the channel), `message`, `tags`, `contexts` (`os`, `gpu`, `device`, `runtime`) and, when the trace has frames, `exception`. It goes to the endpoint's `store_url` with the header `X-Sentry-Auth: Sentry sentry_version=7, sentry_key=<key>, sentry_client=atlas-core/<version>`.

The relay's answer is JSON `{"id": ..., "url": ...}`. The `url` is kept only if `crash::is_issue_url` accepts it, and the `id` only if it is a plain event ID.

## Notifications

`<short name>.notifyrc`, shipped in `/usr/share/knotifications6/` with `DesktopEntry=<app id>` and one `[Event/<eventId>]` group per kind of notification (`Action=Popup`). The user's choice is written by Plasma to `~/.config/<short name>.notifyrc`. See [notify](https://atlasos.eterneon.net/framework/atlas-framework-system/notify.md).
