Skip to content
Encrypted Diagnostic Export

Encrypted Diagnostic Export

AfterTouch can produce an encrypted diagnostic report that users can download and send to the project maintainer without exposing sensitive data to third parties. The report is encrypted with an SSH public key using age; only the holder of the matching private key can read it.


What the report contains

The encrypted .age file decrypts to a .tar.gz archive with:

  • diagnostic.json — structured summary:
    • Service version and build info
    • Full health-check results (same data as the Health tab)
    • Per-device state: sources (IDs, names, SourceKeyTypes), presets (slot, name, Source, SourceID, location), device product code, firmware version, IP, name
  • datastore/accounts/{id}/devices/{id}/*.xml — raw XML files verbatim from the sender’s datastore (Presets.xml, Sources.xml, Recents.xml, …)
  • stats/activity/{kind}/*.json — the local admin-UI activity log (e.g. announcement-banner dismissals), verbatim, one file per recorded event

Having both the structured JSON and the raw XML lets you compare what the service serves via HTTP against what is actually stored on disk.

What is excluded from the JSON: authentication tokens, credentials, OAuth secrets, Spotify refresh tokens. The raw XML files are included as-is.


Local activity log

AfterTouch records a small local activity log for admin-UI actions — today, just announcement-banner dismissals (e.g. the admin-area-gate notice from issue #419) — under stats/activity/{kind}/ in the data directory. Each event is its own plain JSON file (id, timestamp, and any detail), readable with a text editor; there is no encoding or opaque format to decode.

This follows the same “all data stays on your network” principle as the rest of AfterTouch: nothing here is ever transmitted automatically. The only way it leaves the operator’s network is the same as everything else in this document — an explicitly-triggered diagnostic export, which the operator has to click a button and choose to send.


Maintainer setup (one-time)

This section is for the project maintainer only. Users never need to touch keys.

1. Generate the key pair

bash scripts/setup-diagnostic-key.sh

This creates:

  • keys/private/diagnostic — SSH ed25519 private key (gitignored, never commit)
  • keys/private/diagnostic.pub — copy for reference (gitignored)
  • keys/public/diagnostic.pub — public key committed to the repo

2. Add the public key to GitHub

Go to https://github.com/settings/ssh/new and paste the contents of keys/public/diagnostic.pub. This makes the key visible at https://github.com/gesellix.keys so users can independently verify that the key embedded in the binary matches a key actually controlled by the maintainer.

3. Embed the public key in the binary

Open pkg/service/export/encrypt.go and update the DiagnosticPublicKey constant to match the new public key:

const DiagnosticPublicKey = "ssh-ed25519 AAAA... aftertouch-diagnostic@gesellix"

4. Commit

git add keys/public/diagnostic.pub pkg/service/export/encrypt.go
git commit -m "keys: add diagnostic SSH public key"

keys/private/ is .gitignored — the private key will not be committed.

5. Back up the private key

The private key is not stored in git. Keep a copy in a secure location (password manager, encrypted USB drive, etc.). If it is lost, a new key pair must be generated and the constant in encrypt.go updated.


Verifying the embedded key (users)

Users who want to confirm that the key embedded in their running binary matches the maintainer’s GitHub SSH keys can run:

# Compare the raw key text — both should show the same line:
curl -s https://github.com/gesellix.keys
cat keys/public/diagnostic.pub

The key should appear verbatim in both outputs.


Decrypting a received report (maintainer)

When a user sends you an aftertouch-diagnostic-*.age file, use the helper script (no extra tools needed — only Go and the private key). Run from the repository root directory:

# Decrypt and extract in one step:
go run scripts/decrypt-diagnostic.go aftertouch-diagnostic-<timestamp>.age | tar xz

# Or decrypt to a .tar.gz first, then inspect:
go run scripts/decrypt-diagnostic.go aftertouch-diagnostic-<timestamp>.age > report.tar.gz
tar xzf report.tar.gz
# → diagnostic.json
# → datastore/accounts/{id}/devices/{id}/Presets.xml  (and Sources.xml, Recents.xml, …)

The script uses only the filippo.io/age Go module — no separate age CLI installation required.


User workflow

  1. Open the AfterTouch admin UI and go to the Health tab.
  2. Click Download diagnostic report.
  3. The browser downloads aftertouch-diagnostic-<timestamp>.age.
  4. Attach the file to the GitHub issue or send it via a direct channel.

The file is opaque binary — the user cannot read it. All they see is that the report was generated and downloaded.


Key rotation

If the private key is compromised or lost:

  1. Run scripts/setup-diagnostic-key.sh (delete the old keys/private/diagnostic first).
  2. Add the new public key to GitHub and remove the old one.
  3. Update DiagnosticPublicKey in encrypt.go.
  4. Commit and tag a new release.

Old reports encrypted with the previous key cannot be decrypted with the new key.

Last updated on