Skip to content

Repository files navigation

totpd

totpd screenshot

totpd is a small Go web daemon that serves current TOTP codes from key files on disk.

Live example: https://anarjafarov.me/totp/example

The daemon scans a keys/ directory at startup, loads each token, and exposes:

  • GET /totp/<name>: HTML page with the current code and countdown.
  • GET /totp/<name>/status: JSON status for the current code.

totpd does not include authentication. Anyone who can reach a token URL can read that token's current one-time password, so run it behind your own access control, TLS, VPN, allowlist, or trusted reverse proxy.

Requirements

  • Go 1.25.5 or compatible, matching go.mod.
  • Key material in keys/.

Quick Start

Build the daemon:

make

Run it locally:

make run

Open the bundled example token:

http://127.0.0.1:3080/totp/example

The default listen address is 127.0.0.1:3080. Override it with TOTPD_ADDR:

make run TOTPD_ADDR=127.0.0.1:4000

Key Files

Keys live in keys/. Each token name comes from the filename, and names may contain letters, numbers, -, _, and ..

Supported file forms:

  • keys/<name>.url.txt: a text file containing an otpauth://totp/... URL, an otpauth-migration://... URL, or a raw Base32 secret.
  • keys/<name>.qr.png
  • keys/<name>.qr.jpg
  • keys/<name>.qr.jpeg

Examples included in this repository:

keys/example.url.txt
keys/example.qr.png

For a raw Base32 secret, totpd uses the filename as the label and issuer, with SHA1, 6 digits, and a 30 second period.

Supported TOTP settings:

  • Algorithms: SHA1, SHA256, SHA512
  • Digits: 6, 7, or 8
  • Periods: 30 or 60 seconds

Key Sync

On startup, totpd reconciles URL and QR files for each token name:

  • If only a QR file exists, it decodes the QR payload and writes keys/<name>.url.txt.
  • If both URL and QR files exist and they match, the URL file is used.
  • If both exist and differ, the newer source wins.
  • Older conflicting files are moved into keys/deleted/YYYYMMDD/.

Run reconciliation without starting the server:

make sync

Equivalent direct command:

TOTPD_DIR=. go run ./cmd -sync-only

Runtime Configuration

totpd resolves its base directory in this order:

  1. TOTPD_DIR, when set.
  2. The directory containing the totpd binary, when running an installed binary named totpd.
  3. The current working directory.

Runtime environment variables:

Variable Default Description
TOTPD_ADDR 127.0.0.1:3080 HTTP listen address.
TOTPD_DIR auto-detected Directory containing the keys/ folder.

Project Layout

  • cmd/main.go: CLI entry point.
  • internal/app/: application runtime, HTTP routes, and HTML template.
  • internal/config/: flags, environment variables, and base directory resolution.
  • internal/keys/: key directory scanning, QR decoding, and key file reconciliation.
  • internal/otp/: OTP parsing, migration payload handling, and TOTP generation.
  • test/: Docker-run spec tests for externally visible behavior.

API

GET /totp/<name>/status returns JSON:

{
  "name": "example",
  "code": "123456",
  "valid": true,
  "remainingSeconds": 17,
  "periodSeconds": 30,
  "progress": 0.43333333333333335,
  "expiresAt": "2026-08-07T12:00:30Z",
  "label": "TOTPApp:example@totpd",
  "issuer": "TOTPApp",
  "algorithm": "SHA1"
}

Responses use Cache-Control: no-store.

Install

Install the binary and keys into /etc/totpd:

make install

Install and enable the systemd unit:

make service

make install and make service write to /etc paths by default, so run them with sudo when your shell user cannot write there.

The included totpd.service runs:

WorkingDirectory=/etc/totpd
Environment=TOTPD_ADDR=127.0.0.1:3080
ExecStart=/etc/totpd/totpd

Start or restart the service after installing:

sudo systemctl restart totpd.service

Testing

Run the test suite inside Docker:

make test

The test target uses the golang:1.25.5 image by default and runs go test ./....

Nginx

example.nginx.conf proxies /totp/ to the local daemon and denies /keys/:

location /totp/ {
    proxy_pass http://127.0.0.1:3080;
}

location /keys/ {
    return 404;
}

Add your own authentication, IP restrictions, or private network boundary in front of /totp/ before exposing real tokens.

Maintenance

Remove build artifacts and local Go caches:

make clean

About

TOTP daemon to serve short living temporary tokens for 2FA access (i.e. Google Authenticator)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages