A Flutter app that installs and manages a dcommsctl host for people who are not sysadmins. It takes you from "I have a VPS (or a DigitalOcean or Yandex Cloud account)" to "here is my Dawa URL and my running apps", with no terminal work.
It runs on Linux, macOS, Windows, Android and iOS.
There are four ways to add a server:
| Mode | You provide | Result |
|---|---|---|
| SSH | host, port, user, password or private key | The app runs the installer over SSH and saves the server as managed (live status) |
| DigitalOcean | a personal access token | The app creates a droplet with a generated SSH key, installs through cloud-init, and saves it as managed |
| Yandex Cloud | a service account authorized key (JSON) | The app creates a VM with a generated SSH key, installs through cloud-init, and saves it as managed |
| Manual | nothing; you run a curl … | sudo bash one-liner yourself |
You paste the output back and the server is saved as unmanaged (access details only) |
All four use the same install script, generated by lib/install/remote_script.dart. The script:
- installs Docker,
- downloads the dcommsctl tarball and checks its SHA-256,
- unpacks it to
/opt/dcomms, - pulls the Dawa image and tags it
dawa:latest, - starts dcommsctl as a systemd service on
127.0.0.1:6006, - sets up the firewall,
- prints an enrollment block that the app parses.
Managed servers are reached through an SSH tunnel to dcommsctl's JSON-RPC websocket. The
daemon is never exposed publicly. Dawa is served at http://dawa.<ip-dashed>.sslip.io:8000
by default, or under a domain you choose.
See PLAN.md for the design, the security model and known issues.
Warning: dcommsctl's Dawa template still sets
DEMO=true, so anyone who can reach a fresh Dawa can sign in as admin. The app shows this on the server detail screen.
flutter pub get
flutter analyze
flutter test # offline suite
flutter run -d linux # or macos / windows / an Android or iOS devicelib/
config.dart build-time settings: download URL, version, Dawa image
install/ remote script, cloud-init + SSH / DigitalOcean / Yandex installers
providers/ DigitalOcean and Yandex Cloud API clients
ssh/ sessions, host-key pinning, key generation, tunnel
rpc/ JSON-RPC client and dcommsctl API
store/ server records (JSON) and secrets (secure storage)
models/ ui/
test/
tool/e2e_server.sh throwaway Debian + sshd container for the live test
The installer downloads two things that do not come from this repo. Both have to be published before the app can install a working server.
dcommsctl reads conf_tmpl/ and scripts/ by relative path, so the artifact
contains the whole runtime tree, not only the binary. release.sh builds it:
cd ../dcommsctl
./release.sh # version from .dcomms-installation (first line)
./release.sh 0.0.2 # or set it explicitly
ARCHES=amd64 ./release.sh # one architecture onlyIt writes to dcommsctl/dist/:
dcommsctl-<version>-linux-amd64.tar.gz
dcommsctl-<version>-linux-amd64.tar.gz.sha256
dcommsctl-<version>-linux-arm64.tar.gz
dcommsctl-<version>-linux-arm64.tar.gz.sha256
The binaries are static and the tarballs are reproducible (set SOURCE_DATE_EPOCH to pin
the mtime). Before it finishes, the script repeats the installer's own download, verify
and unpack checks on the output, so a broken artifact fails here and not on a user's
server.
Check that the names match what this app asks for:
flutter test test/artifact_test.dartThen upload all files in dist/ to a static HTTPS host, together in one directory.
Both the .tar.gz and its .sha256 must be at the same base URL.
dcommsctl's Dawa compose template uses a bare image: dawa:latest. That image exists only
on a machine that built it, so the installer pulls a registry image and tags it locally.
Build and push it from dawa/:
cd ../dawa
docker build -t dawa .
docker login gitlab.equalit.ie
docker tag dawa gitlab.equalit.ie/dcomms/dawa:latest
docker push gitlab.equalit.ie/dcomms/dawa:latestThe server pulls this image without credentials, so the registry image must be public.
To support arm64 servers, push a multi-arch image (for example,
docker buildx build --platform linux/amd64,linux/arm64 -t <image> --push .).
Everything the installer downloads is set in lib/config.dart. These are compile-time constants, so rebuild the app after changing them.
| Constant | Set it to |
|---|---|
kDcommsctlDownloadUrl |
The base URL where you uploaded dist/, without a trailing slash. Currently the placeholder https://downloads.dcomms.example/dcommsctl. |
kDcommsctlVersion |
The <version> you passed to release.sh. It has to match the filenames exactly. |
kDawaImage |
The registry image you pushed, e.g. gitlab.equalit.ie/dcomms/dawa:latest. |
The installer then fetches:
<kDcommsctlDownloadUrl>/dcommsctl-<kDcommsctlVersion>-linux-<arch>.tar.gz
<kDcommsctlDownloadUrl>/dcommsctl-<kDcommsctlVersion>-linux-<arch>.tar.gz.sha256
<arch> is the server's dpkg --print-architecture (amd64 or arm64).
To release a new version, follow these steps in order:
- Run
release.sh <version>. - Upload
dist/. - Set
kDcommsctlVersion(and the URL, if it changed). - Run
flutter test. - Rebuild the app.
The offline suite does not touch SSH or Docker. To run a real install end to end, you need a local Docker daemon:
tool/e2e_server.sh up # Debian 12 + systemd + sshd on port 2222
DCOMMS_E2E_HOST=127.0.0.1 DCOMMS_E2E_PORT=2222 \
DCOMMS_E2E_USER=root DCOMMS_E2E_PASSWORD=dcomms \
DCOMMS_E2E_DOWNLOAD_URL=<where dist/ is served> \
flutter test test/e2e_ssh_test.dart
tool/e2e_server.sh downWithout DCOMMS_E2E_DOWNLOAD_URL, the run stops at the download step on purpose. That run
still covers connect, sudo, log streaming and the error block. To test without a public
host, serve dist/ locally with python3 -m http.server -d ../dcommsctl/dist 8080, then use
a URL the container can reach (for example, http://172.17.0.1:8080).