-
Discord Authenticator 0.1.4
StableBuild and release image / build (push) Successful in 1m9sreleased this
2026-09-20 04:08:25 -04:00 | 0 commits to main since this releaseAn 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.jsonnorvault.json.bakcould 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.bakat 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 createdthe backup at … exists but could not be parsed eitherthe 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=1and the service boots with an empty vault instead. It
moves the unreadable file tovault.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, KeyDA_VAULT_RESET, Value1.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.4Digest:
sha256:7a4d0518dadd3402d4cad57ab9c15d2203741cae6e90c16dfa8c7b9e2f4adb6cgitea.milesward.dev/mward4/discord-authenticator:0.1.4Downloads
-
Discord Authenticator 0.1.3
StableBuild and release image / build (push) Successful in 11sreleased this
2026-09-20 03:38:33 -04:00 | 1 commits to main since this releaseThis 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_atomicfsyncs 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.jsonnow falls back to that backup, logs oneERRORexplaining what
happened, moves the damaged file tovault.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.3then Force Update on the container. Pin the image to
0.1.3rather than
:latestin 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 alongsidevault.json) andpush-state.json(safe to
delete at any time; the tiles are simply reposted).If you already hit this
If your
vault.jsonis currently zero bytes, check for a.bak:cd /mnt/user/appdata/discord-authenticator ls -la vault* && head -c 100 vault.json.bakv0.1.3recovers 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 ownotpauth://links or a Google Authenticator
export. Keepvault.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 leavesvault.jsonbyte-identical.
Image:
gitea.milesward.dev/mward4/discord-authenticator:0.1.3Digest:
sha256:472c3dd1d8e850d7a1c0f584c8bd661b5feff88bd8307ef41371a04a1db2639bgitea.milesward.dev/mward4/discord-authenticator:0.1.3Downloads