Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions deploy/digitalocean/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Deploy Alby Hub on DigitalOcean

This folder builds a DigitalOcean snapshot for Alby Hub using Packer.

## Prerequisites

Install:

- `packer`
- the DigitalOcean Packer plugin

Example install on macOS with Homebrew:

```bash
brew tap hashicorp/tap
brew install hashicorp/tap/packer
packer plugins install github.com/digitalocean/digitalocean
```

You also need:

- a DigitalOcean API token with write access

## Required Environment Variables

Before running the build, set:

- `DIGITALOCEAN_API_TOKEN`
- `ALBYHUB_VERSION`

Example:

```bash
export DIGITALOCEAN_API_TOKEN=dop_v1_...
export ALBYHUB_VERSION=v1.22.2
```

## Build The Snapshot

From this directory, run:

```bash
./build.sh
```

### Note

The build does not produce a local file you upload manually. Instead, it creates a DigitalOcean snapshot in your account.

At the end of a successful build, Packer should print something like:

```text
A snapshot was created: 'albyhub-v1-22-2-snapshot-1778744165'
```

That snapshot is the artifact you use.

## How To Deploy

1. Open the DigitalOcean dashboard.
2. Go to `Create` -> `Droplet`.
3. Find the new snapshot under "Choose an image".
4. Create a droplet from it.
5. Wait for the droplet to boot.
6. You should now see Alby Hub running at:

```text
http://<droplet-ip>
```

## Updating

The DigitalOcean Marketplace update is currently run manually instead of using the GitHub release workflow.

This keeps the release workflow simpler. Otherwise, we would need to wait for the GHCR Docker image for the release tag to become available before building the snapshot, because the Packer build pulls:

```text
ghcr.io/getalby/hub:<version>
```

For each new release:

1. Wait until the Docker image for the release tag is available in GHCR.
2. Export the required environment variables:

```bash
export DIGITALOCEAN_API_TOKEN=dop_v1_...
export ALBYHUB_VERSION=v1.23.0
```

3. Build the DigitalOcean snapshot:

```bash
cd deploy/digitalocean
./build.sh
```

4. Confirm that `manifest.json` was created and contains the new snapshot artifact.
5. Submit the Marketplace update:

```bash
./scripts/marketplace-submit.sh
```

The script reads the snapshot image ID from `manifest.json` and sends the update request to the DigitalOcean Vendor Portal.

Once this manual flow has been verified during a release, we can move it into `.github/workflows/release.yaml` by adding a job that waits for the GHCR image, builds the snapshot and runs the Marketplace submission script.
19 changes: 19 additions & 0 deletions deploy/digitalocean/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash

set -eu

if [ -z "${DIGITALOCEAN_API_TOKEN:-}" ]; then
echo "DIGITALOCEAN_API_TOKEN is required" >&2
exit 1
fi

if [ -z "${ALBYHUB_VERSION:-}" ]; then
echo "ALBYHUB_VERSION is required (example: v1.22.2)" >&2
exit 1
fi

export DIGITALOCEAN_API_TOKEN
export ALBYHUB_VERSION
export ALBYHUB_DASH_VERSION=$(printf '%s' "$ALBYHUB_VERSION" | sed 's/\./-/g')

packer build template.json
32 changes: 32 additions & 0 deletions deploy/digitalocean/files/etc/update-motd.d/99-one-click
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/sh

myip=$(hostname -I | awk '{print$1}')
Comment thread
im-adithya marked this conversation as resolved.
cat <<EOF
********************************************************************************

Welcome to DigitalOcean's 1-Click Alby Hub Droplet.
To keep this Droplet secure, the UFW firewall is enabled.
All ports are BLOCKED except 22 (SSH), 80 (HTTP), and 443 (HTTPS).

Access Alby Hub
URL: http://${myip}

In a web browser, you can view:
* The Alby Hub instance on this Droplet: http://${myip}
* The Alby Hub GitHub repository for issues/feature requests: https://github.com/getAlby/hub

On the server:
* Alby Hub is served from Docker Compose in /opt/albyhub
* Alby Hub data is stored in /opt/albyhub/data
* The app is started automatically on first boot
* To inspect the service, run:
cd /opt/albyhub && docker compose ps
cd /opt/albyhub && docker compose logs -f

If the app is still starting, refresh http://${myip} after a moment.

For help and more information, visit https://getalby.com

********************************************************************************
To delete this message of the day: rm -rf $(readlink -f ${0})
EOF
12 changes: 12 additions & 0 deletions deploy/digitalocean/files/opt/albyhub/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
services:
albyhub:
container_name: albyhub
image: ghcr.io/getalby/hub:__ALBYHUB_VERSION__
volumes:
- ./data:/data
ports:
- "80:8080"
environment:
WORK_DIR: /data/albyhub
restart: unless-stopped
stop_grace_period: 300s
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh

set -eu

mkdir -p /opt/albyhub/data

cd /opt/albyhub
docker compose up -d --quiet-pull
Empty file.
21 changes: 21 additions & 0 deletions deploy/digitalocean/scripts/010-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh

mkdir -p /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

cat > /etc/apt/sources.list.d/docker.sources <<EOM
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOM

apt-get -y update
apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

systemctl enable docker
systemctl start docker
6 changes: 6 additions & 0 deletions deploy/digitalocean/scripts/012-grub-opts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh

sed -e 's|GRUB_CMDLINE_LINUX="|GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1|g' \
-i /etc/default/grub

update-grub
9 changes: 9 additions & 0 deletions deploy/digitalocean/scripts/014-ufw-albyhub.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh

sed -e 's|DEFAULT_FORWARD_POLICY=.*|DEFAULT_FORWARD_POLICY="ACCEPT"|g' \
-i /etc/default/ufw
Comment on lines +3 to +4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Forward policy is overly permissive for a hardened base image.

Line 3 sets routed/forwarded traffic to global ACCEPT, which broadens network exposure beyond required ingress rules. Prefer default deny for forwarding and explicit route allowances only where needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/digitalocean/scripts/014-ufw-albyhub.sh` around lines 3 - 4, The ufw
forward policy is set to a global ACCEPT which is too permissive for a hardened
image; change the value of DEFAULT_FORWARD_POLICY in /etc/default/ufw from
"ACCEPT" to "DROP" (i.e., ensure the sed replacement sets
DEFAULT_FORWARD_POLICY="DROP") and instead add explicit ufw route/forward rules
only for the specific services or subnets that require forwarding; locate the
occurrence of DEFAULT_FORWARD_POLICY in the sed command and update it, and
document or add corresponding ufw allow/route rules for any required forwarded
traffic.


ufw limit ssh
ufw allow http
ufw allow https
ufw --force enable
10 changes: 10 additions & 0 deletions deploy/digitalocean/scripts/015-albyhub.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh

set -eu

mkdir -p /opt/albyhub/data

sed -i.bak "s|__ALBYHUB_VERSION__|${application_version}|g" /opt/albyhub/docker-compose.yml
rm -f /opt/albyhub/docker-compose.yml.bak

docker pull ghcr.io/getalby/hub:${application_version}
22 changes: 22 additions & 0 deletions deploy/digitalocean/scripts/020-application-tag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/sh

################################
## PART: Write the application tag
##
## vi: syntax=sh expandtab ts=4

build_date=$(date +%Y-%m-%d)
distro="$(lsb_release -s -i)"
distro_release="$(lsb_release -s -r)"
distro_codename="$(lsb_release -s -c)"
distro_arch="$(uname -m)"

cat >> /var/lib/digitalocean/application.info <<EOM
application_name="${application_name}"
build_date="${build_date}"
distro="${distro}"
distro_release="${distro_release}"
distro_codename="${distro_codename}"
distro_arch="${distro_arch}"
application_version="${application_version}"
EOM
50 changes: 50 additions & 0 deletions deploy/digitalocean/scripts/900-cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/bash

# DigitalOcean Marketplace Image Validation Tool
# © 2021 DigitalOcean LLC.
# This code is licensed under Apache 2.0 license (see LICENSE.md for details)

set -o errexit

# Ensure /tmp exists and has the proper permissions before
# checking for security updates
# https://github.com/digitalocean/marketplace-partners/issues/94
if [[ ! -d /tmp ]]; then
mkdir /tmp
fi
chmod 1777 /tmp

if [ -n "$(command -v yum)" ]; then
yum update -y
yum clean all
elif [ -n "$(command -v apt-get)" ]; then
export DEBIAN_FRONTEND=noninteractive
apt-get -y purge droplet-agent
apt-get -y update
apt-get -o Dpkg::Options::="--force-confold" upgrade -q -y --force-yes
apt-get -y autoremove
apt-get -y autoclean
fi

rm -rf /tmp/* /var/tmp/*
history -c
cat /dev/null > /root/.bash_history
unset HISTFILE
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/log/*.gz /var/log/*.[0-9] /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /root/.ssh/authorized_keys /etc/ssh/*key*
touch /etc/ssh/revoked_keys
chmod 600 /etc/ssh/revoked_keys

# Securely erase the unused portion of the filesystem
GREEN='\033[0;32m'
NC='\033[0m'
printf "\n${GREEN}Writing zeros to the remaining disk space to securely
erase the unused portion of the file system.
Depending on your disk size this may take several minutes.
The secure erase will complete successfully when you see:${NC}
dd: writing to '/zerofile': No space left on device\n
Beginning secure erase now\n"

dd if=/dev/zero of=/zerofile bs=4096 || rm /zerofile
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading
Loading