Developer Tools AWS

Wakapi 2.18.2 on AWS User Guide

| Product: Wakapi 2.18.2 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Wakapi 2.18.2 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Wakapi is an open source, self hosted coding statistics server that speaks the WakaTime API. You keep using the standard WakaTime plugins for Visual Studio Code, JetBrains IDEs, Vim, Neovim, Sublime Text and dozens of other editors; you only point them at your own server. Wakapi turns the "heartbeats" those plugins send into dashboards of the time you spend per project, language, editor, operating system and machine, with daily and ranged summaries, project pages, badges and a WakaTime compatible REST API, while your coding history stays inside your own AWS account.

The cloudimg image locks Wakapi down:

  • HTTPS from the first boot. nginx serves Wakapi on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS, and Wakapi's session cookies are marked Secure.
  • An administrator made for your instance, nobody else. The first boot creates the administrator admin with a password and an API key generated on your instance, before nginx or Wakapi accept a single connection, and writes them to /var/lib/cloudimg/wakapi-credentials.txt (root only). It also generates the instance's password salt and the key that signs its session cookies. No account, password or key is baked into the image.
  • Closed to strangers. Public sign-up and invite codes are off, so nobody can register; visitors who are not signed in land on the login page, and the public leaderboard is off.
  • Your plugins keep working. WakaTime plugins authenticate with your API key on /api, so there is no extra password prompt in front of the API. A heartbeat with a wrong key is refused.
  • Outbound requests limited to WakaTime. Wakapi can forward your heartbeats to a WakaTime account. On this image Wakapi can reach api.wakatime.com (and wakapi.dev) and nothing else, so a user cannot make the server fetch an address of their choosing. Data imports are off until you enable them (Step 8). Wakapi and nginx are blocked from the EC2 instance metadata service at the kernel level.
  • Cross-site requests refused. nginx refuses a form post from another web site and a heartbeat from another site that relies on your browser's session instead of an API key, and refuses another site's attempt to trigger the links that change a setting. Compressed request bodies are refused.
  • Features you do not use are closed. The built in API explorer (/swagger-ui, which bundles an old copy of the DOMPurify library), OpenID Connect sign-in, the anonymous plugin error report, metrics and subscriptions answer 404.
  • The host name comes from configuration, never from the request. Wakapi's public URL is set on first boot from the instance's public DNS name, nginx refuses requests for host names the instance does not serve, and Wakapi always sees the real client address, whatever forwarding headers a request carries, so its login rate limit applies to the real client.
  • Built from source on the current Go release. The upstream release binary was built with an older Go and network library that govulncheck flags. cloudimg builds the 2.18.2 source tag with Go 1.27.3 and the fixed golang.org/x/net, reproducibly, and govulncheck finds no vulnerable code in it.
  • Hardened defaults. Wakapi runs as its own unprivileged user with no capabilities and a read only view of the system except its data directory; nginx and Wakapi refuse to start until first boot has finished; the AMI requires IMDSv2.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: Wakapi 2.18.2 (/usr/local/bin/wakapi, with its web UI built in), nginx, SQLite, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH), TCP 443 (Wakapi) and TCP 80 (redirects to HTTPS) from your own networks only, including the networks your developers' editors send heartbeats from.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Wakapi 2.18.2 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (a smaller instance is fine for a few developers), your key pair, a security group as above and a subnet.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=wakapi}]'

Step 2: Connect to your instance

Version SSH user
Wakapi 2.18.2 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the certificate, the password salt, the cookie key and the administrator, then Wakapi and nginx start:

systemctl is-active wakapi nginx wakapi-firstboot
sudo test -f /var/lib/cloudimg/wakapi-firstboot.done && echo FIRSTBOOT_DONE

All three units are active and the first boot marker is present:

active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on the network. Wakapi itself listens on 127.0.0.1 only:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:3000 users:(("wakapi"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))

Step 4: Get your URL, password and API key

sudo cat /var/lib/cloudimg/wakapi-credentials.txt

The file is readable by root only and holds the URL, the administrator's user name, this instance's password and the administrator's API key:

# Wakapi 2.18.2 - generated on first boot by wakapi-firstboot.service
# This administrator was created on THIS instance; no account was baked into the image. Keep this file secret and
# change the password after your first sign-in (Settings > Account).

WAKAPI_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
WAKAPI_ADMIN_USER=admin
WAKAPI_ADMIN_PASSWORD=<WAKAPI_ADMIN_PASSWORD>
WAKAPI_API_KEY=<WAKAPI_API_KEY>
#
# Open WAKAPI_URL in a browser (self-signed certificate minted on this instance's first boot; port 80 redirects to
# https) and sign in. Point your editor's WakaTime plugin at this server in ~/.wakatime.cfg:
#   [settings]
#   api_url = https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/api
#   api_key = <WAKAPI_API_KEY>
# (with this image's self-signed certificate also add: no_ssl_verify = true, or install a trusted certificate)
# Public sign-up and invite codes are OFF. Forwarding to WakaTime reaches api.wakatime.com only; imports are off.
# Self-test:  sudo wakapi-selftest
# Change the name it is served as:  sudo wakapi-set-hostname <your-dns-name>
# User guide: https://www.cloudimg.co.uk/guides/wakapi-2-18-2-on-ubuntu-24-04-aws/

The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To serve Wakapi under your own DNS name, see Step 10.

Step 5: Sign in

Open the WAKAPI_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own certificate, Step 10). Sign in as admin with the password from Step 4. The Sign up button is still drawn on the page, but registration is switched off: Wakapi answers "registration is disabled on this server".

The Wakapi login page on cloudimg, with the user name and password fields

Then open Settings, and change the password under Account to one only you know.

Step 6: Connect your editors

Install the WakaTime plugin for your editor (the WakaTime plugin list covers VS Code, JetBrains IDEs, Vim, Neovim, Sublime Text, Xcode and many more), then point it at your Wakapi server instead of WakaTime. On each workstation, edit ~/.wakatime.cfg (on Windows %USERPROFILE%\.wakatime.cfg):

[settings]
api_url = https://<your-instance-host>/api
api_key = <WAKAPI_API_KEY>
no_ssl_verify = true

no_ssl_verify is only needed while the instance uses its self signed certificate; remove it once you install a certificate your workstations trust (Step 10). Wakapi's Setup page shows the same snippet for the account you are signed in as:

The Wakapi setup instructions page showing the ~/.wakatime.cfg settings with the api_url and api_key lines

You can check the API from the instance itself. This sends one test heartbeat with the administrator's API key (read from the credentials file into a temporary file, never typed on a command line) and reads it back:

H=$(sudo sed -n 's#^WAKAPI_URL=https://\([^/]*\)/#\1#p' /var/lib/cloudimg/wakapi-credentials.txt)
CFG=$(mktemp); chmod 600 "$CFG"
printf 'header = "Authorization: Basic %s"\n' "$(sudo sed -n 's/^WAKAPI_API_KEY=//p' /var/lib/cloudimg/wakapi-credentials.txt | tr -d '\n' | base64 -w0)" > "$CFG"
curl -sk -K "$CFG" --resolve "$H:443:127.0.0.1" -o /dev/null -w 'heartbeat: %{http_code}\n' \
  -H 'Content-Type: application/json' -A 'wakatime/v1.130.1 (linux) curl-test' \
  --data "[{\"entity\":\"/home/dev/hello/main.go\",\"type\":\"file\",\"time\":$(date +%s),\"project\":\"hello-wakapi\",\"language\":\"Go\",\"category\":\"coding\"}]" \
  "https://$H/api/heartbeat"
TODAY=$(curl -sk -K "$CFG" --resolve "$H:443:127.0.0.1" "https://$H/api/compat/wakatime/v1/users/current/heartbeats?date=$(date -u +%F)")
rm -f "$CFG"
grep -o '"project":"hello-wakapi"' <<<"$TODAY" | sort -u
heartbeat: 201
"project":"hello-wakapi"

Each person who uses the server should have their own account and API key (Step 8). API keys are also listed, and can be added or revoked, under Settings > API Keys.

Step 7: Your dashboard

Once your editors send heartbeats, Dashboard shows the totals and breakdowns for the interval you pick (today, this week, the past 7 days, a custom range and more), by project, language, editor, operating system, machine, label and category. The filters at the top narrow everything to one project, language or machine:

The Wakapi dashboard for the past 7 days: total time, heartbeats, top project, language and editor, a projects bar chart and languages and editors pie charts

Projects lists every project you worked on, most recent first; choose one for its own statistics:

The Wakapi projects page listing four projects with their first and last activity

Step 8: Users, forwarding to WakaTime and imports

The image ships single user: sign-up, invite codes and imports are off. The settings live in /etc/wakapi/config.yml:

