Reference
Upgrade Guide
Upgrade Guide
How to update your eIOU node to the latest version while preserving your wallet, transaction history, and configuration.
Table of Contents
Overview
eIOU uses Docker named volumes to separate user data from application code.
Source code lives at /app/eiou/ (baked into the image), while user data is
persisted on seven named volumes. When you upgrade to a new image, the
container is recreated with updated code while those volumes are reattached,
preserving wallet data, plugin state, backups, backup coordination state, and
TLS material.
The short version:
# 1. Create a backup
docker exec <container> eiou backup create
# 2. Pull/build the new image, recreate the container
docker-compose -f <compose-file>.yml up -d --build
# 3. Verify
docker exec <container> eiou info
Your wallet, contacts, transaction history, and settings are preserved automatically.
What Gets Preserved
Preserved (stored on named volumes)
| Data | Volume | Container Path | Notes |
|---|---|---|---|
| Database (transactions, contacts, balances) | {node}-mysql-data |
/var/lib/mysql |
All structured data (encrypted at rest via MariaDB TDE) |
| Wallet keys (encrypted) | {node}-config |
/etc/eiou/config/userconfig.json |
Public key, encrypted private key, mnemonic |
| Master encryption key | {node}-config |
/etc/eiou/config/.master.key |
Derived from seed phrase; recoverable via restore. Optionally encrypted with a volume passphrase (stored as .master.key.enc) |
| Database credentials (encrypted) | {node}-config |
/etc/eiou/config/dbconfig.json |
Auto-generated; encrypted at rest with AES-256-GCM |
| User settings | {node}-config |
/etc/eiou/config/defaultconfig.json |
Fee preferences, transport mode, update check preference, etc. |
| Installed plugins | {node}-plugins |
/etc/eiou/plugins |
Operator-installed plugin directories; bundled plugins are re-seeded from the image on first boot |
| Plugin runtime state | {node}-plugin-scratch |
/var/lib/eiou/plugin-scratch |
Each sandboxed plugin’s private state (keys, balances, ledgers, file-backed stores, caches) under its own 0700 subdir |
| Encrypted backups | {node}-backups |
/var/lib/eiou/backups/*.eiou.enc |
AES-256-GCM encrypted database dumps |
| Backup coordination | {node}-backup-locks |
/var/lib/eiou/backup-locks |
Lifecycle lock and durable pending archive-snapshot obligation |
| SSL certificates and certbot state | {node}-ssl-cert |
/var/lib/eiou/ssl |
One volume with ssl/letsencrypt/ (certbot bookkeeping) and ssl/nginx/ (cert nginx serves on :443) subdirectories; startup.sh symlinks /etc/letsencrypt and /etc/nginx/ssl into them. Self-signed cert fingerprint stable across rebuilds |
Updated automatically (overwritten by new image)
| Data | Container Path | Notes |
|---|---|---|
| PHP source code | /app/eiou/src/ |
Baked into image at build time |
| Web GUI files | /app/eiou/www/ |
Baked into image at build time |
| API/CLI entry points | /app/eiou/api/, /app/eiou/cli/ |
Baked into image at build time |
| Background processors | /app/eiou/processors/ |
Baked into image at build time |
| Composer autoloader | /app/eiou/vendor/ |
Baked into image at build time |
Regenerated (not persisted across container recreation)
| Data | Container Path | Notes |
|---|---|---|
| Tor hidden service keys | /var/lib/tor/hidden_service/ |
Deterministically derived from wallet seed phrase |
| MariaDB TDE config | /etc/mysql/conf.d/encryption.cnf |
Generated on first boot; recreated from master key on subsequent boots |
| MariaDB TDE key file | /dev/shm/.mariadb-encryption-key |
RAM-backed; re-derived from master key on every boot |
Tor address is stable: The .onion address is derived deterministically from your BIP39 seed phrase. A new container will produce the same Tor address as long as the wallet data (in the {node}-config volume) is present.
How It Works
The upgrade mechanism relies on several components working together:
1. Named Volumes Survive Container Recreation
Docker named volumes persist independently of containers. When docker-compose up recreates a container, the named volumes are reattached to the new container at the same mount points. The data on the volumes is untouched.
2. Config File Migration
Older images stored config files at /etc/eiou/ (root level). Current images expect them at /etc/eiou/config/. On startup, startup.sh detects config files at the legacy location and migrates them to /etc/eiou/config/ automatically. This ensures upgrades from any prior version work without manual intervention.
3. Source/Data Separation
Source code and user data are stored in separate locations:
- Source code (
/app/eiou/) is baked into the image at build time. When a new image is built, it contains the updated code. No runtime sync is needed. - User data (
/etc/eiou/config/) is stored on the{node}-confignamed volume and is never overwritten by image updates. - Composer dependencies are installed during image build (
composer install --no-dev --optimize-autoloader), not at runtime. The vendor directory at/app/eiou/vendor/is baked into the image.
4. Database Migrations on Application Init
Application.php calls DatabaseSetup::runMigrations() on every startup. This adds any new tables or columns required by the updated code without affecting existing data.
5. Automatic Pre-Shutdown Backup
When the container receives SIGTERM (from docker compose up -d --build or docker compose down), graceful_shutdown() creates an encrypted database backup before stopping any processors or services. This ensures a recent backup exists even if the user forgot to run eiou backup create manually. The backup is stored on the {node}-backups volume, which is preserved across container recreations.
6. Maintenance Mode During Startup
On startup, startup.sh creates a lockfile (/tmp/eiou_maintenance.lock) before beginning database migrations. While this lockfile exists, all HTTP entry points (API, GUI, P2P transport) return 503 Service Unavailable with a Retry-After: 30 header. This prevents:
- Requests hitting a mid-migration database schema
- Incoming P2P messages being processed before the node is fully initialized
The response body is content-negotiated from the request’s Accept header:
- Browsers (
Accept: text/html...) receive a styled, self-contained HTML page (“Under Maintenance — Your eIOU node is starting up or going through an update. Check back in a little bit.”) with a<meta http-equiv="refresh" content="15">so it auto-recovers without requiring JavaScript (Tor Browser friendly). The page is fully inlined — no external CSS/JS/fonts — since/app/eiou/may be mid-rebuild and none of the app’s own assets are guaranteed resolvable. - API clients (
Accept: application/json,*/*, or missing) receive the existing JSON response ({"success": false, "status": "maintenance", "error": {"message": "...", "code": "maintenance_mode"}}).
Both paths set HTTP 503 and Retry-After: 30.
The lockfile is removed after all initialization is complete (composer install, migrations, processor startup).
7. Data-at-Rest Encryption (MariaDB TDE)
On first startup after wallet creation, MariaDB Transparent Data Encryption (TDE) is enabled automatically. The TDE key is derived from the master encryption key via HMAC-SHA256 and written to /dev/shm (RAM-backed, lost on restart). MariaDB is restarted once to load the file_key_management plugin, then all existing tables are encrypted. On subsequent boots, the TDE key is re-derived before MariaDB starts — no user action is needed.
Database credentials in dbconfig.json are also encrypted at rest with AES-256-GCM on first boot after the master key becomes available.
8. Update Version Check
A daily cron job (2 AM UTC) checks Docker Hub for newer image tags and caches the result in /etc/eiou/config/update-check.json. If Docker Hub is unreachable, it falls back to GitHub Releases. The check is read-only, cached for 24 hours, and respects the user’s updateCheckEnabled setting (enabled by default). Tor-only nodes silently skip the check. When an update is available, a notification is shown in the GUI dashboard.
Visual Flow
Old Container (running v1) — receives SIGTERM:
1. Pre-shutdown backup — encrypted backup saved to {node}-backups volume
2. Processor shutdown — SIGTERM to all PHP processors, wait for completion
3. Service shutdown — web server, MariaDB, Tor, cron stopped in order
4. Lockfile cleanup — processor lockfiles and shutdown flag removed
│ container removed, volumes kept, new image built
▼
New Container (running v2) — startup.sh runs:
1. Maintenance mode ON — /tmp/eiou_maintenance.lock created (HTTP → 503)
2. Config migration — moves legacy config files to /etc/eiou/config/ if needed
3. Volume decryption — if volume passphrase active, decrypt master key to /dev/shm
4. TDE key setup — derive MariaDB TDE key from master key, write to /dev/shm;
if encryption.cnf lost (container rebuild), recreate it
from master key so MariaDB can read encrypted data
5. MariaDB version check — compare binary version to stored version on volume;
if mismatch, use force-recovery to regenerate redo logs
5b. Missing redo log check — if ibdata1 exists but ib_logfile0 does not (broken
prior container), move broken data aside, reinitialize
MariaDB, recreate database + tables from config, enable
TDE, and auto-restore from latest backup
6. Services start — web server, MariaDB, Tor, cron
7. MariaDB upgrade — if version changed, run mariadb-upgrade + store new version
8. Database migrations — adds new tables/columns as needed
9. TDE first-time setup — if new, encrypt existing tables (MariaDB restarts once)
10. Credential encryption — encrypt dbconfig.json credentials if not already encrypted
11. Cron jobs — install update check, analytics, backup cron entries
12. Maintenance mode OFF — lockfile removed, HTTP requests accepted
13. Processors start — P2P, Transaction, Cleanup, ContactStatus
Result:
├── /var/lib/mysql ← same volume reattached (data intact)
├── /app/eiou/ ← new source code (baked into image)
├── /etc/eiou/config/ ← same volume reattached (wallet configuration)
├── /etc/eiou/plugins/ ← same volume reattached (installed plugins)
├── /var/lib/eiou/plugin-scratch ← same volume reattached (plugin state)
├── /var/lib/eiou/backups ← same volume reattached (encrypted backups)
├── /var/lib/eiou/backup-locks ← same volume reattached (backup coordination)
└── /var/lib/eiou/ssl ← same volume reattached (TLS state)
Upgrade Procedures
Method 1: Local Build (from source)
Use this when you have the repository cloned and want to build from the latest source.
cd /path/to/eiou-docker
# Pull the latest source
git pull origin main
# Create a pre-upgrade backup
docker exec <container-name> eiou backup create
# Rebuild image and recreate container (volumes preserved)
docker-compose -f <compose-file>.yml up -d --build
What happens:
- Docker builds a new image from the updated source
- Docker Compose sends SIGTERM to the running container
- Automatic pre-shutdown backup is created (encrypted, stored on
{node}-backupsvolume) - PHP processors receive SIGTERM and finish their current work gracefully
- Services stop in reverse order (web server, MariaDB, Tor, cron)
- Container is removed, named volumes are kept
- New container starts with maintenance mode enabled (HTTP requests return 503)
- New source code is available at
/app/eiou/(baked into the image, including updated vendor dependencies) - Database migrations run if needed (idempotent — only new tables/columns added)
- Maintenance mode is released — HTTP requests are accepted again
- Background processors start normally
Method 2: Docker Hub Pull
Use this when pulling a pre-built image from Docker Hub.
# Create a pre-upgrade backup
docker exec <container-name> eiou backup create
# Pull the latest image
docker pull eiou/eiou:latest
# Recreate container with the new image
docker-compose -f <compose-file>.yml up -d
Note: If your
docker-composefile usesbuild:instead ofimage:, you need to either change it toimage: eiou/eiou:latestor use Method 1 instead.
Method 3: Multi-Node Upgrade (4-line, 10-line, cluster)
For multi-node topologies, all nodes are upgraded together since they share the same image.
cd /path/to/eiou-docker
# Pull latest source
git pull origin main
# Create backups for all nodes
docker-compose -f docker-compose-4line.yml exec alice eiou backup create
docker-compose -f docker-compose-4line.yml exec bob eiou backup create
docker-compose -f docker-compose-4line.yml exec carol eiou backup create
docker-compose -f docker-compose-4line.yml exec daniel eiou backup create
# Rebuild and recreate all containers
docker-compose -f docker-compose-4line.yml up -d --build
Each node’s named volumes (alice-mysql-data, alice-config,
alice-backups, alice-backup-locks, etc.) are reattached to their
respective new containers.
Verification
After upgrading, verify the node is running correctly:
# Check container is healthy
docker ps | grep eiou
# Check node information (wallet address, hostname, etc.)
docker exec <container-name> eiou info --show-auth
# Check backup list (should include pre-upgrade backup)
docker exec <container-name> eiou backup list
# View startup logs for any errors
docker logs <container-name> 2>&1 | head -100
What to look for in the logs:
Wallet already configuredoreIOU has been initiated— existing wallet detected on volumeMariaDB TDE: key file ready— TDE key derived from master key successfullyEnabling MariaDB data-at-rest encryption...— first-time TDE setup (normal on first upgrade to a TDE-enabled version)MariaDB version change detected: X.Y.Z -> A.B.C— version mismatch detected, redo logs will be cleaned (normal after image rebuild with a different MariaDB patch)MariaDB upgrade completed successfully—mariadb-upgraderan after version changeMariaDB: Adding version tracking— first boot with version tracking enabled (normal on first upgrade to v0.1.6+)Update check cron job installed (daily at 2 AM UTC)— version check activeAnalytics cron job installed (daily at 3 AM UTC)— analytics cron activeeIOU Node started successfully!— all processors running, ready to receive
Rollback
If the new version has issues and you need to go back:
Option A: Rebuild from an Older Commit
cd /path/to/eiou-docker
# Check out the previous working version
git log --oneline -10 # find the commit hash
git checkout <commit-hash>
# Rebuild with the old code
docker-compose -f <compose-file>.yml up -d --build
The older image contains the previous source code at /app/eiou/. Your data volumes remain untouched.
Option B: Restore from Backup
If the database was affected by a migration issue:
# List available backups
docker exec <container-name> eiou backup list
# Stop the original node. Do not remove its volumes.
docker compose down
# Save this as recovery-candidate.yml. It replaces the MySQL and backup-locks
# mounts as a paired generation; config, backups, plugins, and certificates
# remain the original node's volumes.
cat > recovery-candidate.yml <<'YAML'
services:
eiou:
environment:
EIOU_BOOT_RECOVERY_BACKUP: "${RECOVERY_BACKUP:?set RECOVERY_BACKUP}"
volumes:
- recovery-mysql:/var/lib/mysql
- recovery-backup-locks:/var/lib/eiou/backup-locks
volumes:
recovery-mysql:
# Replace this with a unique name that has never held database data.
name: replace-with-a-new-empty-candidate-volume-name
recovery-backup-locks:
# Keep this unique volume paired with the candidate MySQL generation.
name: replace-with-a-new-empty-candidate-lock-volume-name
YAML
# Restore into the empty candidate and follow startup to completion.
export RECOVERY_BACKUP=<backup-filename>
docker compose -f docker-compose.yml -f recovery-candidate.yml up -d
docker compose -f docker-compose.yml -f recovery-candidate.yml logs -f eiou
# Validate the candidate before promotion.
docker compose -f docker-compose.yml -f recovery-candidate.yml exec eiou eiou info
docker compose -f docker-compose.yml -f recovery-candidate.yml exec eiou eiou verify-chain
The wallet GUI can create and verify backups, but online restore is disabled:
mysqldump DDL is not atomic against a serving database. Restore into an isolated
new-volume candidate during pre-service boot, validate it, then promote the
candidate. To promote this candidate, first remove the entire environment:
block from recovery-candidate.yml; it is a one-boot recovery request. Keep the
remaining override in every future Compose command so the service continues to
mount the candidate MySQL and backup-locks volumes as one generation. Retain
the original MySQL and backup-locks pair until the restored node has been
validated and backed up; the candidate never mutates their recovery journal.
Do not copy lifecycle.lock or the recovery/ directory between lock volumes.
If an archive-backup-pending obligation must follow the promoted candidate,
copy only that marker while both nodes are stopped. After removing the
environment block, run
unset RECOVERY_BACKUP.
v0.1.18-alpha backup cutoff: earlier encrypted artifacts are intentionally rejected. Restore one with the earlier image that created it, then immediately create a v3 full snapshot. Both scheduled and archive-triggered v3 snapshots contain the complete database and an authenticated schema/table manifest. The encrypted record stream is chunked so memory stays bounded as history grows. This is a hard legacy cutoff.
Troubleshooting
Container starts but wallet is gone
The named volumes were removed (likely by docker-compose down -v). The -v flag deletes all named volumes.
Recovery options:
- From backup: If the
{node}-backupsvolume still exists, or you have a backup file, restore from it - From seed phrase: If you have your 24-word mnemonic, restore the wallet:
The master key is derived deterministically from the seed phrase, so restoring from seed recovers the same master key. Old encrypted backups remain decryptable.environment: - RESTORE=word1 word2 word3 ... word24
Database connection error after upgrade
The dbconfig.json file may have been accidentally removed or corrupted. Check that it exists:
docker exec <container-name> cat /etc/eiou/config/dbconfig.json
If missing, the database credentials are lost. You would need to restore from backup with a fresh wallet setup.
SSL certificate warnings after upgrade
Self-signed SSL certificates are regenerated when a container is recreated. This is normal. If you use external certificates, ensure your bind mount is configured:
volumes:
- /path/to/certs:/ssl-certs:ro
MariaDB fails to start after upgrade
Version mismatch (most common after image rebuild): If MariaDB was upgraded to a different patch version between image builds (e.g., 10.11.6 → 10.11.14), the InnoDB redo logs on the persistent volume are incompatible with the new binary. The error log shows: Reading log encryption info failed; the log was created with MariaDB X.Y.Z. Starting with v0.1.8-alpha, startup.sh handles this automatically:
- Before starting MariaDB, it compares the binary version against
/var/lib/mysql/.mariadb_version - On mismatch, it starts MariaDB with
innodb_force_recovery=1to bypass stale redo logs - It performs a clean shutdown to regenerate redo logs in the new version’s format
- It restarts MariaDB normally, then runs
mariadb-upgradeand stores the new version
If the proactive check is bypassed (e.g., first boot with version tracking), a reactive fallback applies the same force-recovery when the normal startup times out. If all recovery fails, the container exits with a FATAL message instead of looping forever.
Missing redo log (ib_logfile0): If the prior container crashed during initialization or was otherwise broken, the persistent volume may have ibdata1 (InnoDB system tablespace) but no ib_logfile0 (redo log). MariaDB refuses to start without this file, and no innodb_force_recovery level bypasses this. Starting with v0.1.8-alpha, startup.sh detects this condition and performs automatic recovery: moves broken data to /tmp/mysql-broken-<timestamp>/, reinitializes MariaDB with mysql_install_db, recreates the database and tables from the config volume, enables TDE encryption, and auto-restores from the latest backup. Wallet identity is preserved because userconfig.json (keys, .onion address) on the config volume is never modified. The recovery is crash-safe — if the process dies mid-recovery, the next boot retriggers the same flow. The error log shows InnoDB: File ./ib_logfile0 was not found followed by Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
TDE encryption config lost after container rebuild: The encryption.cnf file lives in the container filesystem (/etc/mysql/conf.d/), not on a volume. When the container is recreated (docker compose up -d --build), this file is lost, but the mysql-data volume still has TDE-encrypted redo logs and tablespace files. MariaDB fails with: Obtaining redo log encryption key version 1 failed. Starting with v0.1.8-alpha, the pre-MariaDB TDE key setup detects this condition: if the master key is available and a database exists on the volume, it recreates encryption.cnf and the TDE key file automatically. No manual action needed.
Missing TDE key file: If the TDE encryption plugin was enabled on a previous boot but the TDE key file is missing, MariaDB cannot read its encrypted tables. Check the logs for WARNING: Failed to prepare TDE key file. This can happen if the master key is unavailable (e.g., volume passphrase not provided). Ensure the {node}-config volume has .master.key (or .master.key.enc with the correct EIOU_VOLUME_KEY_FILE).
Volume passphrase lost
If you enabled volume passphrase encryption (EIOU_VOLUME_KEY_FILE) and lost the passphrase, the encrypted master key cannot be decrypted. Recovery requires restoring from your 24-word seed phrase, which re-derives the master key from scratch. Old encrypted backups remain decryptable after seed restore.
Before starting seed recovery, either mount a readable replacement at the
configured EIOU_VOLUME_KEY_FILE path or remove that setting to explicitly
return to plaintext master-key storage. Recovery fails closed when the
configured file is missing so the node cannot complete once and then become
unbootable again at the next container restart.
Recovery logs a RECOVERY POINT line before the first database mutation. It
contains the authenticated backup filename, backup id, artifact modification
time, and selected key path. Treat that timestamp as an operator-visible
estimate: transactions committed after the snapshot are not present in the
recovered database.
The recovery journal uses same-filesystem temporary files, atomic renames, and
directory synchronization. Keep the MySQL and backup-locks paths on durable
Docker volumes backed by a filesystem that honors fsync and atomic rename.
Network filesystems, copy-on-write volume exporters, Docker Desktop/WSL2, and
storage restored from snapshots should be validated with the recovery
crash-test suite before being relied upon for disaster recovery.
Persistent “Chain Gap” on a contact after upgrading from a pre-fix version
If a transaction you sent to a contact was cancelled while still pending (before
being signed and delivered) on a version before the cancelled/rejected
chain-link fix, any transaction you sent to that contact afterwards was signed
with its previous_txid pointing at that unsigned cancelled row. The peer
never received the cancelled row (sync excludes cancelled/rejected) so they
can’t verify the successor, and the contact’s chain status gets stuck as
Action Required / Chain Gap.
New transactions created after the upgrade will chain correctly — the
fixed getPreviousTxid skips cancelled/rejected rows when picking the
predecessor. Pre-upgrade broken chains are not automatically migrated because
re-signing a peer-received transaction requires the sender’s private key;
the receiver can’t fix it locally. Resolve a pre-existing broken chain by
accepting the tx-drop proposal the sender will auto-propose on the next
sync, or manually from the contact’s detail panel. This is exactly what the
tx-drop flow was originally designed for.
Permissions errors in logs
The source sync may have set incorrect permissions. This is usually handled automatically, but if errors persist:
docker exec <container-name> bash -c "
chown -R www-data:www-data /etc/eiou/config/
chmod 600 /etc/eiou/config/.master.key
chmod 600 /etc/eiou/config/userconfig.json
chmod 600 /etc/eiou/config/dbconfig.json
"
Important Notes
Do NOT use docker-compose down -v
The -v flag removes named volumes, which permanently deletes all wallet data, transaction history, and backups. Only use -v if you intentionally want to start fresh.
| Command | Volumes | Safe for upgrade? |
|---|---|---|
docker-compose down |
Preserved | Yes |
docker-compose up -d --build |
Preserved | Yes |
docker-compose down -v |
Deleted | No – data loss |
Back Up Your Seed Phrase
Your 24-word BIP39 mnemonic is displayed only once during initial wallet generation. Store it securely offline. It is your last-resort recovery method if all volumes are lost.
Back Up Before Every Upgrade
Always run eiou backup create before upgrading. The encrypted backup file can be used to restore your database if anything goes wrong. Backup files are stored on the {node}-backups volume, which is preserved across upgrades.
Schema Migrations by Version
Database migrations run automatically on startup (step 8 above) and are idempotent — safe to run multiple times. No manual SQL is ever needed.
| Schema version | Change | Released |
|---|---|---|
| v26 | Added payback_method_answer_log on fresh installs and self-healed it on upgraded nodes, so responder-side payback-method request limits use the same schema on every installation |
v0.1.17-alpha (2026-07-27) |
| v10 | Added transactions_archive table — cold storage for completed transactions older than transactionsArchiveRetentionDays, schema mirrors transactions + an archived_at timestamp. Added transaction_chain_checkpoints table — records gap-free-at-archival proof per bilateral pair (archived_count, archived_txid_hash [SHA-256 over sorted archived txids], highest_archived_timestamp, highest_archived_time, last_verified_gap_free_at). Populated by the nightly archival cron (transaction-archive-cron.php at 01:30 UTC). The checkpoint is consumed by verifyChainIntegrity() on every outbound send so the send hot-path stays O(recent tail); eiou verify-chain does the full O(all history) walk on demand |
v0.1.13-alpha (2026-04-21) |
| v9 | Added payment_requests_archive table — cold storage for resolved (non-pending) payment requests older than paymentRequestsArchiveRetentionDays. Schema mirrors payment_requests + an archived_at timestamp. Populated by the nightly archival cron (payment-request-archive-cron.php at 01:00 UTC). Read paths UNION across live + archive, so archived rows stay queryable via GUI/CLI |
v0.1.13-alpha (2026-04-21) |
| v5 | Added payment_requests table — stores both outgoing requests you sent and incoming requests from contacts, with direction, status, amount, currency, description, requester address, timestamps, and resulting txid on approval |
v0.1.10-alpha (2026-04-08) |
| v4 and earlier | Prior tables (transactions, contacts, balances, P2P, DLQ, etc.) | — |
If you are upgrading from any version with schema ≤ v4, the payment_requests table is created automatically on first boot. No data is lost. Upgrading from schema ≤ v8 adds the new payment_requests_archive table (empty on first boot) — the archival cron starts moving rows once any resolved request is older than the configured retention window. Upgrading from schema ≤ v9 additionally adds transactions_archive and transaction_chain_checkpoints (both empty on first boot) — the transaction archival cron starts moving completed rows once any bilateral pair has rows older than the retention window AND the pair verifies gap-free at that moment.
Version Compatibility
Starting with v0.1.5-alpha, nodes enforce version compatibility. Nodes running versions below 0.1.3-alpha are rejected because earlier versions use an incompatible amount format (cents-based integers vs SplitAmount) that causes data corruption.
What happens if you don’t upgrade:
- Other nodes running v0.1.5+ will reject your contact requests, transactions, and sync messages
- You will see rejection responses with a message indicating the minimum required version
- Existing contacts running newer versions will be unable to send to you
What happens when you upgrade:
- Your node automatically includes its version in all outgoing messages and contact responses
- Contacts learn your new version via the next message, ping, or contact handshake
- Any previously blocked communication auto-heals — no manual action needed
Checking a contact’s version:
- Each contact’s
remote_versionis stored in the database and updated automatically - The version is exchanged during: contact acceptance (mutual acceptance response), ping/pong, and any incoming message envelope
- Version is NOT exposed before trust is established — the initial contact request and “received” response intentionally omit the version to prevent untrusted nodes from fingerprinting your software version. Version is only shared after contact acceptance, through message envelopes (outside the signed content), ping/pong responses, and mutual acceptance payloads
Master Key Is Derived from Seed
The AES-256 master encryption key (/etc/eiou/config/.master.key) is deterministically derived from the BIP39 seed phrase. All other encryption keys (MariaDB TDE, database credential encryption, backup encryption) are derived from this single master key. If the {node}-config volume is lost:
- Wallet keys, Tor address, auth code, and master key are all recoverable from the seed phrase
- Encrypted backups remain decryptable after a seed restore (the same master key is re-derived)
- MariaDB TDE and credential encryption are re-established automatically on next boot
The seed phrase is the single recovery secret for the entire node.