Skip to content
AntoDonoPublic

About

Self-hosted app hosting for a single Linux server. Describe an app once in a small TOML file, and AHost runs it: a systemd service, an nginx site with HTTPS, a port from a registry, GPU assignment, optional sandboxing, and a web dashboard to start, stop, restart, and read logs.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

24 Commits

Folders and files

Repository files navigation

AHost

Self-hosted app hosting for a single Linux server. Describe an app once in a small TOML file, and AHost runs it: a systemd service, an nginx site with HTTPS, a port from a registry, GPU assignment, optional sandboxing, and a web dashboard to start, stop, restart, and read logs.

The AHost rack: every app as a routing strip, domain to port to service to GPU

It's built for the "one beefy box, many projects" setup (a GPU server, a homelab, a small VPS) where apps run as plain processes in their own venv, uv, conda or Node environment, not in Docker, and where the hand-written unit + nginx site + certbot routine has stopped scaling.

Status: working. CLI, root helper, adoption of legacy services, and the web dashboard are implemented and in production use on one server. Sandboxing levels and multi-process splitting are implemented but lightly exercised.

What it does

  • One manifest per app (apps/<name>.toml) is the only input. Everything else is generated.
  • Ports are allocated for you from a reserved range, checked against live listeners, and freed on removal.
  • systemd template units (ahost@<app>.service) with per-app overrides; multi-process apps are grouped under ahost@<app>.target.
  • nginx sites generated from one template: WebSocket and streaming (SSE / LLM token streams) work by default, static folders are served by nginx, nginx -t runs before every reload, and a failed test is rolled back.
  • Routers: put several apps under one domain by path (apps.example.com/chat, /docs, ...). Reserve paths first and assign them later; prefixes are stripped and redirects/cookies fixed up, or passed through.
  • HTTPS via certbot (webroot challenge); renewal uses certbot's own timer.
  • GPU assignment by card UUID, enforced at the device level, not just through CUDA_VISIBLE_DEVICES.
  • Sandboxing with systemd's native options (none / standard / strict), per app.
  • Dynamic DNS: optionally generates the ddclient host list from your manifests.
  • Adopt existing services: an importer turns hand-written units and nginx sites into manifests, and a cutover procedure moves them one at a time with a rollback at every step.
  • Web UI (Nuxt + Bun) and a CLI (ahost) over the same core.
  • Least privilege: the dashboard never runs as root. Root actions go through a small helper that renders files from validated manifests and accepts only a fixed set of verbs.

How it fits together

 apps/*.toml ──▶ ahost (reconciler, runs as user "ahost")
                    │  plan: diff desired vs actual
                    ▼
               ahost-helper (root, via sudo, fixed verbs)
        ┌───────────┼──────────────┬───────────────┬─────────────┐
        ▼           ▼              ▼               ▼             ▼
  /etc/ahost/   systemd         nginx          certbot       ddclient
  apps/*.env    ahost@<app>     ahost.d/       (webroot)     host list

The core operation is idempotent: make reality match the manifests. ahost plan shows what would change; ahost apply does it. Running it twice changes nothing the second time.

Quick look

# apps/blog-api.toml
name     = "blog-api"
workdir  = "/home/me/projects/blog-api"
command  = "uvicorn main:app --host 127.0.0.1 --port $PORT"
runtime  = { venv = ".venv" }
domains  = ["api.example.com"]
health   = "/healthz"
ahost plan blog-api      # port 10003 · write unit + env · write nginx site · issue cert for api.example.com
ahost apply blog-api
ahost logs blog-api -f

More examples: examples/apps/: a static site, a GPU inference server, a multi-process app.

Dashboard

Live logs for one app System: load per CPU thread, the processes on a thread, GPUs by app
Logs per app: live output, nginx requests and errors, filters, since last restart System: load on every CPU thread (click one to see its processes) and GPU memory by app
Router: one domain, a path per app New hosting form with a live plan preview
Router: one domain, a path per app; reserve paths and assign them from a dropdown New hosting: a form with a live preview of exactly what Apply will do (own domain or a router path)
Dark mode
Light and dark themes

Public landing page Rack on a phone

Screenshots come from tools/demo_server.py, which runs the real dashboard against a fictional server (uv run python tools/demo_server.py, then sign in as demo / demo-password-123). Regenerate them with uv run --with playwright python tools/screenshots.py.

ahost.service runs the API and serves the dashboard on ui.bind (127.0.0.1). With ui.domain set, the installer creates its nginx site and certificate (ahost ui-site). There's a password login (argon2), no default account:

sudo -u ahost ahost user add <name>

Rack (live status, start/stop/restart, logs), System, Router, Ports, and New hosting (form + live plan preview).

Documentation

Doc What's in it
docs/architecture.md components, reconcile loop, systemd/nginx/cert/port/GPU/DNS details
docs/manifest.md every manifest field
docs/routers.md several apps under one domain by path
docs/cli.md every ahost command
docs/agents.md for AI agents and scripts: recipes and rules (also ahost guide)
docs/security.md threat model, the root helper, UI auth, sandbox levels
docs/adopting-existing-services.md migrating hand-written units and nginx sites safely
docs/ui.md web UI spec and design direction
config/ahost.example.toml server-wide settings

Layout on a server

/home/<you>/AHost/          this repo: platform code, templates, UI, docs
/home/<you>/apps/           your manifests (a separate git repo; server-specific, never pushed with AHost)
/etc/ahost/ahost.toml       server-wide config
/etc/ahost/apps/            generated env files (0600, may hold secrets)
/etc/nginx/ahost.d/         generated nginx sites
/etc/systemd/system/ahost@.service, ahost@.target, ahost@<app>.service.d/
/var/lib/ahost/             state: port registry, apply history, users (SQLite)

Planned repo layout

ahost/        Python package: models, reconciler, ports, systemd, nginx, certs, gpu, api (FastAPI), cli (Typer)
helper/       ahost-helper: the root-side verbs (installed root-owned to /usr/local/sbin)
templates/    ahost@.service, ahost@.target, override.conf.j2, site.conf.j2, ddclient.j2
ui/           Nuxt (SPA) + Nuxt UI, built with Bun, served by the API as static files
deploy/       ahost.service, sudoers snippet, install script
examples/     example manifests
tests/        reconciler plan tests against fixture manifests (no root needed)

Requirements

Linux with systemd ≥ 249 and cgroup v2, nginx, certbot, uv, Bun (UI build only). NVIDIA driver for GPU features. Tested target: Ubuntu 22.04. uv provides Python 3.12, so the system Python version doesn't matter.

Development

All Python is managed with uv: pyproject.toml + uv.lock, Python pinned in .python-version.

uv sync                 # create .venv from uv.lock
uv run ahost plan       # read-only: works without root
uv run pytest
uv add <package>        # never pip install

Installation layout

The installed service and the root helper never run from a user-writable checkout. Apps run as a normal user, so if AHost ran from that user's home, any compromised app could modify the code that runs as root. deploy/install.sh (run with sudo) builds a root-owned copy with uv, using root's own Python and cache:

/opt/ahost/python/     uv-managed CPython   (UV_PYTHON_INSTALL_DIR)
/opt/ahost/venv/       runtime venv, installed --frozen from uv.lock
/var/cache/ahost-uv/   root's uv cache      (UV_CACHE_DIR), never a user's cache
/usr/local/sbin/ahost-helper   root-owned launcher → /opt/ahost/venv/bin/python -m ahost.helper

ahost.service runs /opt/ahost/venv/bin/ahost serve as the ahost user. To upgrade, review the diff and re-run the install.

Why not Coolify / Dokploy / CapRover?

They're good, but Docker-first: they want to own ports 80/443 with their own proxy, and every app has to become an image. AHost is for servers where apps already run as plain processes, and it adopts them one at a time next to an existing nginx.

About

Self-hosted app hosting for a single Linux server. Describe an app once in a small TOML file, and AHost runs it: a systemd service, an nginx site with HTTPS, a port from a registry, GPU assignment, optional sandboxing, and a web dashboard to start, stop, restart, and read logs.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages