vnctalk-avatar
The VNCtalk avatar service — a small Node/nginx service that preprocesses
(GraphicsMagick resize) and serves user avatars. This is the code that backs
avatar.vnc.biz; deployed in-cluster it becomes the dev cluster's avatar store.
What it does
On PUT /avatarupload/<jid> it receives raw image bytes, resizes them to a
fixed set of resolutions, and writes them to outputDir keyed by md5(jid):
<outputDir>/<md5(jid)>.jpg # default resolution (80)
<outputDir>/<md5(jid)>-<res>.jpg # 46, 47, 57, 80, 420, 800
The frontend reads these back via avatarServiceUrl + "/" + md5(jid) + "-<size>.jpg".
Prosody's vnc_vcard_avatar module is what PUTs the photo here on every vCard
avatar change (avatar_upload_url → <avatar-host>/avatarupload/).
API
| Method | Path | Purpose |
|---|---|---|
PUT |
/avatarupload/:jid |
Upload avatar bytes (Content-Type: image/*); returns md5(jid). |
DELETE |
/avatarupload/:jid |
Remove all of a jid's avatar files. |
GET |
/avatarupload/health |
{"status":"OK"}. |
POST |
/avatarupload/info |
Batch ctime lookup for a list of md5 ids. |
Static image serving (GET /<md5>.jpg) is not handled by app.js — it is
served by an nginx that shares the outputDir volume. The container installs
nginx but the static-serving server config must be supplied at deploy time (see
Deploy below).
Auth
PUT/DELETE require either:
- the legacy global Basic credentials (
avatar/ the shared avatar-server password), or - a JWT (Bearer/basic-password slot) signed with
config.jwtsecretwhosevncdomainclaim matches the jid's domain.
Open item: both the global Basic credential hash and
jwtsecretare hardcoded inapp/app.js/config/vnc-avatarservice.js. These are shared service credentials already committed elsewhere (vncdirectory/vncproject). For a clean in-cluster deploy they should move to env vars / Infisical — tracked separately.
Config (config/vnc-avatarservice.js)
| Key | Default | Purpose |
|---|---|---|
servicePort |
3896 |
Node upload API port. |
outputDir |
/var/opt/out |
Avatar filesystem store (needs a persistent volume). |
defaultresolution |
80 |
.jpg (no suffix) resolution. |
resolutions |
[46,47,57,80,420,800] |
All sizes written on upload. |
jwtsecret |
hardcoded | JWT verification secret (move to env). |
NODE_ENV selects the config block (development / development2 / gr13).
Build
docker build -t vnctalk-avatar .
# CI: push to main → .gitea/workflows/build.yml → kaniko
Deploy
Not yet declared in vnc-iac-env. Two prerequisites for a working in-cluster
deploy (tracked separately):
- Persistent
outputDir(/var/opt/out) — otherwise avatars are lost on pod restart. - nginx static serving — a server block (or ingress) that serves the
outputDirfiles atavatarServiceUrl, plus the CORS headers the browser needs.
Once deployed, point vnctalk-prosody's avatar_upload_url at it
(https://<avatar-host>/avatarupload/) and set avatar_upload_user /
avatar_upload_pass (from Infisical AVATAR_PASSWORD, not git).
Layout
| Path | Purpose |
|---|---|
app/app.js |
Express upload API (PUT /avatarupload/:jid, health, info). |
config/vnc-avatarservice.js |
Env-selected config (port, outputDir, resolutions, jwtsecret). |
Dockerfile |
Alpine multi-stage, non-root (vnc, uid 1001), gm + nginx + node. |
Dockerfile.ubuntu |
Legacy GCP base-image variant (pm2 + deb vnc-avatarservice). |
debian/ |
Legacy Debian packaging (used by the GCP-era image). |
conf.template/ |
Legacy passenger/nginx templates (mostly notification-proxy leftovers). |
.gitea/workflows/build.yml |
kaniko build → vnclagoon/vnctalk-avatar:{sha-…,latest}. |