CLI tool
tvrzna/emptty avatar
tvrzna/emptty

emptty: a display manager that lives entirely on the TTY

Dead simple CLI Display Manager on TTY

969 stars33 forksGoMIT

At a glance

What is it?
emptty is a Go program that takes over a virtual terminal, lets you pick a desktop session, and starts it, replacing the graphical login screen with a text menu. Its configuration is a single flat file of keys and values, and the same binary serves both a one-off session and a long-running daemon.
Who is it for?
emptty fits a machine with no graphical login screen, or an operator who deliberately wants the login step to stay on the text console, and it is cheap enough to try on a spare box since the whole program is a single Go binary with one PAM dependency. It does not fit a desktop that needs the convenience of a display manager with sessions, greeters and accessibility features, because the menu is a text list.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 22 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What emptty replaces and what it leaves alone

A display manager is the program that owns the login screen and hands control to a desktop session. On a typical Linux install that means a graphical greeter, several hundred megabytes of user space and a dependency chain into the session itself.

emptty takes the same job and does it in text mode. It runs on a TTY, draws the list of available sessions, lets you pick one, and starts it. The repository describes it in three words and the description is accurate: dead simple CLI display manager on TTY.

Two things follow from that. First, the session list itself is not part of emptty. The configuration names two directories where desktop session files live, XORG_SESSIONS_PATH defaulting to /usr/share/xsessions/ and WAYLAND_SESSIONS_PATH defaulting to /usr/share/wayland-sessions/, so emptty reads what the rest of the system already installs. Second, the program is a single Go binary whose module requires only github.com/msteinert/pam/v2 v2.0.0, the PAM binding used for authentication.

The topics are limited to cli and display-manager, and the licence is MIT. The last push was on 2026-10-02, with releases v0.16.1 on 2026-03-30 and v0.16.0 on 2026-03-26.

One config file of flat keys, read at startup

The configuration lives in /etc/emptty/conf, and the documentation states plainly that on each change it requires a restart of emptty. There is no reload signal and no second file to reconcile.

Every entry is a single key with a description, and the interesting ones are about session selection. TTY_NUMBER picks the TTY where emptty starts. SWITCH_TTY enables switching to a defined TTY number and defaults to true. DEFAULT_ENV defines the environment used for undefined sessions, with possible values xorg and wayland, and the documentation notes the default of default is xorg. DEFAULT_USER is a preselected user, and DEFAULT_SESSION a preselected desktop session for a user who does not use an emptty file, with lower priority than AUTOLOGIN_SESSION.

The behaviour flags are the same shape. VERTICAL_SELECTION prints each window manager or desktop entry on its own line instead of all on one, and INDENT_SELECTION sets the horizontal offset for that vertical list, described as useful for centering the menu on screen. HIDE_ENTER_LOGIN and HIDE_ENTER_PASSWORD suppress the hostname login and Password prompts. IDENTIFY_ENVS prints environmental groups so Xorg, Wayland, custom and user custom desktops can be told apart in the list.

One config path does not exist at all by default, and that is deliberate. The documentation states that emptty does not source any kind of .profile scripts by default, and points to a samples file for the cases where you do want them.

Autologin needs nopasswdlogin and a retry limit

AUTOLOGIN is the option that turns emptty into a kiosk or a single seat machine. It is disabled by default and, according to the documentation, only takes effect when DEFAULT_USER is defined. The user named there is the one logged in.

There is a hard condition attached. The documentation states that to enable autologin, DEFAULT_USER must be in the nopasswdlogin group, otherwise the user will NOT be authorized. That is the PAM layer doing its job, and it means the machine administrator, not emptty, decides whether an account can be logged in without a password.

The session that starts is AUTOLOGIN_SESSION, with AUTOLOGIN_SESSION_ENV optionally setting its environment to xorg or wayland. If the session is not found in the list of sessions, emptty proceeds to manual selection rather than failing, which is a sensible fallback.

The failure mode that a kiosk operator will care about is handled explicitly. AUTOLOGIN_MAX_RETRY caps how many times a session that fails to start is retried in a short period, so the machine eventually stops restarting itself, with -1 meaning infinite retries and 0 no retry. The default is 2, and AUTOLOGIN_RETRY_PERIOD sets the checking period in seconds with a default of 2 as well. Without that cap, a broken session in an autologin setup produces an infinite restart loop.

Daemon mode brings colours, numlock and logging

emptty runs in two modes, and several configuration keys only apply in the daemon mode. The daemon is the long-running instance that owns the TTY permanently, as opposed to a one-off invocation that starts a session and exits.

In daemon mode, FG_COLOR and BG_COLOR set the foreground and background colours of the login screen. ENABLE_NUMLOCK turns on the numeric keypad lock, and is false by default. PRINT_ISSUE enables printing of /etc/issue in daemon mode, while PRINT_MOTD controls the message of the day, which can be the default, a static file or a dynamic one.

Logging is configurable in a way that suits a machine with limited disk. LOGGING accepts rotate, appending or disabled, and the default is rotate. LOGGING_FILE overrides the path, and the default is /var/log/emptty/[TTY_NUMBER].log, with a note that the directories have to exist beforehand or the file cannot be created. SESSION_ERROR_LOGGING has the same three values for session errors, defaulting to disabled, and its file defaults to /var/log/emptty/session-errors.[TTY_NUMBER].log with the same directory caveat.

The dynamic MOTD path is configurable too, with DYNAMIC_MOTD enabling it and DYNAMIC_MOTD_PATH pointing at /etc/emptty/motd-gen.sh by default. The documentation warns that the script needs correct content and permissions, 744 being the example, and that it runs as the default user, which in daemon mode means root.

Building and installing is a plain make workflow

The repository ships a Makefile and the build is conventional Go. The test target runs the suite with coverage:

bash
@go test -coverprofile cover.out ${TAGS_ARGS} ./...

The build target produces a binary called emptty in a dist directory and stamps the version into it:

bash
@go build ${TAGS_ARGS} -o dist/${DISTFILE} -ldflags "-X github.com/tvrzna/emptty/src.buildVersion=${BUILD_VERSION}" -buildvcs=false

The version string comes from git describe --tags, and the manual page is compressed in the same target. Installation is split into separate targets, which is the part worth noting because each piece lands in a different system location:

bash
@install -DZs dist/${DISTFILE} -m 755 -t ${DESTDIR}/usr/bin
@install -DZ res/conf -m 644 -T ${DESTDIR}/etc/${DISTFILE}/conf
@install -DZ res/pam -m 644 -T ${DESTDIR}/etc/pam.d/${DISTFILE}

The binary goes to /usr/bin, the configuration to /etc/emptty/conf, and a PAM file to /etc/pam.d/emptty. There are also separate targets for an alternative PAM file and for the pam-fedora and pam-suse variants, plus a runit service file installed into /etc/sv/emptty/run, and a motd-gen.sh target that installs with mode 744 into /etc/emptty/. The DESTDIR variable is honoured throughout, so packaging for another root is straightforward.

Note that installing the PAM file is not optional in the same way as installing the motd script. A display manager that authenticates users needs a PAM service entry.

Session plumbing: Xorg, Wayland, dbus and xinitrc

The details that decide whether a session actually starts are grouped under a few keys, and they interact.

DBUS_LAUNCH starts dbus-launch before the desktop command and, when the session ends, dbus-daemon is interrupted. The default is true. ALWAYS_DBUS_LAUNCH forces the same behaviour in any case, ignoring DBUS_LAUNCH and starting even when XINITRC_LAUNCH is set to true; its default is false. XINITRC_LAUNCH starts the Xorg desktop by calling the user's .xinitrc script with the session exec as an argument, and it overrides DBUS_LAUNCH when it is true, the file exists and the selected session is Xorg.

Both of those are overridden to false when the user configuration is handled as a script, meaning a configuration without an Exec option.

The X server itself is configurable. XORG_CMD overrides the path to the X server executable, which allows a different X server implementation, and XORG_ARGS passes arguments to it. There is also ROOTLESS_XORG, which starts Xorg as rootless when the system allows it and emptty runs in daemon mode, and which the documentation says requires additional changes in the Xorg configuration, linked from the same section.

Two more environment options sit at the edge. NO_XDG_FALLBACK disallows setting fallback values for the XDG environment variables and leaves that to the login controls, with a warning that setting it true could lead to unexpected behaviour. DEFAULT_XAUTHORITY, when true, skips the .emptty-xauth file and uses the standard ~/.Xauthority instead, which the documentation describes as a way to handle xauth issues.

Editorial conclusion

emptty fits a machine with no graphical login screen, or an operator who deliberately wants the login step to stay on the text console, and it is cheap enough to try on a spare box since the whole program is a single Go binary with one PAM dependency. It does not fit a desktop that needs the convenience of a display manager with sessions, greeters and accessibility features, because the menu is a text list. Before adopting it, read the PAM section of the documentation, because the autologin path only works when the target account is in the nopasswdlogin group and the session scripts run as root in daemon mode.

Frequently asked questions

How do I install emptty?

The project ships a Makefile with separate install targets. The build target compiles the binary with go build into a dist directory, and the install targets place the binary in /usr/bin, the configuration in /etc/emptty/conf and a PAM file in /etc/pam.d/emptty, all honouring DESTDIR.

Where does emptty store its configuration?

In /etc/emptty/conf, as a flat list of keys. The documentation states that emptty does not source any kind of .profile scripts by default, and that each change to the config file requires a restart of emptty.

What does emptty need for autologin to work?

AUTOLOGIN must be enabled and DEFAULT_USER must be defined, and that user has to be in the nopasswdlogin group, otherwise the user will not be authorized. AUTOLOGIN_MAX_RETRY caps how often a failing session is retried, with a default of 2.

How does emptty find desktop sessions?

It reads desktop entry files from two configured directories: XORG_SESSIONS_PATH, defaulting to /usr/share/xsessions/, and WAYLAND_SESSIONS_PATH, defaulting to /usr/share/wayland-sessions/.

Can emptty run a Wayland session?

Yes. DEFAULT_ENV, DEFAULT_SESSION_ENV and AUTOLOGIN_SESSION_ENV all accept xorg or wayland as values, and the wayland session files are read from the directory configured in WAYLAND_SESSIONS_PATH.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. tvrzna/emptty on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tvrzna-emptty.svg)](https://hysenlabs.com/projects/tvrzna-emptty)

Was this useful?