feat: add digital ocean snapshot builder#2345
Conversation
📝 WalkthroughWalkthroughThis pull request adds a complete DigitalOcean deployment infrastructure for Alby Hub, enabling automated snapshot building via Packer and one-click droplet creation. The implementation includes build orchestration, system provisioning scripts, application configuration, runtime initialization, and security hardening. ChangesDigitalOcean Droplet Deployment Infrastructure
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| "type": "digitalocean", | ||
| "api_token": "{{user `do_api_token`}}", | ||
| "image": "ubuntu-24-04-x64", | ||
| "region": "fra1", |
There was a problem hiding this comment.
I think we can also specify multiple regions here using "snapshot_regions", wanted to know what's the preference here before proceeding
There was a problem hiding this comment.
shouldn't the user chose where to run?
There was a problem hiding this comment.
Yeah I'm not sure about this exactly but they do provide this option of choosing for the published images, maybe it's exclusive to published snapshots
(but I just remembered I didn't try building without a region, will see what happens then and update EDIT: region is required)
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
deploy/digitalocean/scripts/012-grub-opts.sh (1)
3-4: ⚡ Quick winMake the GRUB update idempotent.
Line 3 can append duplicate
cgroup_enable=memory swapaccount=1values if rerun. Add a guard so the flags are only inserted once.Suggested patch
-sed -e 's|GRUB_CMDLINE_LINUX="|GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1|g' \ - -i /etc/default/grub +if ! grep -q 'cgroup_enable=memory swapaccount=1' /etc/default/grub; then + sed -e 's|GRUB_CMDLINE_LINUX="|GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1 |g' \ + -i /etc/default/grub +fi🤖 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/012-grub-opts.sh` around lines 3 - 4, The current sed invocation can duplicate the flags because it blindly replaces the GRUB_CMDLINE_LINUX value; change the script to first check /etc/default/grub for the presence of cgroup_enable=memory and swapaccount=1 in the GRUB_CMDLINE_LINUX line and only perform the modification if those flags are missing. Concretely, locate the GRUB_CMDLINE_LINUX line in /etc/default/grub (reference symbol: GRUB_CMDLINE_LINUX) and if the flags are absent, update that line to include them (and then run update-grub); otherwise leave the file unchanged to make the operation idempotent.deploy/digitalocean/template.json (1)
5-5: ⚡ Quick winUse
linux-image-extra-virtual-hwe-24.04for optimal Noble compatibility.Line 5 includes
linux-image-extra-virtual, which exists on Ubuntu 24.04 but is not the recommended variant. Replace withlinux-image-extra-virtual-hwe-24.04to ensure supported Docker host/kernel compatibility with the HWE (Hardware Enablement) kernel stream tracking on Noble.🤖 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/template.json` at line 5, The apt_packages entry currently lists "linux-image-extra-virtual"; update the apt_packages string to replace linux-image-extra-virtual with linux-image-extra-virtual-hwe-24.04 so the template uses the HWE kernel variant for Noble compatibility (modify the "apt_packages" value in the JSON to include linux-image-extra-virtual-hwe-24.04 instead of linux-image-extra-virtual).
🤖 Prompt for all review comments with 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.
Inline comments:
In `@deploy/digitalocean/files/etc/update-motd.d/99-one-click`:
- Line 3: Replace the unreliable hostname-based IP lookup used to set the myip
variable with a call to the DigitalOcean metadata service; specifically, set
myip by querying the metadata endpoint for the droplet's public IPv4 (e.g.,
metadata/v1/interfaces/public/0/ipv4/address) with a short curl timeout and a
sensible fallback (keep the existing hostname-based command as fallback) so the
onboarding URL always uses the droplet's public IP; update the assignment to
myip in the 99-one-click script accordingly.
In `@deploy/digitalocean/scripts/014-ufw-albyhub.sh`:
- Around line 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.
In `@deploy/digitalocean/scripts/900-cleanup.sh`:
- Line 45: The purge command in 900-cleanup.sh uses sudo and an unquoted glob
which can lead to unintended shell expansion; change the invocation to run as
root (remove sudo) and quote the package pattern (e.g., "droplet-agent*") so the
apt-get --yes purge call in the script invokes apt-get as root and treats the
pattern literally.
---
Nitpick comments:
In `@deploy/digitalocean/scripts/012-grub-opts.sh`:
- Around line 3-4: The current sed invocation can duplicate the flags because it
blindly replaces the GRUB_CMDLINE_LINUX value; change the script to first check
/etc/default/grub for the presence of cgroup_enable=memory and swapaccount=1 in
the GRUB_CMDLINE_LINUX line and only perform the modification if those flags are
missing. Concretely, locate the GRUB_CMDLINE_LINUX line in /etc/default/grub
(reference symbol: GRUB_CMDLINE_LINUX) and if the flags are absent, update that
line to include them (and then run update-grub); otherwise leave the file
unchanged to make the operation idempotent.
In `@deploy/digitalocean/template.json`:
- Line 5: The apt_packages entry currently lists "linux-image-extra-virtual";
update the apt_packages string to replace linux-image-extra-virtual with
linux-image-extra-virtual-hwe-24.04 so the template uses the HWE kernel variant
for Noble compatibility (modify the "apt_packages" value in the JSON to include
linux-image-extra-virtual-hwe-24.04 instead of linux-image-extra-virtual).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3841a427-3041-43f6-bab3-b2a0bbf94aa5
📒 Files selected for processing (13)
deploy/digitalocean/README.mddeploy/digitalocean/build.shdeploy/digitalocean/files/etc/update-motd.d/99-one-clickdeploy/digitalocean/files/opt/albyhub/docker-compose.ymldeploy/digitalocean/files/var/lib/cloud/scripts/per-instance/001_onbootdeploy/digitalocean/files/var/lib/digitalocean/application.infodeploy/digitalocean/scripts/010-docker.shdeploy/digitalocean/scripts/012-grub-opts.shdeploy/digitalocean/scripts/014-ufw-albyhub.shdeploy/digitalocean/scripts/015-albyhub.shdeploy/digitalocean/scripts/020-application-tag.shdeploy/digitalocean/scripts/900-cleanup.shdeploy/digitalocean/template.json
| @@ -0,0 +1,32 @@ | |||
| #!/bin/sh | |||
|
|
|||
| myip=$(hostname -I | awk '{print$1}') | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n deploy/digitalocean/files/etc/update-motd.d/99-one-clickRepository: getAlby/hub
Length of output: 1397
🏁 Script executed:
# Check if this file is actually used in the codebase and see its full context
head -20 deploy/digitalocean/files/etc/update-motd.d/99-one-clickRepository: getAlby/hub
Length of output: 722
🏁 Script executed:
# Search for similar patterns in other parts of the codebase that might handle IP detection
rg "hostname -I" --type shRepository: getAlby/hub
Length of output: 37
🌐 Web query:
DigitalOcean metadata service public IP address 169.254.169.254
💡 Result:
DigitalOcean droplet metadata service is accessible from a Droplet via the special static link-local IP address 169.254.169.254. [1][2] The metadata API is queried with HTTP GET requests to endpoints under /metadata/v1/. For example, to retrieve the Droplet’s public IPv4 address: curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address [1][2] Note: for “Private” Droplets, the interfaces/public/ path is absent; you should use interfaces/private/0/ipv4/address instead. [1][2]
Citations:
- 1: https://docs.digitalocean.com/products/droplets/how-to/access-metadata/
- 2: https://docs.digitalocean.com/products/droplets/how-to/access-metadata/index.html.md
🌐 Web query:
DigitalOcean hostname -I private IP address first issue
💡 Result:
DigitalOcean/VPC Droplets can have both a public IP (usually on eth0) and a private IP (on eth1). On such systems, the command hostname -I prints ALL configured IP addresses “enumerates all configured addresses on all network interfaces” and “Do not make any assumptions about the order of the output,” so cutting on the first address (hostname -I | cut -d' ' -f1) is inherently unreliable; it may output the private IP first or the public IP first depending on interface/address enumeration order.[1] Why you saw “private IP first” on DigitalOcean: 1) Your Droplet likely has both interfaces up: eth0 (public) and eth1 (private). DigitalOcean documents that eth1 is the private/VPC network interface and its address is the Droplet’s private IP.[2][3] 2) hostname -I does not guarantee output order across interfaces/addresses, so “first field = external/public” is a legacy assumption that can break after changes (kernel/libc/tooling/network config, etc.). The hostname(1) man-page guidance is explicitly: do not assume order of hostname -I output.[1] What to do instead (reliable ways): - Grab the IP from the interface you actually want. - Public (eth0): ip -4 addr show dev eth0 |... - Private (eth1): ip -4 addr show dev eth1 |... DigitalOcean’s own guidance and troubleshooting consistently references eth0/public vs eth1/private and that eth1 carries the private IP.[2][3] - If you just need the “private IP that DigitalOcean uses internally,” use the metadata service for the private interface rather than parsing hostname -I. DigitalOcean describes retrieving the anchor/private addressing via the metadata service; for private networking, the “private” interface is used (interfaces/private/0/ipv4/address). [4][5] If you tell me your OS (Ubuntu version) and whether this is a “traditional” Droplet (eth0+eth1) or a “Private Droplet” (single private interface), I can give you the exact one-liner command to extract the correct IP for your case.
Citations:
- 1: https://serverfault.com/questions/952222/what-influences-hostname-i-ip-order
- 2: https://docs.digitalocean.com/support/my-ubuntu-droplet-lost-all-network-connectivity-after-a-software-update.
- 3: https://docs.digitalocean.com/support/how-do-i-create-a-droplet-without-a-private-ip
- 4: https://docs.digitalocean.com/products/droplets/details/private-droplets/
- 5: https://docs.digitalocean.com/reference/api/metadata/network-interfaces/
Use the DigitalOcean metadata service to reliably detect the public IP.
hostname -I | awk '{print$1}' can return a private IP address first on DigitalOcean droplets with both public (eth0) and private (eth1) interfaces. Since hostname -I output order is not guaranteed by the system, the generated onboarding URL becomes inaccessible from outside the droplet.
Suggested fix
-myip=$(hostname -I | awk '{print$1}')
+myip=$(curl -fsS http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address || true)
+[ -n "$myip" ] || myip=$(hostname -I | awk '{print $1}')📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| myip=$(hostname -I | awk '{print$1}') | |
| myip=$(curl -fsS http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address || true) | |
| [ -n "$myip" ] || myip=$(hostname -I | awk '{print $1}') |
🤖 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/files/etc/update-motd.d/99-one-click` at line 3, Replace
the unreliable hostname-based IP lookup used to set the myip variable with a
call to the DigitalOcean metadata service; specifically, set myip by querying
the metadata endpoint for the droplet's public IPv4 (e.g.,
metadata/v1/interfaces/public/0/ipv4/address) with a short curl timeout and a
sensible fallback (keep the existing hostname-based command as fallback) so the
onboarding URL always uses the droplet's public IP; update the assignment to
myip in the 99-one-click script accordingly.
| sed -e 's|DEFAULT_FORWARD_POLICY=.*|DEFAULT_FORWARD_POLICY="ACCEPT"|g' \ | ||
| -i /etc/default/ufw |
There was a problem hiding this comment.
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.
| done | ||
| sync; rm /zerofile; sync | ||
| cat /dev/null > /var/log/lastlog; cat /dev/null > /var/log/wtmp | ||
| sudo apt-get --yes purge droplet-agent* |
There was a problem hiding this comment.
Avoid sudo and quote the purge pattern.
Line 45 should run directly as root and quote the package glob to avoid unintended shell expansion.
Suggested patch
-sudo apt-get --yes purge droplet-agent*
+apt-get --yes purge 'droplet-agent*'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sudo apt-get --yes purge droplet-agent* | |
| apt-get --yes purge 'droplet-agent*' |
🤖 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/900-cleanup.sh` at line 45, The purge command in
900-cleanup.sh uses sudo and an unquoted glob which can lead to unintended shell
expansion; change the invocation to run as root (remove sudo) and quote the
package pattern (e.g., "droplet-agent*") so the apt-get --yes purge call in the
script invokes apt-get as root and treats the pattern literally.
|
fly and render files are in the root, I wonder if it's possible / makes sense to move them so all the third party deployment options are in this deploy folder |
Adds a snapshot builder which we can use to publish on DigitalOcean marketplace
Next steps:
But this PR is fully functional and can also be merged as-is
Summary by CodeRabbit
New Features
Documentation