mothership v0.1.3

voipappz

The VoIPAppz platform — docker-compose, configs, and the installer media that put them on a machine.

CI Release

Run it

One command on the mothership machine — no clone, no CLI, no toolchain:

curl -fsSL https://raw.githubusercontent.com/voipappz/mothership/main/installer/install.sh | sh

It installs Docker if needed, downloads this repository's stack, runs the setup wizard (organization, SIP domain, admin email, Let's Encrypt), pulls the images (Docker Hub token, or an image archive — Amazon S3 or a local file), starts the app plane with Compose, creates the first customer and admin login, and prints where everything is. Nothing is written to /opt/voipappz until setup succeeded; a rerun keeps .env and its secrets.

Unattended:

VA_DOMAIN=voip.example.com VA_CF_TOKEN=… VA_REGISTRY_USER=… VA_REGISTRY_TOKEN=… \
VA_ADMIN_EMAIL=admin@example.com VA_ADMIN_PASSWORD=… \
  sh -c 'curl -fsSL https://raw.githubusercontent.com/voipappz/mothership/main/installer/install.sh | sh'
Variable Meaning
INSTALL_DIR=/opt/voipappz Where the stack lands.
VA_DOMAIN, VA_SSL_EMAIL, VA_ORGANIZATION_NAME, VA_CF_TOKEN, VA_ACME_ALIAS Setup answers (see scripts/setup.sh).
VA_REGISTRY_USER, VA_REGISTRY_TOKEN Docker Hub, for the private nirlevi/* images; used through a temporary Docker config and removed.
VA_IMAGE_SOURCE=dockerhub|s3|archive Where images come from; s3 downloads VA_IMAGES_URL (the latest published archive), archive loads VA_IMAGES_ARCHIVE (path or URL).
VA_STACK_REF=main Which ref of this repository to download; VA_STACK_DIR= uses a local checkout instead.
VA_ADMIN_EMAIL, VA_ADMIN_PASSWORD The first customer's admin login (ONBOARD=0 skips it).
START=0 Install and configure without starting.

From a clone, the same thing is make install (stack from the checkout), and make images-archive / make images-publish produce the offline image archive the installer can load.

up prints where to find things — make urls prints them again later:

Admin website http://127.0.0.1:80
Monitoring http://127.0.0.1:8080
Alert mailbox http://127.0.0.1:8025

Alerts stop in that mailbox. They reach an inbox only once mailpit is given a real mail server to forward to. Deliberate: a node with none configured used to send nothing at all, silently.

Two things that will bite you

The folder name is your data's name. Docker names volumes after the directory. Clone into mothership and you get a clean slate. Clone into a folder matching one you already ran, and setup hands a newly generated database password to a volume holding the old one — postgres then refuses every connection, and says nothing about passwords or volumes.

Container names are fixed, so two checkouts cannot run at once. make down in the other one first.

Commands

make up         start                    make checks     the repo gate
make down       stop (keeps your data)   make test-lua   call-routing logic
make status     what is running          make test-sip   real kamailio: SIP/TLS/WSS
make health     health rollup            make test-alerting  a failure becomes email
make logs       follow the logs          make act        the CI job, locally
make urls       where to reach it        make image      the offline installer ISO
make cert       certificate state        make deploy     provision a remote host
make onboard    create the first customer

make down never passes -v, so your volumes survive.

Two planes

app — postgres, redis, nats, the API, admin SPA, Kong, InfluxDB, monitoring, and the ingress kamailio.

voip — the SIP plane in one container: FreeSWITCH, the node agent, and the egress kamailio. It installs separately, from ../va-crystal.

They can share a machine or not. config/services.tsv is the source of truth for which service is where.

The portal (github.com/voipappz/app)

The user-facing web app is its own repo, cloned beside this one. There is no apps/ here: it was a git subtree until 2026-08-31, then briefly a symlink, and both were removed — a committed symlink dangles on every fresh clone, checks out as a text file on Windows, and is simply absent in CI.

git clone https://github.com/voipappz/app ../app    # once, beside this repo

voipappz portal finds it there, or at $VA_PORTAL_DIR, or at --path.

make portal-dev       # dev stack (Vite :4200 + deno-api :4001)
make portal-print     # print the exact deploy commands, change nothing (DEST=)
make portal-deploy    # build, push, swap the container (DEST=)
make portal-ship      # git push the app repo, then deploy
make portal-status    # git + production health + deployed version
make portal-verify    # health: deno-api, web, /health dependency report

Those are thin wrappers. The implementation is voipappz portal (cli/src/commands/portal.cr); a second copy of that logic in the Makefile is how the two drift, so there is exactly one and it is in the CLI.

The app builds itself; mothership decides where it lands. The source, the Dockerfile and the app's CI belong to voipappz/app. The deploy policy belongs here:

config/portal/deploy.yml                      base
config/portal/deploy.{mtn,pbx20,nimbus}.yml   per-destination overrides
config/portal/portal-destinations.tsv         stop_first + healthcheck per destination
config/portal/.kamal/hooks/                   pre-build, post-deploy smoke checks
config/portal/.kamal/secrets{,-common}        gitignored; secrets.example is tracked

Choosing where the portal ships needs the view of every destination at once, and this is the only repo that has it. portal-destinations.tsv is a CATALOG the CLI reads — not conditionals re-encoded elsewhere. Two of its rows record production incidents; the file says which and why. An unlisted destination is an error, not a default.

Kamal runs containerised over two repos at once: the app checkout as /workdir (build context and .git, so VITE_APP_VERSION is stamped from the app's sha), with config/portal mounted read-only over /workdir/config and /workdir/.kamal. Nothing in the app repo needs to know about it.

Note kamal deploy -d <dest> reads .kamal/secrets-common and .kamal/secrets.<dest>never .kamal/secrets. voipappz portal deploy checks what kamal will actually read and fails early with the fix; a Makefile recipe that checked .kamal/secrets passed its own precheck and died inside the container. Use make portal-print to see exactly what will run.

A repo boundary now runs through a contract that used to be one commit: deploy.yml sets STATIC_DIR, EVENT_STORE_PATH, NATS_CDR_SUBJECTS, and the app's api/*.ts reads them. scripts/check-portal-contract.sh catches a rename that loses its other half; it runs in make checks and skips when no portal checkout is present. Portal CI lives in the app repo. The root make deploy still means "provision a remote host" and is never overloaded.

Install on a server

curl -fsSL https://raw.githubusercontent.com/voipappz/installer/main/install.sh | sh

Ensures Docker, installs into /opt/voipappz, then setup → up -p appup -p voip → health. Tune with INSTALL_DIR, VERSION, BOOTSTRAP=0 (install only), SKIP_VOIP=1 (app profile only).

To pick your own answers rather than auto-detected ones, fill in an answer file — every VOIPAPPZ_* key answers one prompt. It holds credentials, is git-ignored, and should be deleted afterwards:

cp installer.env.example installer.env    # domain, Cloudflare token, SMTP
voipappz setup --env-file installer.env

TLS

Certificates come from acme.sh over DNS-01, so no inbound port need be open — which is the point, since voip nodes have no :80. It needs one static DNS record delegating the challenge into the zone we control:

_acme-challenge.<your-domain>   CNAME   _acme-challenge.<VA_ACME_ALIAS>

Setup asks for a Cloudflare API token (Zone:DNS:Edit) and verifies it live.

Without a token the node runs on a self-signed placeholder. TLS still negotiates, so a fresh node looks fine while real clients reject it. Check make cert before going live.

Deploy a remote host

export VA_REGISTRY_PASSWORD=<registry-token>
make deploy ARGS="--host <ip> --user <u> --key ~/.ssh/<key> --check"   # preflight
make deploy ARGS="--host <ip> --user <u> --key ~/.ssh/<key>"

Configuration

make setup writes .env (config plus generated secrets, mode 0600) and config/va.yaml. Both are git-ignored and never leave the machine.

Build from source

The CLI is built here, from cli/. It compiles in Docker against a pinned Crystal image, so a fresh clone needs nothing but Docker — no toolchain, no sibling checkout, no registry login.

make build       # compile cli/ (static) and put it at bin/voipappz
make test        # its spec suite

The same build produces the release assets, so a binary you compile here and one you download from a release are the same binary.

Docs: https://voipappz.zendesk.com/hc/en-us

Repository

mothership

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 2
  • about 3 hours ago
  • July 21, 2026
License

Links
Synced at

Tue, 01 Sep 2026 01:14:44 GMT

Languages