Just A Good Guy Docs
Sign in

Your website

Use your own server: the host app

Run the small host app on your own Linux server, drop site folders into it, and manage everything from your dashboard.

For owners and developers
On this page

What the host app does

The host app is a small web server you run on your own machine. Once it's connected to your account it:

  • serves every site folder you put in its sites/ folder
  • serves sites you upload in the dashboard, and switches versions when you roll back
  • adds your widget to every page, unless you turn that off
  • gets free HTTPS certificates for your domains (with the Docker setup)
  • shows up in Dashboard → Website, with whether it's online

You don't need it if we host your site for you. It's for people who want the files on a machine they control.

What you need

  • A Linux server running Ubuntu or Debian, with a login that can use sudo. A small cloud server works well.
  • Ports 80 and 443 open to the internet, for websites and HTTPS.
  • Your domain, if you have one. You can also start without it.

Install

  1. Get a pairing code. In your dashboard, go to Website, then Use my own server, then Add a server. You get a code like ABCD-1234. Codes work for 24 hours.

  2. Run the installer on your server, with your code in it:

    Shell
    curl -fsSL https://justagoodguy.us/downloads/host/install.sh | sudo bash -s -- --code ABCD-1234

    It installs Docker if needed, starts the host app behind Caddy for automatic HTTPS, and connects it to your account. At the end it prints the link to your setup page. Keep that link private.

  3. Point your domain at the server. At your registrar, add an A record for @ and for www, each with your server's IP address.

  4. Visit your domain. The first visit can take a few seconds while the HTTPS certificate is set up.

Watch out

Piping a script into sudo bash runs it as root. If you'd rather check it first, download it, read it, then run it.

Everything lives in /opt/goodguy: your site folders in /opt/goodguy/sites, and the settings in /opt/goodguy/.env.

Installer options

Add these after bash -s --:

