02 January 2026

Part 3: A “No-Login” VT Console for Direwolf (and other dashboards)

This is Part 3 of a three-part series:

  • Part 1: Stable udev device paths for RTL-SDR (/dev/sdr-<serial>)
  • Part 2: Run Direwolf RX iGate in Docker (least privilege, single-device passthrough)
  • Part 3 (this post): Show logs/status on a dedicated Linux virtual terminal (VT) without logging in

The goal: when you walk up to the box (or attach a monitor/keyboard), you can hit Ctrl+Alt+F3 and see a live status screen—without anyone having to log in.


Why this method (and why not watch | tail)

A common quick-and-dirty approach is something like:

watch -n 0.5 "docker logs ... | tail -n 60" > /dev/tty3

It works, but it burns CPU (polling every 0.5s, re-running docker logs, piping, tailing, redrawing). We can do better.

The efficient approach is:

  • Use docker logs --follow (one process, blocks until new lines arrive)
  • Use --tail to show recent history once at startup
  • Let systemd attach stdout directly to the VT (no shell redirection required)

What we’re building

We’ll separate the “producer” from the “display,” so you can reuse this pattern for other boards later (weather, NOTAM text, system health, etc.).

  • Producer script: outputs plain text to stdout (example: Direwolf Docker logs)
  • systemd service: sends stdout to a specific VT (/dev/tty3) and restarts on failure
  • Disable getty on that VT so there’s no login prompt and nothing overwrites the screen

Step 1 — Choose the VT and disable getty on it

Pick a VT that you want to “own.” In this example we’ll use tty3.

Check if a getty is running on tty3

systemctl status getty@tty3.service

If it’s active, disable it so it doesn’t draw a login prompt over your dashboard:

sudo systemctl disable --now getty@tty3.service

Optional (stronger): mask it to prevent accidental re-enable by other tooling:

sudo systemctl mask getty@tty3.service

To restore later:

sudo systemctl unmask getty@tty3.service
sudo systemctl enable --now getty@tty3.service

Step 2 — Create the producer script (Direwolf Docker logs)

Create a small script that prints what you want on the VT. Keep it boring: one job, clean stdout. This example streams the last 60 lines and then follows.

Create: /usr/local/bin/board-direwolf-logs

sudo tee /usr/local/bin/board-direwolf-logs >/dev/null <<'EOF'
#!/bin/sh
set -eu

CONTAINER_NAME="${CONTAINER_NAME:-direwolf}"
TAIL_LINES="${TAIL_LINES:-60}"

# One process; blocks until new log lines arrive
exec /usr/bin/docker logs --follow --tail="${TAIL_LINES}" --timestamps "${CONTAINER_NAME}"
EOF

sudo chmod 0755 /usr/local/bin/board-direwolf-logs

You can test it directly (this should stream in your current terminal):

/usr/local/bin/board-direwolf-logs

Stop it with Ctrl+C.


Step 3 — Create the systemd service that writes to the VT

Now we create a template unit so you can choose the VT at enable-time: direwolf-vt@tty3.service, direwolf-vt@tty4.service, etc.

Create: /etc/systemd/system/direwolf-vt@.service

sudo tee /etc/systemd/system/direwolf-vt@.service >/dev/null <<'EOF'
[Unit]
Description=Direwolf board on %I (no-login VT)
After=docker.service
Requires=docker.service

[Service]
Type=simple

# Producer script (stdout only)
Environment=CONTAINER_NAME=direwolf
Environment=TAIL_LINES=60
ExecStart=/usr/local/bin/board-direwolf-logs

# Send stdout to the VT directly
StandardOutput=tty
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes

# Keep it lightweight
Nice=10
CPUSchedulingPolicy=batch
IOSchedulingClass=idle

# If docker restarts or the container rotates, keep coming back
Restart=always
RestartSec=1

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload

Step 4 — Enable the service on tty3

sudo systemctl enable --now direwolf-vt@tty3.service

Then switch to the console:

  • Keyboard: Ctrl+Alt+F3
  • Or command: sudo chvt 3

You should see:

  • ~60 lines of recent logs
  • Then live updates as new logs arrive

Optional: keep Docker logs from growing forever

If you haven’t already, set Docker log rotation for the container (Compose example):

logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

This keeps the log files bounded while your VT board follows “current” output.


Troubleshooting

Nothing appears on tty3

  • Confirm you’re actually on VT3 (Ctrl+Alt+F3).
  • Verify the service is running: systemctl status direwolf-vt@tty3.service
  • Verify the producer works: /usr/local/bin/board-direwolf-logs

Login prompt keeps overwriting the board

  • Disable and optionally mask getty@tty3.service (Step 1).

Permission issues talking to Docker

  • This service runs as root by default so it can access the Docker socket and the VT cleanly.
  • If you run it as a non-root user, you’ll need access to /var/run/docker.sock and the VT device. (Also note: membership in the docker group is effectively root-equivalent.)

Reusing the pattern for other “boards”

To display something else (weather text, a local status script, etc.), you only swap the producer script. Keep the same service, or clone it into another unit.

Example: create another script /usr/local/bin/board-weather that prints a text dashboard once, then sleeps/loops as you prefer. Enable it on another VT (say tty4) with a similar service.


Wrap-up

You now have an appliance-style console:

  • No polling loops (watch)
  • No wasteful pipes (tail)
  • One blocking stream (docker logs --follow)
  • Direct VT output via systemd (StandardOutput=tty)
  • No-login display (getty disabled on that VT)

In the next iteration, you can replace “logs” with a real curated dashboard (still plain text) while keeping the same display mechanism.

No comments:

Post a Comment