sudo grep -E '^  (public_url|allow_signup|invite_codes|import_enabled|trust_reverse_proxy_ips|insecure_cookies|disable_frontpage):' /etc/wakapi/config.yml
  public_url: https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com
  import_enabled: false
  insecure_cookies: false
  allow_signup: false
  invite_codes: false
  disable_frontpage: true
  trust_reverse_proxy_ips: 127.0.0.1
  • Add teammates with invite codes. Set invite_codes: true and restart Wakapi (sudo systemctl restart wakapi). Settings then shows an Invite Friends button that generates a single use invite link (valid for 24 hours); send it to a colleague, who registers with it while open sign-up stays off. Avoid allow_signup: true unless the instance is only reachable from a private network.
  • Forward your heartbeats to WakaTime. Under Settings > Integrations, enter your WakaTime API key and choose Connect. Wakapi then relays each heartbeat to api.wakatime.com as well, so you can use both:

The Wakapi Integrations settings with the WakaTime API URL and API key fields

  • Forward to another server, or import data. Wakapi's outbound requests are limited to the hosts in NO_PROXY in /etc/wakapi/egress.env. To forward to your own Wakapi or WakaTime compatible server, or to import history from WakaTime (whose data export downloads from a separate storage host), add the host names, comma separated, to NO_PROXY; for imports also set import_enabled: true and add the host to import_hosts_whitelist in config.yml. Then sudo systemctl restart wakapi.

Step 9: Run the self test

The image has a self test that checks HTTPS, that anonymous visitors and sign-up are refused, that guessed passwords are refused, then signs in, sends a heartbeat with the API key, reads it back, checks that a wrong key is refused, removes its test heartbeat and checks the listening ports:

sudo wakapi-selftest
SELFTEST-OK https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ : anonymous refused, sign-up refused, guessed passwords refused, admin signed in, heartbeat stored + read back with the API key (wrong key 401), listeners 22/80/443

Step 10: Your own DNS name and certificate

Point a DNS name at the instance, then set it as the name Wakapi is served as. This writes it into the nginx configuration and Wakapi's public URL, generates a new self signed certificate for it and restarts Wakapi:

sudo wakapi-set-hostname <your-domain>

To use a certificate from your own certificate authority (or one you obtained for the name), replace /etc/ssl/wakapi/wakapi.crt and /etc/ssl/wakapi/wakapi.key (keep the key mode 0600, owner root) and reload nginx with sudo systemctl reload nginx. Running wakapi-set-hostname again generates a new self signed certificate, so install your own after it. Once your workstations trust the certificate, remove no_ssl_verify from their ~/.wakatime.cfg.

Step 11: Check the security posture from the command line

H=$(sudo sed -n 's#^WAKAPI_URL=https://\([^/]*\)/#\1#p' /var/lib/cloudimg/wakapi-credentials.txt)
c() { curl -sk --resolve "$H:443:127.0.0.1" --resolve "$H:80:127.0.0.1" -o /dev/null "$@"; }
c -w 'port 80: %{http_code} -> %{redirect_url}\n' "http://$H/"
c -w 'API without a key: %{http_code}\n' "https://$H/api/summary?interval=today"
c -w 'heartbeat with a wrong key: %{http_code}\n' -u 'not-a-key:' -H 'Content-Type: application/json' --data '[]' "https://$H/api/heartbeat"
c -w '%{http_code}\n' -X POST --data 'username=someone&password=secret123&password_repeat=secret123' "https://$H/signup" | sed -E 's/^(403|429)$/sign-up: refused/'
c -w 'cross-origin form post: %{http_code}\n' -X POST -H 'Origin: https://evil.example' "https://$H/login"
c -w 'API explorer (/swagger-ui): %{http_code}\n' "https://$H/swagger-ui/"
c -w 'compressed request body: %{http_code}\n' -X POST -H 'Content-Encoding: gzip' --data 'x' "https://$H/api/heartbeat"
curl -s -o /dev/null -w 'unknown host name: %{http_code} (connection closed)\n' -H 'Host: other.example' http://127.0.0.1/ || true
port 80: 301 -> https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
API without a key: 401
heartbeat with a wrong key: 401
sign-up: refused
cross-origin form post: 403
API explorer (/swagger-ui): 404
compressed request body: 415
unknown host name: 000 (connection closed)

The sign-up attempt is refused with 403 ("registration is disabled"), or with 429 once the sign-up rate limit for your address is reached.

Wakapi and nginx cannot reach the instance metadata service, and Wakapi's outbound requests go only to the allowed hosts:

for u in wakapi nginx; do echo "$u denies: $(systemctl show -p IPAddressDeny --value $u | tr ' ' '\n' | sort | xargs)"; done
sudo grep -E '^(HTTPS_PROXY|NO_PROXY)=' /etc/wakapi/egress.env
wakapi denies: 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
nginx denies: 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
HTTPS_PROXY=http://127.0.0.1:9
NO_PROXY=api.wakatime.com,wakapi.dev

