This commit is contained in:
René Aaron 2026-08-12 13:11:54 +08:00 committed by GitHub
commit 9fdef9ad58
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 459 additions and 0 deletions

View file

@ -0,0 +1,85 @@
# Deploy Alby Hub on DigitalOcean (Marketplace 1-Click App)
A submission-ready scaffold for publishing Alby Hub as a DigitalOcean Marketplace 1-Click Droplet App. Follows the layout documented at [digitalocean/marketplace-partners](https://github.com/digitalocean/marketplace-partners).
## What this scaffold produces
A Droplet image (Ubuntu 24.04) with:
- docker-ce pre-installed
- `ghcr.io/getalby/hub:latest` pre-pulled
- caddy 2 for TLS termination (Let's Encrypt)
- A `systemd` unit that runs Alby Hub at boot with a persistent `/opt/albyhub/data` volume
- A first-boot cloud-init script that prompts for `DOMAIN` via droplet user-data or auto-configures IP-only access
- A first-login MOTD with the Hub URL and onboarding steps
## Directory layout
```
deploy/digitalocean/
├── README.md # this file
├── marketplace-image.json # Packer template (input to `packer build`)
├── scripts/
│ ├── 01-install-albyhub.sh # installs docker, caddy, pulls image, drops systemd unit
│ ├── 90-cleanup.sh # DigitalOcean-required log/history cleanup
│ └── 99-img_check.sh # DigitalOcean-required image validation
└── files/
├── etc/
│ ├── systemd/system/albyhub.service # systemd unit for the container
│ ├── caddy/Caddyfile # caddy reverse-proxy config
│ └── update-motd.d/99-albyhub # first-login message
└── var/lib/cloud/scripts/per-instance/
└── 01-albyhub-firstboot.sh # first-boot config (runs once on each new droplet)
```
## Building the image
Prerequisites: `packer` ≥ 1.9, a DigitalOcean API token with `write` scope.
```bash
export DIGITALOCEAN_API_TOKEN=dop_v1_...
packer init deploy/digitalocean/marketplace-image.json
packer build deploy/digitalocean/marketplace-image.json
```
Packer creates a snapshot in your DO account. To test: launch a droplet from that snapshot, wait ~60 seconds, SSH in, read the MOTD, open the URL.
## Submitting to the Marketplace
1. Apply at [digitalocean.com/landing/partner-application](https://www.digitalocean.com/landing/partner-application) (Partner Pod program).
2. Fork [digitalocean/marketplace-partners](https://github.com/digitalocean/marketplace-partners) and open a PR that references the snapshot ID.
3. Provide marketing assets:
- Logo: [doc/logo.svg](../../doc/logo.svg)
- Category: `Blockchain` (they have a dedicated bitcoin subcategory)
- Short description: "Your own bitcoin lightning node — connectable, feature-rich, self-sovereign."
- Long description: adapt the top of the main [README.md](../../README.md).
- Recommended droplet size: **Basic Regular 2 GB RAM / 1 vCPU / 50 GB SSD** ($12/mo at the time of writing). Call out that 1 GB is not enough for the embedded LDK node.
4. Iterate on DO's validation feedback (their reviewers will run `99-img_check.sh` and manual checks).
## Droplet user-data options
Users can pre-configure the deploy by passing cloud-init user-data when creating the droplet:
```yaml
#cloud-config
write_files:
- path: /etc/albyhub/hub.env
content: |
DOMAIN=albyhub.example.com
BASE_URL=https://albyhub.example.com
```
If `DOMAIN` is set, Caddy requests a Let's Encrypt cert automatically on first boot. If not, the droplet serves Hub on `http://<public-ip>:8080` — the MOTD walks users through setting a domain afterwards.
## Affiliate attribution
Add `?refcode=albyhub` (or whatever Alby's DO affiliate code is at submission time) to every "Deploy on DigitalOcean" link in marketing materials. Per DO's affiliate terms: 10%/mo for 12 months on referred spend + $25 per paying referral after first $25 spend.
## Caveats to surface on the listing page
- **Backup `/opt/albyhub/data` regularly.** Holds the encrypted seed, channel state, and SQLite db. Loss = loss of funds.
- **Lightning p2p port 9735 is opened in the droplet firewall by the provisioner.** If the user enables DigitalOcean's Cloud Firewall on top, they must allow 9735 there too.
- **Self-custody tradeoff.** Same wording as the Hostinger listing — a VPS-hosted lightning node is less sovereign than a home node-distribution.
## Support surface
A new Alby Hub release that changes the container interface must trigger a new Packer build + snapshot + marketplace update. Add to the Hub release checklist. DO supports "update image in place" via a new snapshot push — existing users don't auto-upgrade, so release notes must tell them to `docker compose pull && docker compose up -d` (or use the MOTD update hint).

View file

@ -0,0 +1,8 @@
# Caddy terminates TLS on :443 and proxies to the Alby Hub container on 127.0.0.1:8080.
# The DOMAIN placeholder is rewritten by /var/lib/cloud/scripts/per-instance/01-albyhub-firstboot.sh
# based on droplet user-data. If no DOMAIN is set, the firstboot script replaces the whole
# file with a :80 site block so Hub is reachable over plain HTTP.
{$DOMAIN:localhost} {
reverse_proxy 127.0.0.1:8080
}

View file

@ -0,0 +1,25 @@
[Unit]
Description=Alby Hub (bitcoin lightning node)
After=docker.service network-online.target
Requires=docker.service
Wants=network-online.target
[Service]
Type=simple
EnvironmentFile=-/etc/albyhub/hub.env
Environment=ALBYHUB_IMAGE=ghcr.io/getalby/hub:latest
ExecStartPre=-/usr/bin/docker rm -f albyhub
ExecStart=/usr/bin/docker run --rm --name albyhub \
--env-file /etc/albyhub/hub.env \
-e WORK_DIR=/data/albyhub \
-e LDK_LISTENING_ADDRESSES=[::]:9735 \
-v /opt/albyhub/data:/data \
-p 127.0.0.1:8080:8080 \
-p 9735:9735 \
${ALBYHUB_IMAGE}
ExecStop=/usr/bin/docker stop albyhub
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,27 @@
#!/bin/sh
# First-login motd for Alby Hub droplets.
PUBLIC_IP=$(curl -s --max-time 2 http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address || echo "<your-droplet-ip>")
DOMAIN=$(grep -s '^DOMAIN=' /etc/albyhub/hub.env | cut -d= -f2-)
cat <<EOF
✨ Alby Hub is installed on this droplet.
Status: systemctl status albyhub
Logs: journalctl -u albyhub -f
Data: /opt/albyhub/data (BACK THIS UP — holds your seed and channel state)
Config: /etc/albyhub/hub.env
EOF
if [ -n "$DOMAIN" ]; then
echo " Open Alby Hub: https://$DOMAIN"
else
echo " Open Alby Hub: http://$PUBLIC_IP:8080"
echo ""
echo " To enable TLS, set DOMAIN in /etc/albyhub/hub.env, point your DNS at"
echo " $PUBLIC_IP, then run: systemctl restart caddy albyhub"
fi
echo ""

View file

@ -0,0 +1,31 @@
#!/bin/bash
# Runs once on the first boot of a droplet launched from the Alby Hub marketplace snapshot.
# Reads /etc/albyhub/hub.env (optionally pre-populated via cloud-init user-data) and
# rewrites the Caddy config to match: TLS + domain if DOMAIN is set, plain HTTP otherwise.
set -euo pipefail
ENV_FILE=/etc/albyhub/hub.env
CADDYFILE=/etc/caddy/Caddyfile
touch "$ENV_FILE"
DOMAIN=$(grep -s '^DOMAIN=' "$ENV_FILE" | cut -d= -f2- || true)
if [ -n "$DOMAIN" ]; then
cat > "$CADDYFILE" <<EOF
$DOMAIN {
reverse_proxy 127.0.0.1:8080
}
EOF
grep -q '^BASE_URL=' "$ENV_FILE" || echo "BASE_URL=https://$DOMAIN" >> "$ENV_FILE"
else
# No domain — expose Hub on :80 over plain HTTP for IP-based access.
cat > "$CADDYFILE" <<EOF
:80 {
reverse_proxy 127.0.0.1:8080
}
EOF
fi
systemctl restart caddy
systemctl restart albyhub

View file

@ -0,0 +1,45 @@
{
"variables": {
"token": "{{env `DIGITALOCEAN_API_TOKEN`}}",
"image_name": "albyhub-snapshot-{{timestamp}}",
"albyhub_image": "ghcr.io/getalby/hub:latest"
},
"builders": [
{
"type": "digitalocean",
"api_token": "{{user `token`}}",
"image": "ubuntu-24-04-x64",
"region": "fra1",
"size": "s-1vcpu-2gb",
"ssh_username": "root",
"snapshot_name": "{{user `image_name`}}",
"droplet_name": "packer-albyhub-{{timestamp}}"
}
],
"provisioners": [
{
"type": "file",
"source": "files/",
"destination": "/tmp/files"
},
{
"type": "shell",
"inline": [
"cp -r /tmp/files/* /",
"rm -rf /tmp/files"
]
},
{
"type": "shell",
"environment_vars": [
"ALBYHUB_IMAGE={{user `albyhub_image`}}",
"DEBIAN_FRONTEND=noninteractive"
],
"scripts": [
"scripts/01-install-albyhub.sh",
"scripts/90-cleanup.sh",
"scripts/99-img_check.sh"
]
}
]
}

View file

@ -0,0 +1,36 @@
#!/bin/bash
# Provisioner: install docker + caddy, pre-pull Alby Hub image, register systemd unit.
# Runs during Packer build against a fresh Ubuntu 24.04 droplet.
set -euxo pipefail
: "${ALBYHUB_IMAGE:=ghcr.io/getalby/hub:latest}"
apt-get update
apt-get install -y ca-certificates curl gnupg ufw
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" > /etc/apt/sources.list.d/docker.list
curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/gpg.key | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt | tee /etc/apt/sources.list.d/caddy-stable.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin caddy
docker pull "$ALBYHUB_IMAGE"
mkdir -p /opt/albyhub/data /etc/albyhub
touch /etc/albyhub/hub.env
ufw --force enable
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 9735/tcp
systemctl enable albyhub.service
systemctl enable caddy

View file

@ -0,0 +1,23 @@
#!/bin/bash
# Standard DigitalOcean Marketplace cleanup: strip logs, shell history, cloud-init state,
# and package-manager caches before snapshot. Mirrors the upstream sample in
# https://github.com/digitalocean/marketplace-partners/blob/master/scripts/90-cleanup.sh
set -euxo pipefail
apt-get -y autoremove
apt-get -y autoclean
rm -rf /tmp/* /var/tmp/*
rm -rf /var/log/*.gz /var/log/*.[0-9] /var/log/*-????????
: > /var/log/wtmp
: > /var/log/lastlog
: > /var/log/auth.log
: > /var/log/syslog
rm -rf /root/.ssh /root/.bash_history /root/.cache /root/.lesshst
rm -rf /home/*/.ssh /home/*/.bash_history /home/*/.cache /home/*/.lesshst
cloud-init clean --logs
rm -rf /var/lib/cloud/instances/*
truncate -s 0 /etc/machine-id

View file

@ -0,0 +1,22 @@
#!/bin/bash
# DigitalOcean Marketplace image validator (local dry-run). The official img_check.sh
# is shipped via https://github.com/digitalocean/marketplace-partners and must pass
# before submission. This local version covers the Alby Hub-specific invariants.
set -euo pipefail
fail() { echo "FAIL: $*" >&2; exit 1; }
command -v docker >/dev/null || fail "docker not installed"
docker image inspect ghcr.io/getalby/hub:latest >/dev/null || fail "alby hub image not pre-pulled"
command -v caddy >/dev/null || fail "caddy not installed"
[[ -f /etc/systemd/system/albyhub.service ]] || fail "albyhub.service missing"
systemctl is-enabled albyhub.service >/dev/null || fail "albyhub.service not enabled"
systemctl is-enabled caddy >/dev/null || fail "caddy not enabled"
[[ -f /etc/caddy/Caddyfile ]] || fail "Caddyfile missing"
[[ -f /var/lib/cloud/scripts/per-instance/01-albyhub-firstboot.sh ]] || fail "firstboot script missing"
[[ -f /etc/update-motd.d/99-albyhub ]] || fail "motd missing"
[[ -d /opt/albyhub/data ]] || fail "/opt/albyhub/data not created"
echo "OK: image looks valid for marketplace submission"

View file

@ -0,0 +1,3 @@
{$DOMAIN} {
reverse_proxy albyhub:8080
}

View file

@ -0,0 +1,60 @@
# Deploy Alby Hub on Hostinger VPS
A Hostinger Application Catalog template for Alby Hub. Hostinger wraps a `docker-compose.yml` with a prompt-driven env-var UI and ships it under their VPS Docker templates (see precedents: [openclaw](https://www.hostinger.com/vps/docker/openclaw), [hermes-agent](https://www.hostinger.com/vps/docker/hermes-agent)).
## Artifacts in this directory
- **[docker-compose.yml](./docker-compose.yml)** — two services: Alby Hub (port 8080, lightning p2p port 9735) and an optional Caddy reverse proxy (ports 80/443) activated with the `tls` compose profile. Volume `albyhub-data` persists the SQLite db, seed, channel state, and LDK data.
- **[Caddyfile](./Caddyfile)** — reverse proxy config that terminates TLS for `${DOMAIN}` and proxies to `albyhub:8080`. Caddy auto-provisions a Let's Encrypt cert on startup when a real DNS name is set.
## Deploying without submitting to Hostinger (works today)
Any Hostinger VPS with the Ubuntu 24.04 + Docker template already provisioned can run this:
```bash
curl -O https://raw.githubusercontent.com/getAlby/hub/master/deploy/hostinger/docker-compose.yml
curl -O https://raw.githubusercontent.com/getAlby/hub/master/deploy/hostinger/Caddyfile
# No TLS (IP-only access on port 8080):
docker compose up -d
# With TLS (requires DNS pointed at the VPS):
DOMAIN=albyhub.example.com BASE_URL=https://albyhub.example.com docker compose --profile tls up -d
```
## Submitting to the Hostinger Application Catalog
Hostinger does not publish a public submission process. Reach out to Hostinger BD / partnerships with this package:
| Asset | Source |
|---|---|
| Title | `Alby Hub` |
| Tagline | `Your own bitcoin lightning node` |
| Category | `Finance` / `Self-hosted` |
| Logo (SVG) | [doc/logo.svg](../../doc/logo.svg) |
| Long description | Adapt the top of the main [README.md](../../README.md) |
| `docker-compose.yml` | [./docker-compose.yml](./docker-compose.yml) |
| Reverse proxy config | [./Caddyfile](./Caddyfile) |
| User-facing env-var schema | See table below |
| Minimum VPS plan | KVM 2 (2 GB RAM, 1 shared vCPU, 50 GB NVMe) |
| Post-deploy URL | `http://{public_ip}:8080` (or `https://{domain}` when TLS profile active) |
### Env vars to surface in Hostinger's template UI
| Key | Label | Default | Required |
|---|---|---|---|
| `DOMAIN` | Your domain (optional, for TLS) | *empty* | No |
| `BASE_URL` | Public URL of this Hub (e.g. `https://albyhub.example.com`) | `http://localhost:8080` | No |
| `LDK_ESPLORA_SERVER` | Blockchain API endpoint | `https://electrs.getalbypro.com` | No |
Every other env var from [config/models.go](../../config/models.go) keeps its default; do not surface them in the Hostinger UI or users will be overwhelmed.
## Caveats to surface on the listing page
- **Backup the `albyhub-data` volume regularly.** It holds the encrypted seed, channel state, and SQLite db. Loss = loss of funds.
- **Lightning p2p on port 9735 needs the VPS firewall open.** Hostinger's default firewall blocks it; link users to Hostinger's firewall docs.
- **Self-custody tradeoff.** A VPS-hosted lightning node is less sovereign than a home node-distribution (umbrel, start9). Mention this honestly in the listing so users choose eyes-open.
## Support surface
A new Alby Hub release that changes the container interface must also be smoke-tested against this compose file. Add to the Hub release checklist.

View file

@ -0,0 +1,46 @@
services:
albyhub:
image: ghcr.io/getalby/hub:latest
container_name: albyhub
restart: unless-stopped
environment:
WORK_DIR: /data/albyhub
BASE_URL: ${BASE_URL:-http://localhost:8080}
LDK_ESPLORA_SERVER: https://electrs.getalbypro.com
LDK_LISTENING_ADDRESSES: "[::]:9735"
volumes:
- albyhub-data:/data
ports:
- "8080:8080"
- "9735:9735"
networks:
- albyhub
caddy:
image: caddy:2-alpine
container_name: albyhub-caddy
restart: unless-stopped
depends_on:
- albyhub
environment:
DOMAIN: ${DOMAIN:-}
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
networks:
- albyhub
profiles:
- tls
volumes:
albyhub-data:
caddy-data:
caddy-config:
networks:
albyhub:
driver: bridge

48
deploy/railway/README.md Normal file
View file

@ -0,0 +1,48 @@
# Deploy Alby Hub on Railway
A "Deploy on Railway" one-click template for Alby Hub. Railway templates are created in Railway's dashboard and shared as a `https://railway.com/template/<id>` URL — there is no repo-committed template file. This document defines the exact config to enter in the dashboard so the template is reproducible.
## Template configuration
Create a new service in a Railway template with the following settings.
### Source
- **Type:** Docker image
- **Image:** `ghcr.io/getalby/hub:latest`
### Volumes
- **Mount path:** `/data`
- **Size:** 1 GB (users on larger wallets should resize after deploy)
### Networking
- **Public HTTP port:** `8080` (Railway will assign a `*.up.railway.app` domain and terminate TLS)
- **Lightning p2p port 9735:** *not exposed.* Railway's HTTP proxy does not forward arbitrary TCP, so inbound channel opens are not reachable by default. Users who need inbound p2p should deploy on a VPS provider (DigitalOcean, Hostinger) instead. Document this clearly on the template's Railway listing.
### Environment variables
| Key | Value | Notes |
|---|---|---|
| `WORK_DIR` | `/data/albyhub` | Matches `render.yaml`. Data persists on the `/data` volume. |
| `BASE_URL` | `${{RAILWAY_PUBLIC_DOMAIN}}` | Prefix with `https://` if Railway's variable doesn't include it. Needed for OAuth redirects and copy-to-clipboard URLs. |
| `LDK_ESPLORA_SERVER` | `https://electrs.getalbypro.com` | Same default as `render.yaml`. Override for self-hosted esplora. |
| `PORT` | *(leave unset)* | Defaults to 8080, matches Railway's exposed port. |
| `AUTO_UNLOCK_PASSWORD` | *(leave unset)* | Users can opt in after first-run for auto-start on reboot. Trades custody for convenience — do not pre-fill. |
All other env vars from [config/models.go](../../config/models.go) use their defaults. Surface only `LDK_ESPLORA_SERVER` and `AUTO_UNLOCK_PASSWORD` as user-editable in the Railway template UI; keep `WORK_DIR` and `BASE_URL` locked.
## README button (paste once template is live)
Once the template URL is known, add this to the main [README.md](../../README.md) next to the existing Render button:
```markdown
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/template/<TEMPLATE_ID>?utm_campaign=alby-hub)
```
Replace `utm_campaign=alby-hub` consistently with the other marketplace buttons so conversion can be attributed.
## Economics
Railway's [template kickback program](https://blog.railway.com/p/template-kickback-program-cash) returns 25% of the first 12 months of infrastructure spend from each deploy to the template owner. The receiving account should be owned by Alby (not a personal account) so the revenue flows through the business.
## Support surface
Each template deploy is a live Alby Hub instance that expects `ghcr.io/getalby/hub:latest` to keep working. Before a breaking image change lands, verify that a fresh Railway deploy still completes onboarding. Add to the Hub release checklist.