Compare commits

..
16 Commits
Author SHA1 Message Date
nobswebdev 3a0291d450 Broaden hero headline for general crypto merchants. 2026-09-19 16:43:46 +02:00
nobswebdev 7871f9beb7 Tweak managed setup and contact section copy. 2026-09-19 15:28:58 +02:00
nobswebdev 624a3e4bbd Add Get started section for self-hosted and managed setup.
Replace How it works with tier cards, nav link, and managed setup pricing copy.
2026-09-19 15:18:28 +02:00
nobswebdev cd3262d016 Describe NullCart buyer shop as working without JavaScript. 2026-09-19 14:18:22 +02:00
nobswebdev 5f4e16bbb0 Fold privacy pillars into Privacy by design and tidy features grid.
Remove the standalone privacy section, pair e-commerce with privacy in the two-column layout, and drop unused pillar styles.
2026-09-19 14:00:07 +02:00
nobswebdev 1acb669f61 Highlight shop blog in e-commerce features on promo site. 2026-09-19 13:47:07 +02:00
nobswebdev ca4d4b5682 Add community Instances section and refresh hero styling.
List hosted shops with local logos and clearnet or Tor links; remove hero and lead-card gradient decorations while keeping lead card accents.
2026-09-18 15:26:29 +02:00
nobswebdev 88d6d9ddef add hero mockup 2026-09-12 21:09:40 +02:00
nobswebdev ebfe68c132 Add Onion-Location header for Tor Browser discovery.
Mirror NullCart's ONION_HOSTNAME env and nginx clearnet header so visitors on HTTPS see ".onion available".
2026-09-11 16:27:36 +02:00
nobswebdev 169bbb8bad remove redundant step from setup 2026-09-10 13:29:21 +02:00
nobswebdev d7733afdad Document VPS production deployment in README.
Add copy-paste deploy steps matching the NullCart guide, including TLS bootstrap, Let's Encrypt, Tor onion, and updates.
2026-09-10 12:48:23 +02:00
nobswebdev b57db5f1ac Add production Docker stack with nginx and Tor.
Mirror NullCart's clearnet HTTPS and onion hidden-service pattern for serving the static promo site on a VPS.
2026-09-10 12:48:17 +02:00
nobswebdev edbc591a04 tinify storefront image 2026-09-07 15:55:23 +02:00
nobswebdev d91c03002b Improve hero layout, gallery grid, and anchor scroll offset.
Stack hero actions in a row, center the fifth gallery item, and offset in-page navigation for the sticky header.
2026-09-07 15:43:38 +02:00
nobswebdev 8d201527a9 Rebrand promo site as a crypto shop and refresh page copy.
Position NullCart as a multi-crypto self-hosted shop, expand the gallery to five screens, and tighten wording across hero, features, and roadmap sections.
2026-09-07 15:43:31 +02:00
nobswebdev 66d5744418 Update gallery screenshots and add cart image.
Refresh product screenshots for the multi-coin storefront and add the shopping cart screen to the gallery.
2026-09-07 15:43:27 +02:00
25 changed files with 1140 additions and 688 deletions
+3
View File
@@ -0,0 +1,3 @@
COMPOSE_PROJECT_NAME=nullcart_promo_prod
CLEARNET_DOMAIN=nullcart.net
ONION_HOSTNAME=samplexxxxxxxxx.onion
+3
View File
@@ -0,0 +1,3 @@
.env.prod
/deploy/certs
/deploy/certbot/www
+129 -1
View File
@@ -1,3 +1,131 @@
# NullCart Promo Website
Static promo landing page for [NullCart](https://git.nobswebdev.com/nobswebdev/nullcart) — HTML and CSS only, zero JavaScript.
Static promo landing page for [NullCart](https://git.nobswebdev.com/nobswebdev/nullcart) - HTML and CSS only, zero JavaScript.
## Development
No Docker required locally. Open `index.html` in your browser.
## Production
### 1. Requirements
- Ubuntu 22.04+ or similar Linux with Docker Engine and the Compose plugin — follow [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository)
- A domain name pointing at your server (A records for apex and `www`)
### 2. Server setup
Deploy as **root** on the VPS.
Verify:
```bash
docker compose version
```
### 3. Clone the repository
```bash
cd /root
git clone https://git.nobswebdev.com/nobswebdev/nullcart_promo_website.git nullcart_promo_website
cd nullcart_promo_website
```
### 4. Configure environment
```bash
cd /root/nullcart_promo_website
cp .env.example .env.prod
chmod 600 .env.prod
```
Edit `.env.prod`:
| Variable | Production value |
| ---------------------- | ------------------------------ |
| `COMPOSE_PROJECT_NAME` | `nullcart_promo_prod` |
| `CLEARNET_DOMAIN` | apex only, e.g. `nullcart.net` |
### 5. Bootstrap TLS certificates
Nginx needs certificate files before it can start on port 443. For the **first** deploy, create a temporary self-signed pair (replaced after Let's Encrypt):
```bash
./deploy/scripts/bootstrap-certs.sh
```
After the stack is running, obtain real certificates (step 7).
### 6. Start the stack
```bash
./deploy/scripts/deploy.sh
```
Check containers:
```bash
docker compose --env-file .env.prod -f docker-compose.prod.yml ps
```
### 7. Issue Let's Encrypt certificates
Remove the temporary bootstrap certificates under `deploy/certs/live/` (Certbot cannot issue into the layout created by `bootstrap-certs.sh`):
```bash
rm -rf deploy/certs/live/*
```
Request the real certificate (apex + www):
```bash
./deploy/scripts/issue-certs.sh --email you@example.com
```
Reload nginx:
```bash
docker compose --env-file .env.prod -f docker-compose.prod.yml exec nginx nginx -s reload
```
#### Automatic renewal
Open root's crontab:
```bash
crontab -e
```
Add a weekly job (`/root/nullcart_promo_website` is the standard deploy path):
```cron
0 3 * * 0 /root/nullcart_promo_website/deploy/scripts/renew-certs.sh >> /var/log/nullcart-promo-cert-renew.log 2>&1
```
Save and exit the editor. Optional — run once manually to verify:
```bash
/root/nullcart_promo_website/deploy/scripts/renew-certs.sh
```
### 8. Tor onion address
```bash
./deploy/scripts/show-onion.sh
```
Copy the hostname into `.env.prod` as `ONION_HOSTNAME`, then recreate nginx:
```bash
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d --force-recreate nginx
```
Tor Browser will then show ".onion available" when visitors open the clearnet site over HTTPS.
### 9. Updates
```bash
./deploy/scripts/update.sh
```
This pulls the latest code and rebuilds the stack (`deploy.sh`).
Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

+496 -475
View File
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ENV_FILE="${ROOT_DIR}/.env.prod"
cd "$ROOT_DIR"
if [[ ! -f "$ENV_FILE" ]]; then
echo "Missing ${ENV_FILE}. Copy .env.example to .env.prod and configure it." >&2
exit 1
fi
# shellcheck disable=SC1090
set -a
source "$ENV_FILE"
set +a
if [[ -z "${CLEARNET_DOMAIN:-}" ]]; then
echo "CLEARNET_DOMAIN is not set in .env.prod" >&2
exit 1
fi
LIVE_DIR="${ROOT_DIR}/deploy/certs/live/${CLEARNET_DOMAIN}"
if [[ -f "${LIVE_DIR}/fullchain.pem" ]]; then
echo "Certificates already exist at deploy/certs/live/${CLEARNET_DOMAIN}" >&2
exit 1
fi
mkdir -p "$LIVE_DIR"
openssl req -x509 -nodes -newkey rsa:2048 -days 1 \
-keyout "${LIVE_DIR}/privkey.pem" \
-out "${LIVE_DIR}/fullchain.pem" \
-subj "/CN=${CLEARNET_DOMAIN}"
echo "Temporary self-signed certificates created at deploy/certs/live/${CLEARNET_DOMAIN}"
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ENV_FILE="${ROOT_DIR}/.env.prod"
cd "$ROOT_DIR"
if [[ ! -f "$ENV_FILE" ]]; then
echo "Missing ${ENV_FILE}. Copy .env.example to .env.prod and configure it." >&2
exit 1
fi
docker compose --env-file "$ENV_FILE" -f docker-compose.prod.yml up -d --build
echo "Pruning unused Docker data older than 24h..."
docker system prune -af --filter "until=24h"
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ENV_FILE="${ROOT_DIR}/.env.prod"
CERTBOT_EMAIL=""
usage() {
cat <<EOF
Usage: $(basename "$0") --email you@example.com
Obtain Let's Encrypt certificates for CLEARNET_DOMAIN and www.CLEARNET_DOMAIN using
the webroot challenge. Nginx must be running and serving /.well-known/acme-challenge/
from deploy/certbot/www.
Environment is read from .env.prod (CLEARNET_DOMAIN).
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--email)
CERTBOT_EMAIL="$2"
shift 2
;;
-h | --help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 1
;;
esac
done
if [[ ! -f "$ENV_FILE" ]]; then
echo "Missing ${ENV_FILE}" >&2
exit 1
fi
# shellcheck disable=SC1090
set -a
source "$ENV_FILE"
set +a
if [[ -z "${CLEARNET_DOMAIN:-}" ]]; then
echo "CLEARNET_DOMAIN is not set in .env.prod" >&2
exit 1
fi
if [[ -z "$CERTBOT_EMAIL" ]]; then
echo "Pass --email for Let's Encrypt registration." >&2
usage >&2
exit 1
fi
mkdir -p "${ROOT_DIR}/deploy/certbot/www" "${ROOT_DIR}/deploy/certs"
docker run --rm \
-v "${ROOT_DIR}/deploy/certbot/www:/var/www/certbot" \
-v "${ROOT_DIR}/deploy/certs:/etc/letsencrypt" \
certbot/certbot certonly \
--webroot \
-w /var/www/certbot \
-d "$CLEARNET_DOMAIN" \
-d "www.${CLEARNET_DOMAIN}" \
--email "$CERTBOT_EMAIL" \
--agree-tos \
--non-interactive
if [[ ! -f "${ROOT_DIR}/deploy/certs/live/${CLEARNET_DOMAIN}/fullchain.pem" ]]; then
echo "Expected certificates at deploy/certs/live/${CLEARNET_DOMAIN}" >&2
exit 1
fi
echo "Certificates issued at deploy/certs/live/${CLEARNET_DOMAIN}"
echo "Reload nginx: docker compose --env-file .env.prod -f docker-compose.prod.yml exec nginx nginx -s reload"
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ENV_FILE="${ROOT_DIR}/.env.prod"
cd "$ROOT_DIR"
if [[ ! -f "$ENV_FILE" ]]; then
echo "Missing ${ENV_FILE}" >&2
exit 1
fi
mkdir -p "${ROOT_DIR}/deploy/certbot/www" "${ROOT_DIR}/deploy/certs"
docker run --rm \
-v "${ROOT_DIR}/deploy/certbot/www:/var/www/certbot" \
-v "${ROOT_DIR}/deploy/certs:/etc/letsencrypt" \
certbot/certbot renew \
--webroot \
-w /var/www/certbot
docker compose --env-file "$ENV_FILE" -f docker-compose.prod.yml exec nginx nginx -s reload
echo "Certificate renewal complete; nginx reloaded."
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ENV_FILE="${ROOT_DIR}/.env.prod"
cd "$ROOT_DIR"
if [[ ! -f "$ENV_FILE" ]]; then
echo "Missing ${ENV_FILE}" >&2
exit 1
fi
docker compose --env-file "$ENV_FILE" -f docker-compose.prod.yml exec tor \
cat /var/lib/tor/hs/hostname
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"
git pull
"${ROOT_DIR}/deploy/scripts/deploy.sh"
+7
View File
@@ -0,0 +1,7 @@
FROM alpine:3.20
RUN apk add --no-cache tor
COPY torrc /etc/tor/torrc
CMD ["tor", "-f", "/etc/tor/torrc"]
+5
View File
@@ -0,0 +1,5 @@
SocksPort 0
Log notice stdout
HiddenServiceDir /var/lib/tor/hs/
HiddenServicePort 80 nginx:8080
+29
View File
@@ -0,0 +1,29 @@
services:
nginx:
build:
context: .
dockerfile: nginx/Dockerfile.prod
container_name: ${COMPOSE_PROJECT_NAME}_nginx
restart: unless-stopped
env_file:
- .env.prod
ports:
- '80:80'
- '443:443'
volumes:
- ./deploy/certs:/etc/nginx/certs:ro
- ./deploy/certbot/www:/var/www/certbot:ro
tor:
build:
context: ./deploy/tor
container_name: ${COMPOSE_PROJECT_NAME}_tor
restart: unless-stopped
volumes:
- nullcart_promo.tor.prod.data:/var/lib/tor
depends_on:
nginx:
condition: service_started
volumes:
nullcart_promo.tor.prod.data:
+211 -212
View File
@@ -5,9 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="NullCart — free, self-hosted Monero shop. No JS, no buyer accounts. Sell on clearnet and Tor."
content="NullCart - free, self-hosted, privacy-focused crypto shop. Sell on clearnet and Tor."
/>
<title>NullCart — Free self-hosted Monero shop</title>
<title>NullCart - free self-hosted crypto shop</title>
<link rel="icon" href="assets/favicon.png" type="image/png" />
<link rel="stylesheet" href="css/styles.css" />
</head>
@@ -38,10 +38,12 @@
</a>
<nav class="site-nav" aria-label="Main">
<a href="#features">Features</a>
<a href="#get-started">Get started</a>
<a href="#gallery">Gallery</a>
<a href="#demo">Live demo</a>
<a href="#source">Open source</a>
<a href="#roadmap">Roadmap</a>
<a href="#instances">Instances</a>
<a href="#contact">Contact</a>
</nav>
</div>
@@ -49,70 +51,25 @@
<main>
<section class="hero">
<div class="container">
<img
class="hero__watermark"
src="assets/favicon.png"
width="463"
height="539"
alt=""
aria-hidden="true"
/>
<div class="container hero__inner">
<div class="hero__content">
<p class="hero__badge">Free &amp; open source</p>
<h1>Self-hosted Monero shop</h1>
<ul class="hero__highlights" aria-label="Key features">
<li>Non-custodial</li>
<li>No JS</li>
<li>No accounts</li>
<li>Clearnet &amp; Tor onion</li>
</ul>
<h1>Self-hosted crypto shop for your business</h1>
<p class="hero__sub">
Complete crypto e-commerce solution, with no third parties
involved. Built with a mission to support the crypto economy.
Complete, privacy-focused crypto e-commerce solution. Built with a
mission to grow the crypto economy.
</p>
<div class="hero__actions">
<div class="link-group">
<span class="link-group__label">Repository</span>
<div class="repo-links">
<a
class="btn btn--primary"
href="https://git.nobswebdev.com/nobswebdev/nullcart"
target="_blank"
rel="noopener noreferrer"
>Clearnet</a
>
<a
class="btn btn--ghost"
href="http://4f5t7o2vgo4sdaliwax3isyxzfuxvbvkuwk4smfowutrvx2uneu4egyd.onion/nobswebdev/nullcart"
target="_blank"
rel="noopener noreferrer"
>Tor</a
>
</div>
</div>
<div class="link-group">
<span class="link-group__label">Live demo</span>
<div class="repo-links">
<a
class="btn btn--primary"
href="https://demo.nullcart.net/"
target="_blank"
rel="noopener noreferrer"
>Clearnet</a
>
<a
class="btn btn--ghost"
href="http://3jh2ltksxtsmjidik3st6dawfdcheo2cyv2vth2365af75zdxnwqqsyd.onion/"
target="_blank"
rel="noopener noreferrer"
>Tor</a
>
</div>
</div>
<a class="btn btn--ghost" href="#features">See features</a>
<a class="btn btn--primary" href="#features">See features</a>
</div>
</div>
<img
class="hero__visual"
src="assets/hero_mockup.png"
width="1024"
height="562"
alt="NullCart storefront on desktop and mobile"
/>
</div>
</section>
@@ -120,72 +77,12 @@
<div class="container about">
<h2 class="section__title">What is NullCart?</h2>
<p>
NullCart is an open-source, self-hosted shop for accepting Monero.
Run it on your server with a non-custodial wallet, and sell on
clearnet and Tor.
NullCart is a free, open-source crypto shop you host yourself. You
run it on your server with built-in non-custodial wallets - no
payment processors in the middle. Buyers pay in crypto without
accounts, and the storefront works without JavaScript. You manage
products and orders from a merchant CMS, on clearnet and Tor.
</p>
<p>
Built for the Monero community — designed for a maximum privacy
experience and full control over funds and infrastructure. Lower the
barrier to selling with XMR and help grow the circular crypto
economy.
</p>
</div>
</section>
<section class="section" id="privacy">
<div class="container">
<h2 class="section__title">Privacy features</h2>
<p class="section__lead">
Built for the Monero community — privacy at every layer of the buyer
experience.
</p>
<ul class="pillars privacy-features">
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> Free and open
source
</li>
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> No JS on
buyer shop
</li>
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> No buyer
accounts
</li>
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> Non-custodial
wallet
</li>
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> XMR-only
payments
</li>
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> Zero
third-party processors
</li>
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> Own wallet
integration
</li>
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> Encrypted
sensitive data
</li>
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> Auto order
data wipe
</li>
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> Self-hosted
clearnet + onion
</li>
<li class="pillar">
<span class="pillar__dot" aria-hidden="true"></span> SimpleX
merchant alerts
</li>
</ul>
</div>
</section>
@@ -193,7 +90,7 @@
<div class="container">
<h2 class="section__title">Features</h2>
<p class="section__lead">
Everything you need to sell digital and physical goods with Monero.
Everything you need to sell digital and physical goods with crypto.
</p>
<div class="features-grid">
<article class="feature-card feature-card--lead">
@@ -209,46 +106,22 @@
</svg>
<h3>Privacy by design</h3>
<p class="feature-card__lead">
Your buyers don't need accounts. Your shop doesn't hoard their
data.
Privacy at every layer of the buyer experience. Your buyers
don't need accounts, and your shop doesn't hoard their data.
</p>
<ul>
<li>Free and open source</li>
<li>Buyer shop works without JavaScript</li>
<li>Works in Tor Browser strictest security mode</li>
<li>
No JavaScript needed — the buyer shop runs without client-side
scripts
No buyer accounts - orders open with a private access token
</li>
<li>Works perfectly in Tor Browser strictest security mode</li>
<li>
No sign-up, no profiles — orders opened with a private access
token
</li>
</ul>
</article>
<article class="feature-card">
<svg
class="feature-card__icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 6v12M8 10h8M8 14h8" />
</svg>
<h3>Monero-native payments</h3>
<p class="feature-card__lead">
Built-in Monero integration. Zero third-party payment services.
</p>
<ul>
<li>
Direct XMR payments to your own wallet — no custodial gateway
</li>
<li>
Per-order addresses with QR codes; live rates, fiat display
</li>
<li>You hold the seed — funds never touch a middleman</li>
<li>Non-custodial shop wallets</li>
<li>Monero supported</li>
<li>Encrypted sensitive data at rest</li>
<li>Automatic order data wipe</li>
<li>Self-hosted clearnet and Tor onion</li>
<li>SimpleX merchant alerts</li>
</ul>
</article>
@@ -266,7 +139,7 @@
</svg>
<h3>E-commerce features</h3>
<p class="feature-card__lead">
A full storefront for digital and physical goods — catalog,
A full storefront for digital and physical goods - catalog,
cart, checkout, and fulfillment.
</p>
<ul>
@@ -275,16 +148,48 @@
</li>
<li>Shopping cart, checkout, and discount codes</li>
<li>
Auto-deliver digital goods; quote and collect shipping for
physical orders
Auto-deliver digital goods and quote shipping for physical
orders
</li>
<li>
Order tracking, live payment status, and per-order buyer chat
</li>
<li>Custom branding — your logo, favicon, and shop name</li>
<li>Custom branding - your logo, favicon, and shop name</li>
<li>
Prices shown in fiat, paid in Monero with live exchange rates
Shop blog for product spotlights, how-tos, and SEO-friendly
content
</li>
<li>
Prices shown in fiat, paid in crypto with live exchange rates
</li>
</ul>
</article>
<article class="feature-card">
<svg
class="feature-card__icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 6v12M8 10h8M8 14h8" />
</svg>
<h3>Direct crypto payments</h3>
<p class="feature-card__lead">
Built-in crypto wallets. No third-party payment services.
</p>
<ul>
<li>
Payments go straight to your wallets - no custodial gateway
</li>
<li>
Per-order addresses with QR codes, live rates, and fiat
display
</li>
<li>You hold the keys - funds never touch a middleman</li>
</ul>
</article>
@@ -317,32 +222,42 @@
</div>
</section>
<section class="section">
<section class="section section--border" id="get-started">
<div class="container">
<h2 class="section__title">How it works</h2>
<h2 class="section__title">Get started</h2>
<p class="section__lead">
Three steps from zero to selling with Monero.
Launch on your own server: install from the guide, or get one-time
help if you are not comfortable with VPS and DNS yourself.
</p>
<ol class="steps">
<li class="step">
<h3>Deploy</h3>
<div class="tiers">
<article class="tier-card">
<h3>Self-hosted</h3>
<p>
Clone the repo, configure your server, and go live on HTTPS and
Tor.
Full NullCart is free and open source. Use the deployment guide
in the repository to install on your VPS with HTTPS and Tor. You
handle provisioning, DNS, and updates.
</p>
</li>
<li class="step">
<h3>Set up your shop</h3>
<p>Add products in the CMS and upload your branding.</p>
</li>
<li class="step">
<h3>Sell</h3>
<a
class="btn btn--primary"
href="https://git.nobswebdev.com/nobswebdev/nullcart"
target="_blank"
rel="noopener noreferrer"
>Repository</a
>
</article>
<article class="tier-card">
<h3>Managed setup</h3>
<p>
Buyers pay in XMR; you fulfill orders, chat when needed, get
notified via SimpleX.
We will setup everything for you if you are not comfortable with
servers yourself or you want to hand over this task to us.
<strong>$25 USD</strong> paid in Monero covers our help to make
your shop up and running. VPS and domain are paid separately.
Optional upgrade to the latest version later:
<strong>$25 USD</strong> in Monero if you ever need it.
</p>
</li>
</ol>
<a class="btn btn--primary" href="#contact">Contact</a>
</article>
</div>
</div>
</section>
@@ -362,14 +277,33 @@
<div class="gallery__frame">
<img
src="assets/gallery/storefront.png"
width="1508"
height="902"
width="1469"
height="899"
alt="Storefront product catalog"
/>
</div>
</a>
<figcaption class="gallery__caption">
Storefront — product catalog
Storefront - product catalog
</figcaption>
</figure>
<figure class="gallery__item">
<a
class="gallery__link"
href="#lightbox-cart"
aria-label="View larger cart screenshot"
>
<div class="gallery__frame">
<img
src="assets/gallery/cart.png"
width="1469"
height="899"
alt="Shopping cart"
/>
</div>
</a>
<figcaption class="gallery__caption">
Cart - shopping cart
</figcaption>
</figure>
<figure class="gallery__item">
@@ -381,14 +315,14 @@
<div class="gallery__frame">
<img
src="assets/gallery/checkout.png"
width="1508"
height="902"
alt="Checkout with Monero QR code"
width="1469"
height="899"
alt="Checkout with crypto QR code"
/>
</div>
</a>
<figcaption class="gallery__caption">
Checkout — XMR payment with QR code
Checkout - crypto payment with QR code
</figcaption>
</figure>
<figure class="gallery__item">
@@ -400,17 +334,17 @@
<div class="gallery__frame">
<img
src="assets/gallery/order.png"
width="1508"
height="902"
width="1469"
height="899"
alt="Order page with encrypted chat"
/>
</div>
</a>
<figcaption class="gallery__caption">
Order page — status and encrypted chat
Order page - status and encrypted chat
</figcaption>
</figure>
<figure class="gallery__item">
<figure class="gallery__item gallery__item--last">
<a
class="gallery__link"
href="#lightbox-cms"
@@ -446,8 +380,8 @@
<img
class="lightbox__img"
src="assets/gallery/storefront.png"
width="1508"
height="902"
width="1469"
height="899"
alt="Storefront product catalog"
/>
<a class="lightbox__close" href="#gallery" aria-label="Close"
@@ -459,7 +393,7 @@
class="lightbox"
role="dialog"
aria-modal="true"
aria-label="Checkout with Monero QR code"
aria-label="Checkout with crypto QR code"
>
<a
class="lightbox__backdrop"
@@ -469,9 +403,32 @@
<img
class="lightbox__img"
src="assets/gallery/checkout.png"
width="1508"
height="902"
alt="Checkout with Monero QR code"
width="1469"
height="899"
alt="Checkout with crypto QR code"
/>
<a class="lightbox__close" href="#gallery" aria-label="Close"
>&times;</a
>
</div>
<div
id="lightbox-cart"
class="lightbox"
role="dialog"
aria-modal="true"
aria-label="Shopping cart"
>
<a
class="lightbox__backdrop"
href="#gallery"
aria-label="Close"
></a>
<img
class="lightbox__img"
src="assets/gallery/cart.png"
width="1469"
height="899"
alt="Shopping cart"
/>
<a class="lightbox__close" href="#gallery" aria-label="Close"
>&times;</a
@@ -492,8 +449,8 @@
<img
class="lightbox__img"
src="assets/gallery/order.png"
width="1508"
height="902"
width="1469"
height="899"
alt="Order page with encrypted chat"
/>
<a class="lightbox__close" href="#gallery" aria-label="Close"
@@ -531,8 +488,8 @@
<div class="cta-block">
<h2>Live demo</h2>
<p>
Try a running NullCart shop — browse products, cart, and checkout
flow. Test payments with stagenet Monero.
Try a running NullCart shop. Browse products, cart, and checkout.
Test payments on test networks.
</p>
<div class="repo-links repo-links--center">
<a
@@ -593,10 +550,10 @@
</p>
</li>
<li class="roadmap__item">
<h3>More privacy coins</h3>
<h3>More cryptocurrencies</h3>
<p>
Support for additional privacy-focused cryptocurrencies beyond
Monero.
Support for more coins over time, with the same non-custodial
wallet model.
</p>
</li>
<li class="roadmap__item">
@@ -607,12 +564,52 @@
</div>
</section>
<section class="section section--border" id="instances">
<div class="container">
<h2 class="section__title">Instances</h2>
<p class="section__lead">NullCart shops hosted by the community.</p>
<ul class="instances-list">
<li>
<article class="instance-card">
<img
class="instance-card__logo"
src="assets/instances_logos/accsnake.png"
width="248"
height="40"
alt="Accsnake"
/>
<div class="instance-card__body">
<h3 class="instance-card__name">Accsnake</h3>
<p class="instance-card__desc">
Ready-made accounts merchant.
</p>
<div class="repo-links">
<a
class="btn btn--primary"
href="https://accsnake.com/"
target="_blank"
rel="noopener noreferrer"
>Clearnet</a
>
<a
class="btn btn--ghost"
href="http://itj7v5hhp6cohgkeuy6ijuxj7f3gak6may6glwx3z27l32lbfxktmtyd.onion/"
target="_blank"
rel="noopener noreferrer"
>Tor</a
>
</div>
</div>
</article>
</li>
</ul>
</div>
</section>
<section class="section section--border" id="contact">
<div class="container">
<h2 class="section__title">Contact</h2>
<p class="section__lead">
Questions, feedback, or collaboration — reach out.
</p>
<p class="section__lead">Need help? Have questions? Reach out.</p>
<div class="contact-grid">
<div class="contact-card">
<h3>Email</h3>
@@ -637,10 +634,12 @@
<p class="site-footer__copy">&copy; 2026 NullCart by nobswebdev.</p>
<nav class="site-footer__nav" aria-label="Footer">
<a href="#features">Features</a>
<a href="#get-started">Get started</a>
<a href="#gallery">Gallery</a>
<a href="#demo">Demo</a>
<a href="#source">Open source</a>
<a href="#roadmap">Roadmap</a>
<a href="#instances">Instances</a>
<a href="#contact">Contact</a>
</nav>
<div class="site-footer__links">
+15
View File
@@ -0,0 +1,15 @@
FROM nginx:alpine
RUN apk add --no-cache gettext \
&& rm /etc/nginx/conf.d/default.conf
COPY index.html /usr/share/nginx/html/
COPY css/ /usr/share/nginx/html/css/
COPY assets/ /usr/share/nginx/html/assets/
COPY nginx/conf.d/ /etc/nginx/templates/conf.d/
COPY nginx/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
+29
View File
@@ -0,0 +1,29 @@
server {
listen 80;
server_name ${CLEARNET_DOMAIN} www.${CLEARNET_DOMAIN};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name ${CLEARNET_DOMAIN} www.${CLEARNET_DOMAIN};
ssl_certificate /etc/nginx/certs/live/${CLEARNET_DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/live/${CLEARNET_DOMAIN}/privkey.pem;
${ONION_LOCATION_HEADER}
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
+11
View File
@@ -0,0 +1,11 @@
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
set -eu
export CLEARNET_DOMAIN
if [ -n "${ONION_HOSTNAME:-}" ]; then
export ONION_LOCATION_HEADER="add_header Onion-Location \"http://${ONION_HOSTNAME}\$request_uri\";"
else
export ONION_LOCATION_HEADER=""
fi
envsubst '${CLEARNET_DOMAIN} ${ONION_LOCATION_HEADER}' \
< /etc/nginx/templates/conf.d/clearnet.conf.template \
> /etc/nginx/conf.d/clearnet.conf
cp /etc/nginx/templates/conf.d/onion.conf /etc/nginx/conf.d/onion.conf
exec nginx -g 'daemon off;'