HTTPS_PROXY points at a port where nothing listens, so any request to a host that is not in NO_PROXY fails before a connection is made.

Step 12: Server components

Item Location
Wakapi binary /usr/local/bin/wakapi
Configuration /etc/wakapi/config.yml (root:wakapi, mode 0640)
Per-instance salt and cookie key /etc/wakapi/wakapi.env (root, mode 0600)
Outbound allowlist /etc/wakapi/egress.env
Database (SQLite) /var/lib/wakapi/wakapi.db
Service units wakapi.service, nginx.service, wakapi-firstboot.service
nginx site /etc/nginx/sites-available/cloudimg-wakapi (closed routes: /etc/nginx/wakapi-closed.conf)
Certificate and key /etc/ssl/wakapi/
First boot script /usr/local/sbin/wakapi-firstboot.sh
Host name helper /usr/local/sbin/wakapi-set-hostname
Self test /usr/local/sbin/wakapi-selftest
Credentials /var/lib/cloudimg/wakapi-credentials.txt (mode 0600)
/usr/local/bin/wakapi -version
command -v go || echo "no Go toolchain"
nginx -v 2>&1
sed -n '1,5p' /usr/share/doc/cloudimg-wakapi/BUILD-INFO.txt
2.18.2
no Go toolchain
nginx version: nginx/1.24.0 (Ubuntu)
wakapi            2.18.2 (tag 2.18.2, commit 1157f203c8b646466b4e72563764738b10f0cd0d), MIT - built on the build instance from the tag
                  https://github.com/muety/wakapi/archive/refs/tags/2.18.2.tar.gz (sha256 1c77089b75b6e88dbb984708d0a03476867913e29c61b09bf783063336e73e1e)
                  go1.27.3 (official tarball, sha256 65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40; not shipped), CGO_ENABLED=0,
                  GOEXPERIMENT=greenteagc,jsonv2, -trimpath, -ldflags '-w -s'; binary sha256 7a121ee6531fa3c2259f7e3300714db4deab9880b9e189fc30f00978584f9156
                  (reproducible: identical to the binary audited off-instance)

/usr/share/doc/cloudimg-wakapi/BUILD-INFO.txt records the source and toolchain checksums, the dependency update, the bundled web libraries and the govulncheck result; go.mod, go.sum and govulncheck-wakapi-source.txt sit beside it.

Backups: Wakapi keeps everything in the SQLite database /var/lib/wakapi/wakapi.db on the root volume. Take an EBS snapshot of the root volume (or use AWS Backup); for a consistent copy, sudo systemctl stop wakapi, start the snapshot, then sudo systemctl start wakapi.

What cloudimg changed on the operating system: SSH accepts keys only (no passwords, no root login), the root account has no password, the firmware metadata refresh timer (fwupd-refresh) is masked because EC2 has no updatable firmware, the golden image's boot time updater and the SSM user's passwordless sudo are removed, and unattended security upgrades are on.

Step 13: Security notes and recommendations

  • Allow ports 22, 80 and 443 in the security group from your own networks only.
  • Change the administrator password after your first sign-in (Step 5), keep the credentials file secret, and give each person their own account and API key (Step 8).
  • Use your own DNS name and a certificate your clients trust (Step 10), then drop no_ssl_verify from the plugins' configuration.
  • Send API keys in the Authorization header, as the WakaTime plugins do, rather than as an api_key= URL parameter: request URLs are written to the nginx and Wakapi logs.
  • govulncheck lists one advisory for a module in Wakapi's build whose vulnerable package Wakapi never uses, so no vulnerable code is reachable: GO-2026-5932 (the golang.org/x/crypto/openpgp package is unmaintained; there is no fixed version). The built in API explorer bundles DOMPurify 2.3.10; its /swagger-ui route is closed by nginx on this image (use the API reference at https://wakapi.dev/swagger-ui/ instead). cloudimg rechecks both at each Wakapi release.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.

Support and licensing

Wakapi is open source under the MIT licence (Ferdinand Mütsch and contributors); the licence text is in /usr/share/doc/cloudimg-wakapi/, and the source of this exact release is at https://github.com/muety/wakapi/tree/2.18.2. Its built in web UI includes open source libraries under the MIT licence and Iconify under the Apache 2.0 licence. WakaTime is a trademark of its owner; Wakapi and this image are not affiliated with or endorsed by WakaTime. There is no per user or per CPU licence fee. The AMI price covers cloudimg's build, hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA