Help us shape the future of Go! Take the 2026 Go Developer Survey

tero

module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: Apache-2.0

README ΒΆ

Tero

Tero is like Vercel, but on your own VPS. Push to GitHub and your app is live with HTTPS on a server you own, and the server comes locked down from the first command.

Status: early development. Tero is not ready for use yet. The install script, the server setup (sudo tero init) and the dashboard login work; deploys are still being built.

What makes Tero cool

Deploys (planned)

  • πŸͺ„ No Dockerfile needed. Tero detects your language and framework and builds the app for you, with Railpack. Already have a Dockerfile? Tero uses it.
  • πŸš€ Deploy on push. Connect a GitHub repo and every push to your branch builds and goes live.
  • 🌐 Automatic HTTPS. Every app gets a free nip.io address, or use your own domain. Certificates come from Let's Encrypt.
  • πŸ”‘ Environment variables and secrets, set from the dashboard.
  • πŸ“œ Live logs for builds and running apps.
  • βͺ One-click rollback to any earlier deploy.
  • βš™οΈ Web apps and background workers.
  • πŸ‘€ Preview deploys for pull requests (later).

Security

  • πŸ”’ One-command lockdown. sudo tero init secures the server to a documented subset of the CIS benchmark: SSH keys only, a default-deny firewall, automatic security updates, locked-down kernel settings, auditing and AppArmor. Root is used only for sudo tero commands. Tero itself runs as an unprivileged user, and apps run in rootless containers. Every control, and every one it skips, is listed in docs/security/cis-subset.md.
  • πŸ›Ÿ Won't lock you out. Init checks everything before it changes anything, and refuses to turn off SSH passwords until you have a key in place.
  • πŸ“ Checked, not claimed. Every requirement is a spec scenario with an end-to-end test on a fresh server, plus a minimum Lynis security score.
  • πŸ›‘οΈ A dashboard built to OWASP ASVS Level 2 (planned): long passwords checked against breach lists, TOTP, rate limiting and strict sessions.
  • ✍️ Verifiable releases: SLSA Build L3 provenance and an SBOM for every binary.

Simple

  • πŸ“¦ One binary. Tero is a single Go binary with the web server and HTTPS built in.
  • ⚑ One command to set up. sudo tero init turns a fresh server into a Tero host, with no config files to write.
  • 🧱 Nothing else to run. No nginx, no Kubernetes, and no container registry: images are built and kept on the server (planned).
  • 🏠 Your server, your bill. Runs on any VPS with a supported Ubuntu, from any provider.

Supported platforms

Ubuntu 24.04 LTS and Ubuntu 26.04 LTS on amd64 and arm64. The install script and tero init refuse to run anywhere else.

Install

On a fresh server, run:

curl -fsSL https://github.com/leanhanc/tero/releases/latest/download/install.sh | sh

The script downloads the latest release binary for the server's architecture, checks it against the release's checksums, and installs it as /usr/local/bin/tero. It changes nothing else. Then set up the server:

sudo tero init

Verify a release

Every release binary has a signed SLSA Build Level 3 provenance attestation, made by the release workflow through GitHub artifact attestations, and an SPDX SBOM attached to the release. To check a binary yourself, download it from the releases page and run, with the GitHub CLI signed in:

gh attestation verify tero-linux-amd64 --repo leanhanc/tero --signer-workflow leanhanc/tero/.github/workflows/build-release.yml

Verification succeeds only for the exact bytes the release workflow built, and fails for a binary that was changed afterwards. The install script checks checksums, not attestations, so it doesn't need the GitHub CLI on the server.

Development

You need Go and Bun (to build the dashboard) and, for the end-to-end tests, Lima 2.0 or later on an Apple silicon Mac.

make build    # Linux binaries for amd64 and arm64 in bin/
make test     # unit tests
make e2e      # end-to-end tests in fresh Ubuntu VMs

make e2e creates a fresh VM for each supported Ubuntu release, installs Tero with install.sh from a fake release served inside the VM, runs sudo tero init and checks every scenario. It takes a few minutes per release. Follow it with tail -f .tero-e2e.log. To test a single release, run scripts/vm-test.sh 24.04.

Releases are published by pushing a version tag (git tag v0.1.0 && git push origin v0.1.0): .github/workflows/release.yml builds them with GoReleaser. To build the release files locally without publishing, run goreleaser release --snapshot --clean.

Specs live in openspec/, one folder per capability. Specs for later features are in openspec/drafts/.

Directories ΒΆ