OptionWhat it does
--code ABCD-1234Connect to your account straight away
--no-dockerRun with Node.js 24 and systemd instead of Docker. This serves plain HTTP only, with no automatic HTTPS.
--port NPort for --no-docker (default 80)
--dir DIRInstall folder (default /opt/goodguy)
--api URLThe Just a Good Guy address to connect to (default https://justagoodguy.us)
--sites-domain NAMEAlso serve each folder at <folder>.<NAME>

Note

The host app itself speaks plain HTTP. HTTPS comes from Caddy, which the Docker install sets up for you. With --no-docker, put your own web server in front if you need HTTPS, or use the Docker install. Caddy works well with the ask endpoint below.

Without the installer

If you'd rather set it up by hand, the host app ships with a Dockerfile, a docker-compose.yml and a Caddyfile. Put them in one folder with the built dist/host.mjs, add a .env file with your settings (see Settings), and start it:

Shell
docker compose up -d --build

Connect it to your account

If you didn't pass --code to the installer, open the setup page link it printed. It looks like http://YOUR-SERVER-IP/_gg/setup?token=.... There you can connect in two ways:

  • With a pairing code (easiest). Type the code from Website → Use my own server → Add a server.
  • By signing in. Enter your Just a Good Guy email and password. Your password is used once, to get a connection key for this server, and is never saved on the server. Only a business owner can connect a server.

The setup page also shows which business the server is connected to, which sites it serves and where each one comes from, when it last synced, and a button to disconnect it.

Once it's connected, your dashboard shows the server as online. It checks in about once a minute, and counts as online if it was seen in the last 2 minutes.

The setup page needs its setup token, so nobody else can connect your server to their own account. The installer creates the token for you. To see it:

Shell
sudo grep SETUP_TOKEN /opt/goodguy/.env

Then open http://YOUR-SERVER-IP/_gg/setup?token= with the token on the end. Pages opened from the server itself don't need the token.

Add your sites

There are two ways, and you can mix them.

Upload it in the dashboard. Go to Website and upload a zip of your site. The server shows the new version within seconds. For uploaded sites, turn the widget on or off in the dashboard.

Put a folder on the server. Copy your site's folder into /opt/goodguy/sites/, for example with an SFTP app. Each folder needs an index.html at the top.

Text
sites/
  joesplumbing.com/
    index.html
    about.html
    images/
  spring-offer/
    index.html
  • Name a folder after your domain (sites/joesplumbing.com) and it becomes the site for that domain. www.joesplumbing.com is included.
  • Any other name (sites/spring-offer) becomes a site on a free address, if that name isn't already taken.
  • New folders are picked up within a few seconds, and appear in your dashboard after the next check-in.
  • If the server has only one site, it shows that site for every address, including the server's bare IP address.
  • Your sites keep working if the connection to Just a Good Guy drops. The server keeps the last version it received.

Turning the widget off for one site

The host app adds your widget to each page. To leave it out for one site, add a file called .goodguy.json to that site's folder:

JSON
{ "injectWidget": false }

Settings

With the installer, change settings in /opt/goodguy/.env (it holds JAGG_API, SETUP_TOKEN and SITES_DOMAIN), then restart: cd /opt/goodguy && sudo docker compose up -d. Running the host app yourself, set these as environment variables or command-line options.

VariableOptionDefaultWhat it does
JAGG_API--apihttps://justagoodguy.usThe Just a Good Guy server to connect to
HOST_PORT--port8200Port the host app listens on
HOST_BIND--bind0.0.0.0Address to listen on
SITES_DIR--sites-dir./sitesWhere your site folders live
STATE_DIR--state-dir./stateWhere it keeps its connection key and downloaded versions. Back this up.
SETUP_TOKEN--setup-tokennoneRequires this secret to use the setup page
SITES_DOMAIN--sites-domainnoneAlso serve <folder>.<this domain>, for example spring-offer.example.com

Other commands:

Shell
node host.mjs status       # what it's connected to, and its sites
node host.mjs setup-url    # the setup page link, with its token
node host.mjs disconnect   # forget the connection on this server
node host.mjs --help

With the Docker install, run them inside the container, for example cd /opt/goodguy && sudo docker compose exec host node /app/host.mjs status. Logs: sudo docker compose logs -f host (Docker) or journalctl -u goodguy-host -f (without Docker).

For developers

HTTPS with Caddy: the ask endpoint

Caddy's on-demand TLS asks the host app before it requests a certificate, so nobody can make your server request certificates for random names. The host app answers at:

HTTP
GET /_gg/tls-ok?domain=joesplumbing.com

It returns 200 for a host name it serves and 404 for anything else. The Caddyfile that ships with the host app uses it like this:

Text
{
  on_demand_tls {
    ask http://host:8200/_gg/tls-ok
  }
}

https:// {
  tls {
    on_demand
  }
  reverse_proxy host:8200
}

It also keeps plain HTTP working for bare IP addresses, so the setup page opens before your DNS is ready, and redirects named hosts to HTTPS.

GET /_gg/health returns { ok, version, paired, sites, uptime } for monitoring.

How it talks to your account

The host app only makes outgoing requests, so it works behind a home router or firewall as long as it can reach the internet. All requests go to JAGG_API and carry the server's connection key as Authorization: Bearer hst_....

RequestWhat it's for
POST /api/host/pairExchange a pairing code, or an owner's email and password, for the connection key. Limited to 10 tries per 15 minutes.
GET /api/host/sync?version=…&wait=25Long poll. Returns the list of sites this server should serve as soon as it differs from version, or after wait seconds (50 at most).
GET /api/host/deploys/:id/archiveDownloads one uploaded version as a zip.
POST /api/host/heartbeatReports the app version, basic stats and the folder names in sites/. New folders become sites in your account.

Each site in the sync answer has its host names, its folder name, the version to serve and where to download it, whether to add the widget, and the exact widget <script> tag to add.

If the dashboard removes the server, its key stops working and the host app asks you to connect it again.

Search the docs