• v0.1.4 bd7f09095f

    Discord Authenticator 0.1.4
    Build and release image / build (push) Successful in 1m9s
    Stable

    mward4 released this 2026-09-20 04:08:25 -04:00 | 0 commits to main since this release

    An accurate message, and a way out

    No functional change to how the vault is written or recovered. This fixes what
    the service tells you when recovery is not possible, and adds an escape hatch
    for the case it is not.

    The message said the wrong thing

    When neither vault.json nor vault.json.bak could be loaded, the error read:

    ...and the backup at /var/lib/discord-authenticator/vault.json.bak could not
    be used either.

    That is true for three quite different situations, and it reads as though a
    backup exists. The case that actually bites is the one where no backup was
    ever created
    : a vault that has not completed a single write since v0.1.3
    introduced backups has no .bak at all, so there is nothing on disk to recover
    and no file to go looking for. The old wording sent you looking anyway.

    It now says which situation you are in:

    • there is no backup at … — this vault has not been written since backups were introduced, so none was ever created
    • the backup at … exists but could not be parsed either
    • the backup at … is empty as well, so there is nothing to recover

    It also lists any vault.json.corrupt-<timestamp> files left by earlier
    recoveries, since those are the only other thing worth inspecting.

    DA_VAULT_RESET

    Refusing to start on an unreadable vault is still the default — booting empty
    would look identical to a vault you had emptied on purpose, and the whole point
    is that the service cannot tell the difference. But refusing must not be a dead
    end, and having to edit files by hand inside a container that will not start is
    a poor answer at 2am.

    Set DA_VAULT_RESET=1 and the service boots with an empty vault instead. It
    moves the unreadable file to vault.json.corrupt-<timestamp> before writing
    the fresh one, so nothing is overwritten, and the log line names where it went.
    Remove the variable afterwards: it has no effect on a vault that loads, but
    leaving it set means the next genuinely-broken vault boots empty rather than
    telling you about it.

    In the Unraid template: Docker → the container → Add another Path, Variable,
    Port or Label
    → Variable, Key DA_VAULT_RESET, Value 1.

    Upgrading

    Nothing to do. Vault format unchanged, no accounts to re-import. If you are
    currently stuck on the old message, this release does not recover the accounts
    either — it just tells you why, and lets you start clean without leaving the UI.


    Image: gitea.milesward.dev/mward4/discord-authenticator:0.1.4

    Digest: sha256:7a4d0518dadd3402d4cad57ab9c15d2203741cae6e90c16dfa8c7b9e2f4adb6c

    gitea.milesward.dev/mward4/discord-authenticator:0.1.4
    
    Downloads
  • v0.1.3 72b9cbcab8

    Discord Authenticator 0.1.3
    Build and release image / build (push) Successful in 11s
    Stable

    mward4 released this 2026-09-20 03:38:33 -04:00 | 1 commits to main since this release

    This release fixes the failure that could leave the container in a restart loop
    with:

    Error: could not load the vault
    
    Caused by:
        /var/lib/discord-authenticator/vault.json exists but could not be parsed
        (EOF while parsing a value at line 1 column 0). Refusing to start with an
        empty vault — move the file aside to start fresh.
    

    What was wrong

    The vault was written as fs::write(tmp) → rename(tmp, path). The rename is
    atomic, so a reader never saw a half-written file — but atomicity is not
    durability
    . Nothing fsynced the new bytes before the rename, so on ext4
    (data=ordered, the default) a crash or hard power-off could commit the rename
    while the data it published never landed. The result is a file that exists and
    is zero bytes long, which is exactly what the message above describes.

    Two things made it far more likely. The write happened on every push tick,
    so a ten-account vault was rewriting all of its secrets thousands of times a
    day, and there was no backup to fall back on — one damaged file meant a crash
    loop, with the web UI that could have fixed it locked behind the service that
    would not start.

    What changed

    Durable writes. store_io::write_atomic fsyncs the new bytes, renames, then
    fsyncs the directory. The rename now cannot reach the journal ahead of the data
    it publishes.

    A backup, and recovery from it. Every write refreshes vault.json.bak. The
    backup is taken after the new file is safely in place, so the two always hold
    the same accounts — backing up first would leave the backup a full change
    behind and quietly lose your most recent edit. At startup, an unparseable
    vault.json now falls back to that backup, logs one ERROR explaining what
    happened, moves the damaged file to vault.json.corrupt-<timestamp> instead of
    deleting it, and writes the recovered accounts back so the next boot is
    ordinary. With no usable backup it still refuses to start, exactly as before.

    The secrets are no longer rewritten on every code rotation. Discord
    bookkeeping — message ids, last-sent codes, last errors — moved to its own
    push-state.json, batched and flushed at most every five seconds, with a final
    flush on clean shutdown. It is a cache by construction: losing or corrupting it
    costs one reposted Discord tile, never a TOTP secret, so it is never a reason to
    refuse to boot.

    Upgrading from v0.1.2

    Nothing to do. The vault format is unchanged and no accounts need re-importing.

    On Unraid, either wait for the Docker page to notice :latest, or force it:

    docker pull gitea.milesward.dev/mward4/discord-authenticator:0.1.3
    

    then Force Update on the container. Pin the image to 0.1.3 rather than
    :latest in the Unraid template if you would rather updates be deliberate.

    Your data directory gains two files: vault.json.bak (a copy of the vault —
    include it in backups alongside vault.json) and push-state.json (safe to
    delete at any time; the tiles are simply reposted).

    If you already hit this

    If your vault.json is currently zero bytes, check for a .bak:

    cd /mnt/user/appdata/discord-authenticator
    ls -la vault* && head -c 100 vault.json.bak
    

    v0.1.3 recovers automatically when that backup parses — just update the image
    and start it. If there is no usable backup, the bytes are gone and the accounts
    must be re-added from your own otpauth:// links or a Google Authenticator
    export. Keep vault.key: without it, nothing in an old vault file can be
    decrypted even if the bytes turn up later.

    Verifying the fix

    143 tests cover this, including recovery from a zero-length vault, retention of
    the damaged file, write-back on recovery, refusal when both files are bad,
    reload of tile ids across a restart, batching, and an assertion that a code
    rotation leaves vault.json byte-identical.


    Image: gitea.milesward.dev/mward4/discord-authenticator:0.1.3

    Digest: sha256:472c3dd1d8e850d7a1c0f584c8bd661b5feff88bd8307ef41371a04a1db2639b

    gitea.milesward.dev/mward4/discord-authenticator:0.1.3
    
    Downloads