V4CALL

From CompleteNoobs
Revision as of 14:17, 19 April 2026 by AwesomO (talk | contribs) (Created page with "{{:LICENCE_HEADER_MIT}} = v4call — Deploy Your Own Server on Ubuntu 24.04 with Docker = From CompleteNoobs This guide walks through deploying your own v4call server from scratch on a fresh Vultr Ubuntu 24.04 VPS — from first login to a working HTTPS video/audio calling service on your own domain. v4call is an open-source, decentralised video and audio calling platform that uses Hive blockchain for identity and HBD micropayments. It supports custom Hive-Engine to...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
Please Select a Licence from the LICENCE_HEADERS page
And place at top of your page
If no Licence is Selected/Appended, Default will be CC0

Default Licence IF there is no Licence placed below this notice! When you edit this page, you agree to release your contribution under the CC0 Licence

LICENCE: More information about the cc0 licence can be found here:
https://creativecommons.org/share-your-work/public-domain/cc0

The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.

You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.

Licence:

Statement of Purpose

The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").

Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.

For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.

1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:

   the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
   moral rights retained by the original author(s) and/or performer(s);
   publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
   rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
   rights protecting the extraction, dissemination, use and reuse of data in a Work;
   database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
   other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.

2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.

3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.

4. Limitations and Disclaimers.

   No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
   Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
   Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
   Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.

LICENCE: When you edit this page, you agree to release your contribution under the MIT Licence

LICENCE

Copyright <YEAR> <COPYRIGHT HOLDER>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


v4call — Deploy Your Own Server on Ubuntu 24.04 with Docker

From CompleteNoobs

This guide walks through deploying your own v4call server from scratch on a fresh Vultr Ubuntu 24.04 VPS — from first login to a working HTTPS video/audio calling service on your own domain.

v4call is an open-source, decentralised video and audio calling platform that uses Hive blockchain for identity and HBD micropayments. It supports custom Hive-Engine token payments, encrypted direct messages with persistent chat history, voice-only and video calls, and a free-market platform fee system. Fork it, run your own server, keep all your platform fees, join the federation, federation coming (unknown time) kismet.

Source code: https://github.com/CompleteNoobs/v4call

End result: A working v4call server at https://call.yourdomain.com that you can log into with a Hive account.

Contents

What You Need

Before starting, make sure you have the following:

  • A Vultr account — sign up at vultr.com - please use are Vultr Referral link to help us cover server costs.
  • A domain name with DNS access — e.g. call.yourdomain.com
  • Two Hive accounts — one for your server identity (receives platform fees), one for escrow (holds caller funds during calls). Create free accounts at signup.hive.io
  • Hive Keychain browser extension — for login and payments. Install from hive-keychain.com
  • A GitHub account — free at github.com. You will fork the v4call project.
  • A terminal — Mac: Terminal app. Windows: PowerShell or PuTTY.

You do not need to know how to code. Every command can be copy-pasted exactly as shown.

Step 1: Create Your Vultr VPS

  1. Log into my.vultr.com
  2. Click Deploy New Server
  3. Choose Cloud Compute — Shared CPU
  4. Choose a location close to you
  5. Choose Ubuntu 24.04 LTS x64
  6. Choose the $6/month plan (1 CPU, 1GB RAM)
  7. Set Server Hostname to something like v4call-server
  8. Click Deploy Now

Wait ~60 seconds for it to start. Click the server to find:

  • IP Address — looks like 123.456.789.012 — write it down
  • Password — click the eye icon — write it down

Step 2: Point Your Domain at the VPS

Log into your DNS provider and add an A record:

Field Value
Type A
Name call (or @ for root domain)
Value Your VPS IP address
TTL 300

DNS takes a few minutes to propagate. Check from your computer later:

nslookup call.yourdomain.com

Must show your VPS IP before Step 12. You can continue with all other steps while waiting.

Step 3: Log into Your VPS

Open a terminal on your computer:

ssh root@YOUR_SERVER_IP

Type yes when asked about the fingerprint, then paste the password from Vultr (right-click to paste in most terminals).

When you see root@v4call-server:~# you are in.

Step 4: Update the Server

apt update && apt upgrade -y

Wait for it to complete (1-2 minutes).

Step 5: Install Docker

Install Docker using the official installer script:

curl -fsSL https://get.docker.com | sh

Verify:

docker --version

Should show Docker version 26.x.x or similar.

Install Docker Compose plugin:

apt install -y docker-compose-plugin

Verify:

docker compose version

Should show Docker Compose version v2.x.x.

Step 6: Install Git

apt install -y git
git --version

Step 7: Download and/or Fork and Clone the Code

Clone onto your VPS

cd /opt
git clone https://github.com/CompleteNoobs/v4call.git
cd v4call

Fork on GitHub - Optional

Forking gives you your own copy of the code that you can customise — change the name, branding, fees — without affecting the original project.

  1. Go to https://github.com/CompleteNoobs/v4call
  2. Click the Fork button (top right of the page)
  3. Select your GitHub account as the destination
  4. Click Create fork

You now have your own copy at https://github.com/YOURUSERNAME/v4call

Clone onto your VPS

cd /opt
git clone https://github.com/YOURUSERNAME/v4call.git
cd v4call

List the files to confirm:

ls

You should see: server.js public/ Dockerfile docker-compose.yml nginx/ package.json .env.example

Step 8: Configure Your Server (.env file)

All settings live in a single .env file. Copy the template:

cp .env.example .env
nano .env

Fill in your values:

# ── Server Identity ──────────────────────────────────────
SERVER_NAME=yourcallapp
SERVER_DOMAIN=call.yourdomain.com
SERVER_HIVE_ACCOUNT=yourhiveaccount
ESCROW_ACCOUNT=yourescrowaccount
V4CALL_ESCROW_KEY=5Kxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Secret key to access /admin/ledger and /admin/balance endpoints.
# Choose a long random string — treat it like a password.
# Example: openssl rand -hex 32
ADMIN_KEY=make-up-a-long-random-string-at-least-20-characters

# ── Hive Blockchain ──────────────────────────────────────
CHAIN=hive
HIVE_API=

# ── Platform Fee ─────────────────────────────────────────
# Percentage your server takes from each paid call/DM (10 = 10%)
# This is the MINIMUM fee — users whose rates post sets a lower
# platform fee will be rejected. Users who set a higher fee
# get the best price (your server's rate, not their higher number).
DEFAULT_PLATFORM_FEE=10

# ── Network ──────────────────────────────────────────────
PORT=3000
BIND_HOST=127.0.0.1

# ── Chat Storage ─────────────────────────────────────────
# How many days to keep stored DMs before automatic cleanup
DM_RETENTION_DAYS=33
# How many days to keep stored room messages before cleanup
ROOM_RETENTION_DAYS=33
# How many recent DMs per conversation to show on login (0 = off)
DM_PREVIEW_COUNT=1

# ── Call Behaviour (advanced — defaults are fine) ────────
# CALL_COOLDOWN_MS=30000
# MAX_CALL_DURATION_MIN=120
# PAYMENT_VERIFY_RETRIES=3
# PAYMENT_VERIFY_DELAY_MS=5000

Key points:

  • V4CALL_ESCROW_KEY — must be the active private key for your escrow account. Find it in your Hive wallet → Keys & Permissions → Active. Starts with 5K. Never share this.
  • ADMIN_KEY — invent a secret password for accessing admin tools
  • HIVE_API — leave blank to use all built-in Hive nodes automatically
  • DEFAULT_PLATFORM_FEE — your server's minimum platform fee percentage. See Platform Fee System below for how this works.
  • DM_RETENTION_DAYS / ROOM_RETENTION_DAYS — how long chat messages are kept in the database. A cleanup job runs every hour and deletes anything older. Set to 0 to keep messages indefinitely (not recommended).
  • DM_PREVIEW_COUNT — when a user logs in, this many recent DMs per conversation are loaded into the lobby chat so they can see previews. Set to 0 to disable previews (users still get an unread count alert).

Save: Ctrl+XYEnter

Step 9: Configure Nginx — HTTP Only First

This step is critical. A very common mistake is putting the HTTPS/SSL config in Nginx before getting the certificate. Nginx tries to load the certificate at startup — if the file doesn't exist yet, Nginx crashes in a restart loop. Certbot then cannot serve the challenge because Nginx is down. Result: no certificate, stuck in a loop.

The fix: always start with HTTP only, get the certificate, then add HTTPS.

Edit the Nginx config:

nano /opt/v4call/nginx/v4call.conf

Delete everything and replace with this HTTP-only config:

server {
    listen 80;
    server_name call.yourdomain.com www.call.yourdomain.com;

    # Certbot challenge path — do not remove this block
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # Proxy all other requests to the v4call app
    location / {
        proxy_pass         http://app:3000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout 86400;
    }
}

Replace call.yourdomain.com with your actual domain in both places.

Save: Ctrl+XYEnter

Step 10: Create Data Directories and Fix Permissions

Create the folders Docker uses for persistent data:

mkdir -p /opt/v4call/data/logs  \
mkdir -p /opt/v4call/data/certbot/conf  \
mkdir -p /opt/v4call/data/certbot/www/.well-known/acme-challenge  \
mkdir -p /opt/v4call/data/certbot/logs

Fix permissions — do not skip this.

The v4call app runs inside Docker as user node (UID 1000). On the host, these directories are created by root, so the container cannot write to them. This causes a SQLITE_CANTOPEN error that crashes the app.

Fix the logs directory for the app:

chown -R 1000:1000 /opt/v4call/data/logs

Certbot runs as root so its directories stay root-owned:

chown -R root:root /opt/v4call/data/certbot
chmod -R 755 /opt/v4call/data/certbot

Note: The app creates two separate SQLite databases in the logs directory:

  • v4call-ledger.db — payment records (calls, ring fees, payouts). Only the server writes to this.
  • v4call-chat.db — stored DMs and room messages. Separate from the ledger for security — if a bug in chat storage were exploited, the payment ledger remains untouched.

Step 11: Build and Start the Server

cd /opt/v4call
docker compose up -d --build

The first build downloads dependencies and takes 2-4 minutes. Check the status:

docker compose ps

You should see:

NAME              STATUS
v4call-app        Up (healthy)
v4call-nginx      Up

Check the app started correctly:

docker compose logs app

Look for:

[ledger] SQLite ready: /app/logs/v4call-ledger.db
[chat] SQLite ready: /app/logs/v4call-chat.db
v4call server running on 0.0.0.0:3000
[config] Server: yourcallapp (call.yourdomain.com)
[config] DM retention: 33 days | Room retention: 33 days | DM preview: 1
✓ Escrow key verified — matches @yourescrowaccount active key
✓ Escrow account @yourescrowaccount balance: 0.000 HBD

If you see SqliteError: unable to open database file — run the chown command from Step 10 again then docker compose restart app.

Test HTTP is working:

curl http://call.yourdomain.com/debug-state

Should return: {"lobbyUsers":[],"rooms":[]}

If you can see the site in a browser over HTTP at this point — everything is working and ready for the certificate.

Step 12: Get Your SSL Certificate

Before running certbot, verify DNS and the challenge path are both working:

# Check DNS points to this server
nslookup call.yourdomain.com
# Test the challenge path
echo "test" > /opt/v4call/data/certbot/www/.well-known/acme-challenge/testfile
curl http://call.yourdomain.com/.well-known/acme-challenge/testfile

The curl command must return test. If it does not, Nginx is not running — check docker compose ps and docker compose logs nginx.

When both work, get the certificate. The --entrypoint certbot flag is required — without it, Docker runs the container's default renewal loop instead of the certonly command:

docker compose run --rm \
  --entrypoint certbot \
  certbot certonly \
  --webroot \
  -w /var/www/certbot \
  -d call.yourdomain.com \
  --email your@email.com \
  --agree-tos \
  --no-eff-email

Replace call.yourdomain.com with your domain and your@email.com with your email.

Success looks like:

Requesting a certificate for call.yourdomain.com

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/call.yourdomain.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/call.yourdomain.com/privkey.pem
This certificate expires on 2026-07-09.

Verify the files exist on the host:

ls /opt/v4call/data/certbot/conf/live/call.yourdomain.com/

Should show: cert.pem chain.pem fullchain.pem privkey.pem README

Step 13: Enable HTTPS in Nginx

Now the certificate exists, update Nginx to serve HTTPS.

nano /opt/v4call/nginx/v4call.conf

Replace everything with the full HTTPS config:

# HTTP — only used for certbot challenge and redirect to HTTPS
server {
    listen 80;
    server_name v4call.com v4call.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS — auth lives here
server {
    listen 443 ssl;
    server_name v4call.com v4call.com;
# CHANGE PATH: for ssl certifcates change v4call.com to your path: which you can find at "ls data/certbot/conf/live/"
    ssl_certificate     /etc/letsencrypt/live/v4call.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/v4call.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;

    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    # When user cancels the login prompt, send them here
    error_page 401 /info.html;

    # info.html is served directly by Nginx — no auth, no proxy
    location = /info.html {
        root /usr/share/nginx/html;
        auth_basic off;
    }

    # WebSocket — requires auth
    location /socket.io/ {
# uncomment to require username and password to enter site - useful for testing
#        auth_basic           "v4call — Private Testing";
#        auth_basic_user_file /etc/nginx/.htpasswd;

        proxy_pass         http://app:3000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 86400;
    }

    # Everything else — requires auth, proxied to app
    location / {
# uncomment to require username and password to enter site - useful for testing
#        auth_basic           "v4call — Private Testing";
#        auth_basic_user_file /etc/nginx/.htpasswd;

        proxy_pass         http://app:3000;
        proxy_http_version 1.1;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout 300;
        proxy_send_timeout 300;
    }
    # This will log correct and incorrect attempts logging in, if .htpasswd used
    error_log /var/log/nginx/error.log warn;
    access_log /var/log/nginx/access.log;
}
  • REPLACE call.yourdomain.com with your domain throughout (it appears 5 times).
  • CHANGE PATH: for ssl certifcates change v4call.com to your path: which you can find at ls data/certbot/conf/live/ if not sure.
    ssl_certificate     /etc/letsencrypt/live/v4call.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/v4call.com/privkey.pem;

Save and restart Nginx:

docker compose restart nginx

Check the logs — you should see no [emerg] errors:

docker compose logs nginx

The last lines should show:

nginx/1.x.x ...
start worker processes


  • If you see an error such as:
[emerg] 1#1: cannot load certificate "/etc/letsencrypt/live/call.yourdomain.com/fullchain.pem": BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory:calling fopen(/etc/letsencrypt/live/call.yourdomain.com/fullchain.pem, r) error:10000080:BIO routines::no such file)
  • Make sure you edited nginx/v4call.conf correctly.
  • To find the path for your SSL certs look in path data/certbot/conf/live/
  • There are 4 lines to check in nginx/v4call.conf:
    • Line 4: server_name call.yourdomain.com www.call.yourdomain.com;
    • Line 18: server_name call.yourdomain.com www.call.yourdomain.com;
    • Line 20: ssl_certificate /etc/letsencrypt/live/call.yourdomain.com/fullchain.pem;
    • Line 21: ssl_certificate_key /etc/letsencrypt/live/call.yourdomain.com/privkey.pem;
  • When done restart nginx with: docker compose restart nginx

Step 14: Set Up SSL Auto-Renewal

Let's Encrypt certificates expire after 90 days. A cron job renews them automatically.

crontab -e

Add this line at the bottom:

0 3 * * * cd /opt/v4call && docker compose run --rm --entrypoint certbot certbot renew --quiet && docker compose exec nginx nginx -s reload

Save and exit. This runs at 3am every day, renews if the cert is close to expiry, and reloads Nginx to pick up the new certificate.

Step 15: Test Everything is Working

Test HTTPS:

curl https://call.yourdomain.com/debug-state

Should return: {"lobbyUsers":[],"rooms":[]}

Test admin access:

curl "https://call.yourdomain.com/admin/balance?key=YOUR_ADMIN_KEY"

Should return your escrow account balance.

Open your browser and go to https://call.yourdomain.com

You should see the v4call login page with:

  • A padlock icon in the browser address bar
  • A green ⚡ Sign in with Keychain button (if Hive Keychain is installed)
  • A manual posting key login option below it
  • A 📖 New here? Learn the v4call basics → link at the bottom

Log in with Hive Keychain or a posting key to confirm the login flow works. 🎉

Step 16: Set Up Your Call Rates on Hive

For callers to be charged when they ring you, publish your rates on the Hive blockchain:

  1. Make sure Hive Keychain is installed in your browser
  2. Go to https://call.yourdomain.com/rate-editor.html
  3. Enter your Hive username
  4. Set your rates — ring fee, connect fee, duration rate per hour, minimum credit deposit
  5. Set PLATFORM-FEE to at least your server's DEFAULT_PLATFORM_FEE percentage (e.g. 10 for 10%). If you set it lower, paid contacts to your account will be rejected on this server.
  6. Optionally add custom token sections (e.g. [TOKEN:CNOOBS]) to offer discounted rates for callers who hold your token
  7. Click Generate to preview the rates block
  8. Click Post to Hive — Keychain will ask you to approve the post

This creates a post titled v4call-rates on your Hive blog. Your server reads this post automatically.

To verify your server read the rates correctly:

https://call.yourdomain.com/debug-rates/yourusername

To test with a specific caller (checks their token balances too):

https://call.yourdomain.com/debug-rates/yourusername?caller=theirusername&type=voice

You should see your rates as JSON, including which currency and rates apply for that caller.

Feature Guide: What Your Server Can Do

This section explains all the features available in v4call and how they work. No code changes needed — everything described here is built in and ready to use.

Login Options

v4call supports two ways to sign in:

  • Hive Keychain (recommended) — click the green ⚡ Sign in with Keychain button. Keychain signs a challenge to prove your identity. No key paste needed. After login, a 🔑 panel appears in the lobby where you can optionally enter your posting key to unlock encrypted messaging (Keychain cannot expose private keys, so encryption needs the key entered once per session).
  • Manual posting key — paste your Hive posting private key (starts with 5J) directly. The key stays in browser session memory only — never sent to the server.

Both methods verify your identity against the Hive blockchain.

Voice and Video Calls

Each online user in the lobby shows three action buttons:

  • 📞 Green phone icon — start a voice-only call (audio, no camera)
  • 🎥 Blue camera icon — start a video call (audio + camera)
  • 💬 Purple chat bubble icon — open the DM panel to send a direct message

Voice calls request microphone only — no camera permission prompt. Video calls request both. The caller and callee can have different call types — the type is set by whoever initiates the call.

Separate rates can be set for voice and video calls in the rates post (voice is typically cheaper).

Direct Messages (DMs)

Click the purple 💬 button next to any online user to open the DM panel. DMs are end-to-end encrypted using Hive posting keys — the server stores only ciphertext it cannot read.

Chat storage: DMs are stored on the server in an encrypted database (v4call-chat.db) for up to DM_RETENTION_DAYS (default: 33 days). Both sender and recipient get their own encrypted copy stored, so both can retrieve their history later.

Unread alerts: When you log in, if you have unread DMs, a popup appears showing how many messages from how many users. Click a username in the popup to open their DM history.

DM previews: The last DM_PREVIEW_COUNT messages per conversation are loaded into the lobby chat on login, so you can see recent activity at a glance. Set to 0 in .env to disable.

Full history: Click the DM button for any user to load the complete conversation history, shown between "— DM history —" dividers.

Rooms

Users can create private rooms by selecting users in the lobby (toggle switch) and clicking Create & Invite. Rooms support encrypted messaging, video, and voice.

Room history: When a new user joins a room, they see past messages — broadcasts in full, encrypted messages only if they were addressed to them. A "— earlier messages —" divider separates history from live messages.

Ephemeral rooms: A warning banner at the top of every room says: "⚠ Room is ephemeral — if all users leave, the room and its history are deleted. New members can only read messages encrypted to their key." When the last person leaves a room, all stored messages for that room are deleted from the database.

Custom Token Payments (Hive-Engine)

v4call supports payment in any Hive-Engine token, not just HBD. This is configured per-user in their rates post using [TOKEN:SYMBOL] sections.

How it works:

  1. A user (e.g. @cnoobz) creates a custom token on Hive-Engine (e.g. CNOOBS)
  2. In their rates post, they add a [TOKEN:CNOOBS] section with lower rates than their default HBD rates
  3. When a caller who holds CNOOBS contacts @cnoobz, the server detects the token balance and offers the token rates
  4. If the caller holds multiple qualifying tokens, all options are shown in a currency picker — the caller chooses which to pay with
  5. The payment goes through Hive Keychain as a custom_json Hive-Engine transfer

For the escrow account: Your escrow account needs to hold some of each custom token that users on your server accept. Token payouts (to the callee) and refunds (to the caller) are sent from the escrow account's token balance, just like HBD.

Payment picker: When multiple payment options exist (e.g. CNOOBS at 1 per message, HBD at 100000 per message), the payment modal shows clickable currency buttons so the caller can see all rates and choose the best option.

Platform Fee System

The platform fee is how your server earns revenue from paid calls and DMs.

How it works:

  • Your server's DEFAULT_PLATFORM_FEE in .env is the minimum percentage your server accepts (e.g. 10 = 10%)
  • Each user sets PLATFORM-FEE in their Hive rates post — this is the maximum fee they are willing to pay to a server
  • If the user's posted fee is lower than your server's minimum → rejected. The caller sees a message explaining the mismatch, and the callee is told to raise their fee.
  • If the user's posted fee meets or exceeds your server's minimum → accepted, and the server charges its own rate (the minimum), not the user's higher number. The callee gets the best price.
  • If the user has no PLATFORM-FEE line in their rates post → the server's default is used automatically. No mismatch.
  • Free contacts (no payment involved) are never affected by fee enforcement.

Why this matters for federation: Different servers can set different platform fees. Users can shop around — pick a server with a fee they find agreeable. This creates a free market for server operators.

Example:

  • Your server: DEFAULT_PLATFORM_FEE=3 (3%)
  • @alice posts: PLATFORM-FEE: 5% → accepted, server charges 3% (best price for alice)
  • @bob posts: PLATFORM-FEE: 1% → rejected, bob needs to raise to at least 3%
  • @charlie has no fee line → defaults to 3%, automatically accepted

Admin Configuration Reference

All settings are in the .env file. After changing any value, rebuild:

docker compose down && docker compose build --no-cache && docker compose up -d
Variable Default Description
SERVER_NAME v4call Display name for your server
SERVER_DOMAIN v4call.com Your server's domain
SERVER_HIVE_ACCOUNT v4call Hive account that receives platform fees
ESCROW_ACCOUNT v4call-escrow Hive account that holds funds during calls
V4CALL_ESCROW_KEY (none) Active private key for the escrow account. Required.
ADMIN_KEY (none) Password for admin endpoints (/admin/balance, /admin/ledger)
DEFAULT_PLATFORM_FEE 10 Server's minimum platform fee percentage
DM_RETENTION_DAYS 33 Days to keep stored DMs before cleanup
ROOM_RETENTION_DAYS 33 Days to keep stored room messages before cleanup
DM_PREVIEW_COUNT 1 Recent DMs per conversation shown on login (0 = off)
HIVE_API (blank) Override primary Hive API node. Blank = auto-select from built-in list
MAX_CALL_DURATION_MIN 120 Maximum call length in minutes before auto-disconnect
CALL_COOLDOWN_MS 30000 Milliseconds between call attempts to same user
PAYMENT_VERIFY_RETRIES 3 Number of attempts to verify a blockchain payment
PAYMENT_VERIFY_DELAY_MS 5000 Delay between verification retry attempts

Debug Endpoints

These are useful for testing without making actual calls:

  • /debug-state — shows current lobby users and active rooms (no auth required)
  • /debug-rates/USERNAME — shows parsed rates for a user
  • /debug-rates/USERNAME?caller=CALLER&type=voice — shows what rates a specific caller would receive (checks token balances too)
  • /admin/balance?key=YOUR_ADMIN_KEY — shows escrow account HBD balance
  • /admin/ledger?key=YOUR_ADMIN_KEY — shows recent payment records

Updating Your Server

Important: Always use docker compose down before rebuilding. Without this step, Docker may reuse the old container even after a rebuild, and your changes will not take effect.

cd /opt/v4call
docker compose down
docker compose build --no-cache
docker compose up -d

Your data, SQLite databases and .env config are preserved — they live in data/logs/ which is a mounted volume.

To pull updates from GitHub and deploy:

cd /opt/v4call
git pull
docker compose down
docker compose build --no-cache
docker compose up -d

To push your own customisations to GitHub:

# On your local computer after making changes:
git add .
git commit -m "describe what you changed"
git push
# On the VPS:
cd /opt/v4call
git pull
docker compose down
docker compose build --no-cache
docker compose up -d

Common Problems and Fixes

Changes not showing after rebuild

If you edited server.js or index.html but changes are not visible, you probably forgot to bring Docker down first. docker compose restart and even docker compose up -d --build can reuse old containers.

Fix:

docker compose down
docker compose build --no-cache
docker compose up -d

SqliteError: unable to open database file

The app container runs as UID 1000 but the logs directory was created by root. Fix:

chown -R 1000:1000 /opt/v4call/data/logs
docker compose restart app

Certbot says "No renewals were attempted"

The certbot container's default behaviour is a renewal loop. Your certonly command is being ignored. Always use --entrypoint certbot to override it:

docker compose run --rm --entrypoint certbot certbot certonly ...

Without --entrypoint certbot the container runs its renewal script instead of your command.

Nginx crashes with "cannot load certificate: No such file"

You added the HTTPS server block before getting the certificate. Nginx reads all server blocks at startup — if the cert file doesn't exist, the entire process fails.

Fix: revert nginx config to HTTP-only (Step 9), restart Nginx, get the certificate (Step 12), then re-add HTTPS (Step 13).

Webroot challenge test returns nothing

Nginx is not running. Check:

docker compose ps
docker compose logs nginx

If Nginx is restarting — it has the HTTPS config with a missing cert file. Use the HTTP-only config.

"Escrow key does NOT match" warning on startup

The key in .env is the wrong type. You need the active private key, not the posting or owner key. Find it in your Hive wallet → Keys & Permissions → Active. It starts with 5K.

Site unreachable on port 443

Check that the certificate was issued:

ls /opt/v4call/data/certbot/conf/live/

Should show a folder with your domain name. If empty — go back to Step 12.

npm ci error during Docker build

The npm ci command requires a package-lock.json file. The project uses npm install instead. If you see this error, check your Dockerfile — it should say npm install not npm ci.

Custom token payments not working

If token rates are detected but payments fail:

  • Check the escrow account holds the token — send some tokens to your escrow account on Hive-Engine
  • Check the token symbol matches exactly (case-sensitive) between the rates post and Hive-Engine
  • Check the server logs: docker compose logs app | grep -i "token\|cnoobs\|escrow-token"
  • The Hive-Engine API endpoint must be https://api.hive-engine.com/rpc/contracts — this is built into the code

[encrypted — unlock with 🔑 key panel to read]

You logged in with Hive Keychain. Keychain does not expose private keys, so encrypted messages cannot be decrypted without your posting key. Enter your posting key in the 🔑 panel at the bottom of the online users list. The key stays in browser session memory only — it is needed once per session.

Quick Reference

Command What it does
docker compose ps Show status of all containers
docker compose logs app Show app logs
docker compose logs nginx Show Nginx logs
docker compose logs -f app Watch live logs (Ctrl+C to stop)
docker compose down Stop everything (always do this before rebuilding)
docker compose build --no-cache Rebuild without using cached layers
docker compose up -d Start everything in background
docker compose down && docker compose build --no-cache && docker compose up -d Full rebuild cycle (use after any code change)
docker compose restart nginx Restart Nginx after config-only changes (no rebuild needed)
chown -R 1000:1000 /opt/v4call/data/logs Fix SQLite write permissions
docker compose run --rm --entrypoint certbot certbot certificates List SSL certificates
curl http://yourdomain.com/.well-known/acme-challenge/testfile Test certbot webroot works
escrow" Check token payment logs

Optional: Password Protect Your Server During Testing

Optional: Password Protect Your Server During Testing (HTTP Basic Auth)

During development and testing you may want to restrict access so only people you invite can use your server. This uses Nginx HTTP Basic Auth — a simple username and password prompt that appears before the v4call login screen.

When a visitor cancels the login prompt they are shown a public info.html page where they can read about the project and request access.

  • Step 1 — Install the htpasswd tool

apt install -y apache2-utils

  • Step 2 — Create the password file and add your first user

htpasswd -c /opt/v4call/nginx/.htpasswd yourusername

You will be prompted to enter and confirm a password. The -c flag creates the file. Do not use -c again or it will overwrite the file and delete existing users.

To add more users later:

htpasswd /opt/v4call/nginx/.htpasswd anotherusername

To remove a user:

htpasswd -D /opt/v4call/nginx/.htpasswd username

  • Step 3 — Create the public info page

nano /opt/v4call/public/info.html

Paste your HTML content — a page explaining the project and how to request access. This page is served publicly without a password so visitors who cancel the login prompt can still read it.

  • Step 4 — Mount the files into the Nginx container

Edit docker-compose.yml and add two lines to the nginx volumes section:

  nginx:
    volumes:
      - ./nginx/v4call.conf:/etc/nginx/conf.d/default.conf:ro
      - ./nginx/.htpasswd:/etc/nginx/.htpasswd:ro          # add this
      - ./public/info.html:/usr/share/nginx/html/info.html:ro  # add this
      - ./data/certbot/conf:/etc/letsencrypt:ro
      - ./data/certbot/www:/var/www/certbot:ro
  • Step 5 — Update your Nginx config

Add auth to the HTTPS server block (not the HTTP block — users are redirected to HTTPS so auth must live there). Add these lines to both the /socket.io/ and / location blocks, and add the error_page and info.html location blocks:

server {
    listen 443 ssl;
    server_name call.yourdomain.com www.call.yourdomain.com;

    # ... ssl_certificate lines stay the same ...

    # Send cancelled logins to the public info page
    error_page 401 /info.html;

    # info.html is served by Nginx directly — no auth, no proxy
    location = /info.html {
        root /usr/share/nginx/html;
        auth_basic off;
    }

    # WebSocket — requires auth
    location /socket.io/ {
        auth_basic           "v4call — Private Testing";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://app:3000;
        # ... rest of proxy headers stay the same ...
    }

    # All other requests — requires auth
    location / {
        auth_basic           "v4call — Private Testing";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://app:3000;
        # ... rest of proxy headers stay the same ...
    }
}
  • Note: if you want to log successful and unsuccessful login attempts, add:
    error_log /var/log/nginx/error.log warn;
    access_log /var/log/nginx/access.log;
  • Step 6 — Recreate the Nginx container to pick up the new volume mounts

Important: docker compose restart nginx is not enough — it reuses the old container and ignores new volume mounts. You must recreate it:

docker compose down && docker compose up -d

After this, verify the files are mounted inside the container:

docker compose exec nginx ls /usr/share/nginx/html/

You should see info.html listed alongside 50x.html and index.html.

  • Step 7 — Test it works

curl -o /dev/null -s -w "%{http_code}" https://call.yourdomain.com/

Should return 401 (login required).

curl -o /dev/null -s -w "%{http_code}" https://call.yourdomain.com/info.html

Should return 200 (public, no auth needed).

curl -u youruser:yourpassword -o /dev/null -s -w "%{http_code}" https://call.yourdomain.com/

Should return 200 (correct credentials accepted).

  • Managing users without restarting

After adding or removing users from the .htpasswd file, reload Nginx config without downtime:

docker compose exec nginx nginx -s reload

No restart needed — Nginx re-reads the .htpasswd file on every request anyway.

  • Keep .htpasswd out of GitHub

The password file should never be committed to your repository. Add it to .gitignore:

echo "nginx/.htpasswd" >> /opt/v4call/.gitignore

  • Removing auth when you go public

When you are ready to open your server to everyone, simply remove the auth_basic lines from nginx/v4call.conf and reload:

docker compose exec nginx nginx -s reload

No other changes needed.

How to check if anyone tried to log in (failed attempts) or successfully logged in with .htpasswd on Nginx in Docker

Important Tip: Always run these commands in the same folder where your docker-compose.yml file is located. If you are in the wrong directory the commands will not find your container and nothing will show up.

  • Basic Commands
See everything live (good while testing)
docker compose logs -f nginx
-f means "follow/live" – new log lines appear automatically. Remove -f if you only want to read the current logs once and stop.
Best command to watch failed login attempts (people guessing passwords)
docker compose logs -f nginx | grep -E "mismatch|not found|401"
See who successfully logged in
docker compose logs nginx | grep "remote_user"
Combined view – most useful for daily checking (failed + successful)
docker compose logs -f --tail=100 nginx | grep -E "(mismatch|not found|remote_user|401)"
  • What the Logs Look Like

Failed attempt (wrong password):

2026/04/17 01:23:45 [error] ... user "admin": password mismatch, client: 185.123.45.67, ...

Failed attempt (wrong username):

2026/04/17 01:24:12 [error] ... user "hacker123" was not found in "/etc/nginx/.htpasswd", client: 45.67.89.10, ...

Successful login:

... "GET /protected/ HTTP/1.1" 200 ... remote_user: "myuser" ...
  • How to Customise These Commands
  • Change nginx to the exact name of your service if it is different in docker-compose.yml.
  • Remove -f to read the full log once without live following.
  • Change --tail=100 to --tail=500 (or any number) to show more or fewer old lines.
  • Add or remove words in the grep part to filter differently.
 Examples:
 * Only failed attempts: grep -E "mismatch|not found"
 * Only 401 errors: grep " 401 "
 * Everything auth related: grep -E "(auth|password|mismatch|not found|remote_user)"
  • Quick Copy-Paste Commands
  1. Watch failed guesses live

docker compose logs -f nginx | grep -E "mismatch|not found|401"

  1. Check successful logins

docker compose logs nginx | grep "remote_user"

  1. Combined quick check (recommended)

docker compose logs -f --tail=100 nginx | grep -E "(mismatch|not found|remote_user|401)"