Path Synopsis
cmd
tero command
Command tero is the whole of Tero: `sudo tero init` sets up a server once, `tero serve` is the long-running service systemd starts as the tero user, and `tero keys` is the key service systemd starts as tero-keys.
Command tero is the whole of Tero: `sudo tero init` sets up a server once, `tero serve` is the long-running service systemd starts as the tero user, and `tero keys` is the key service systemd starts as tero-keys.
internal
agent
Package agent is `tero agent`, the process that builds and runs apps.
Package agent is `tero agent`, the process that builds and runs apps.
apps
Package apps holds the rules for apps the admin creates.
Package apps holds the rules for apps the admin creates.
auth
Package auth is the dashboard's side of the single-admin login and account settings.
Package auth is the dashboard's side of the single-admin login and account settings.
breach
Package breach is `sudo tero assume-breach`: the one command for a server that may be compromised.
Package breach is `sudo tero assume-breach`: the one command for a server that may be compromised.
build
Package build turns an app's source into an image in tero's Podman store: Railpack plans the build, a throwaway rootless BuildKit container runs it, and the result is loaded with `podman load`.
Package build turns an app's source into an image in tero's Podman store: Railpack plans the build, a throwaway rootless BuildKit container runs it, and the result is loaded with `podman load`.
config
Package config holds the settings `tero init` writes under /etc/tero.
Package config holds the settings `tero init` writes under /etc/tero.
containers
Package containers starts and checks app containers under tero's rootless Podman, with the ADR 0001 rules: no capabilities, no privilege gain, a user namespace slice of their own, and a network namespace of their own that can't reach other apps.
Package containers starts and checks app containers under tero's rootless Podman, with the ADR 0001 rules: no capabilities, no privilege gain, a user namespace slice of their own, and a network namespace of their own that can't reach other apps.
dashboard
Package dashboard serves the dashboard's JSON API under /api and the web app everywhere else.
Package dashboard serves the dashboard's JSON API under /api and the web app everywhere else.
deployer
Package deployer is the agent's pipeline for one deploy: fetch the branch's latest commit, build it, start it next to the live release (or in its place, when the app's setting says so), check it answers, and make it live.
Package deployer is the agent's pipeline for one deploy: fetch the branch's latest commit, build it, start it next to the live release (or in its place, when the app's setting says so), check it answers, and make it live.
deploylog
Package deploylog stores each deploy's build log as a file the agent writes and the service streams to the dashboard.
Package deploylog stores each deploy's build log as a file the agent writes and the service streams to the dashboard.
deploys
Package deploys is the deploy queue shared by the service and the agent through the SQLite store.
Package deploys is the deploy queue shared by the service and the agent through the SQLite store.
hostfs
Package hostfs reads and writes host files relative to a root directory: "/" on a real server, a temporary directory in unit tests.
Package hostfs reads and writes host files relative to a root directory: "/" on a real server, a temporary directory in unit tests.
initflow
Package initflow implements `sudo tero init`: it checks the host, then turns it into a hardened Tero host in one run.
Package initflow implements `sudo tero init`: it checks the host, then turns it into a hardened Tero host in one run.
journal
Package journal writes structured entries to the systemd journal through its native socket, so they carry fields that `journalctl` can filter on.
Package journal writes structured entries to the systemd journal through its native socket, so they carry fields that `journalctl` can filter on.
keyfiles
Package keyfiles manages the sealed master key files for root commands: sealing a new version, rotating to it, and setting aside files that no longer open.
Package keyfiles manages the sealed master key files for root commands: sealing a new version, rotating to it, and setting aside files that no longer open.
keys
Package keys is the key service, `tero keys`: the only process that holds Tero's master key.
Package keys is the key service, `tero keys`: the only process that holds Tero's master key.
loginlink
Package loginlink issues dashboard setup links for root CLI commands (`sudo tero init` and `sudo tero reset-login`).
Package loginlink issues dashboard setup links for root CLI commands (`sudo tero init` and `sudo tero reset-login`).
password
Package password holds the dashboard password rules (OWASP ASVS 5.0.0 V6.2) and stores passwords as Argon2id hashes (RFC 9106).
Package password holds the dashboard password rules (OWASP ASVS 5.0.0 V6.2) and stores passwords as Argon2id hashes (RFC 9106).
platform
Package platform identifies the host OS and CPU architecture and checks them against the platforms Tero is tested on.
Package platform identifies the host OS and CPU architecture and checks them against the platforms Tero is tested on.
podman
Package podman runs the podman CLI as the current user, which for Tero is always the agent running as tero.
Package podman runs the podman CLI as the current user, which for Tero is always the agent running as tero.
publicip
Package publicip finds the server's public IPv4 address, used to build the default <ip>.nip.io dashboard domain.
Package publicip finds the server's public IPv4 address, used to build the default <ip>.nip.io dashboard domain.
rootcheck
Package rootcheck guards the subcommands that must run as root.
Package rootcheck guards the subcommands that must run as root.
serve
Package serve runs the long-lived Tero service: an embedded Caddy that serves the dashboard over HTTPS on the domain chosen at init, getting and renewing its certificate automatically.
Package serve runs the long-lived Tero service: an embedded Caddy that serves the dashboard over HTTPS on the domain chosen at init, getting and renewing its certificate automatically.
source
Package source fetches app source code from public GitHub repositories.
Package source fetches app source code from public GitHub repositories.
store
Package store opens Tero's SQLite database, which holds apps and deploys.
Package store opens Tero's SQLite database, which holds apps and deploys.
sysexec
Package sysexec runs host commands for init.
Package sysexec runs host commands for init.
totp
Package totp implements RFC 6238 time-based one-time passwords with the settings every authenticator app supports: SHA-1, 6 digits, 30 seconds.
Package totp implements RFC 6238 time-based one-time passwords with the settings every authenticator app supports: SHA-1, 6 digits, 30 seconds.
webauthntest
Package webauthntest is a software passkey authenticator for tests: it answers the options the key service issues the way a browser and a platform authenticator would, with ES256 keys and attestation "none".
Package webauthntest is a software passkey authenticator for tests: it answers the options the key service issues the way a browser and a platform authenticator would, with ES256 keys and attestation "none".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL