One command to setup AI coding agents on cloud servers (Hetzner, AWS, etc.) with optional Caddy reverse proxy for secure HTTPS access.
- ✅ Automated setup of development environment (Docker, Git, Node.js, Python, etc.)
- ✅ OpenCode installation for AI-powered coding
- ✅ Optional Caddy reverse proxy with auto HTTPS
- ✅ Password-protected access via OpenCode authentication
- ✅ Google OAuth2 login via oauth2-proxy (optional, replaces password auth)
- ✅ Support for both domain-based and IP-only configurations
- ✅ Automatic firewall configuration
- ✅ Zero-downtime certificate renewal
Recommended: Download and review first
curl -fsSL https://raw.githubusercontent.com/mikeboe/henry/main/setup.sh -o setup.sh
less setup.sh # Review the script
sudo bash setup.shOne-line install (executes immediately)
curl -fsSL https://raw.githubusercontent.com/mikeboe/henry/main/setup.sh | sudo bashRecommended: Download and review first
curl -fsSL https://raw.githubusercontent.com/mikeboe/henry/main/setup.sh -o setup.sh
less setup.sh # Review the script
sudo bash setup.sh \
--install-caddy \
--domain your.domain.com \
--password 'YourStrongPassword123!'One-line install (executes immediately)
curl -fsSL https://raw.githubusercontent.com/mikeboe/henry/main/setup.sh | sudo bash -s -- \
--install-caddy \
--domain your.domain.com \
--password 'YourStrongPassword123!'Recommended: Download and review first
curl -fsSL https://raw.githubusercontent.com/mikeboe/henry/main/setup.sh -o setup.sh
less setup.sh # Review the script
sudo bash setup.sh \
--install-caddy \
--ip-only \
--password 'YourStrongPassword123!'One-line install (executes immediately)
curl -fsSL https://raw.githubusercontent.com/mikeboe/henry/main/setup.sh | sudo bash -s -- \
--install-caddy \
--ip-only \
--password 'YourStrongPassword123!'Use this when a CDN (e.g., BunnyCDN, Cloudflare) or another reverse proxy sits in front of Caddy and handles TLS termination. Without this flag, Caddy's automatic HTTP→HTTPS redirect will create an infinite redirect loop because the CDN terminates HTTPS but proxies requests to the origin over HTTP.
Recommended: Download and review first
curl -fsSL https://raw.githubusercontent.com/mikeboe/henry/main/setup.sh -o setup.sh
less setup.sh # Review the script
sudo bash setup.sh \
--install-caddy \
--domain your.domain.com \
--behind-proxy \
--password 'YourStrongPassword123!'One-line install (executes immediately)
curl -fsSL https://raw.githubusercontent.com/mikeboe/henry/main/setup.sh | sudo bash -s -- \
--install-caddy \
--domain your.domain.com \
--behind-proxy \
--password 'YourStrongPassword123!'Protect OpenCode with Google login using oauth2-proxy. Users are redirected to Google sign-in before accessing OpenCode. You must create Google OAuth2 credentials first.
Recommended: Download and review first
curl -fsSL https://raw.githubusercontent.com/mikeboe/henry/main/setup.sh -o setup.sh
less setup.sh # Review the script
sudo bash setup.sh \
--install-caddy \
--domain your.domain.com \
--oauth2-proxy \
--google-client-id 'YOUR_CLIENT_ID.apps.googleusercontent.com' \
--google-client-secret 'YOUR_CLIENT_SECRET' \
--cookie-secret 'YOUR16BYTESECRET' \
--allowed-email-domain 'yourcompany.com'One-line install (executes immediately)
curl -fsSL https://raw.githubusercontent.com/mikeboe/henry/main/setup.sh | sudo bash -s -- \
--install-caddy \
--domain your.domain.com \
--oauth2-proxy \
--google-client-id 'YOUR_CLIENT_ID.apps.googleusercontent.com' \
--google-client-secret 'YOUR_CLIENT_SECRET' \
--cookie-secret 'YOUR16BYTESECRET' \
--allowed-email-domain 'yourcompany.com'Security Note: Always review scripts before executing them, especially with sudo privileges. The "download and review first" method is recommended for production environments.
./setup.sh [OPTIONS]| Option | Description | Required |
|---|---|---|
--install-caddy |
Install and configure Caddy reverse proxy | No |
--domain DOMAIN |
Domain name for Caddy (enables auto HTTPS) | With --install-caddy (or use --ip-only) |
--username USERNAME |
Username for OpenCode web server (default: opencode) |
No |
--password PASSWORD |
Password for OpenCode web server | With --install-caddy (not needed with --oauth2-proxy) |
--port PORT |
OpenCode port (default: 4096) | No |
--ip-only |
Configure Caddy for IP-only access with self-signed cert | No |
--behind-proxy |
Configure Caddy for HTTP-only mode when behind a CDN/reverse proxy (prevents redirect loops) | No |
--oauth2-proxy |
Enable oauth2-proxy with Google login for Caddy authentication | No |
--google-client-id ID |
Google OAuth2 client ID | With --oauth2-proxy |
--google-client-secret SECRET |
Google OAuth2 client secret | With --oauth2-proxy |
--cookie-secret SECRET |
Cookie secret for oauth2-proxy (must be exactly 16, 24, or 32 characters) | With --oauth2-proxy |
--allowed-email-domain DOMAIN |
Restrict Google login to users from this email domain (default: * = any Google account) |
No |
--allowed-email EMAIL |
Restrict Google login to a single specific email address | No |
-h, --help |
Display help message | No |
Perfect for development or when using a different reverse proxy:
./setup.shAfter setup, access OpenCode at: http://<server-ip>:4096
Best for production use with automatic HTTPS certificates:
./setup.sh \
--install-caddy \
--domain code.example.com \
--password 'SecurePassword123!'Requirements:
- DNS A record for
code.example.commust point to your server's IP - Port 80 and 443 must be accessible for Let's Encrypt certificate validation
After setup, access OpenCode at: https://code.example.com
For scenarios without a domain name:
./setup.sh \
--install-caddy \
--ip-only \
--password 'MySecurePassword!'After setup, access OpenCode at: https://<server-ip> (you'll see a browser warning about the self-signed certificate, which is expected)
Use when a CDN (e.g., BunnyCDN, Cloudflare) or reverse proxy handles TLS in front of Caddy. This configures Caddy for HTTP-only mode, preventing redirect loops:
./setup.sh \
--install-caddy \
--domain code.example.com \
--behind-proxy \
--password 'SecurePassword123!'After setup, configure your CDN/proxy to forward HTTP traffic to this server on port 80.
Run OpenCode on a custom port:
./setup.sh \
--install-caddy \
--domain code.example.com \
--password 'SecurePassword123!' \
--port 8080Override the default OpenCode username (default is opencode):
./setup.sh \
--install-caddy \
--domain code.example.com \
--username myuser \
--password 'SecurePassword123!'Replace password authentication with Google login. Users must sign in with their Google account before accessing OpenCode:
./setup.sh \
--install-caddy \
--domain code.example.com \
--oauth2-proxy \
--google-client-id '123456789-abc.apps.googleusercontent.com' \
--google-client-secret 'GOCSPX-...' \
--cookie-secret 'a8f3k9s2b1n7m4q6' \
--allowed-email-domain 'yourcompany.com'Restrict to a single email address instead of a whole domain:
./setup.sh \
--install-caddy \
--domain code.example.com \
--oauth2-proxy \
--google-client-id '123456789-abc.apps.googleusercontent.com' \
--google-client-secret 'GOCSPX-...' \
--cookie-secret 'a8f3k9s2b1n7m4q6' \
--allowed-email 'user@gmail.com'See the Google Cloud Console Setup section for how to obtain your credentials.
- System updates
- Essential tools:
tmux,git,curl,build-essential,python3-pip,unzip - Docker
- NVM (Node Version Manager)
- Node.js LTS
- Global Node tools:
nodemon - OpenCode (AI coding agent)
- Caddy web server
- Automatic HTTPS certificates (with domain) or self-signed certificates (IP-only)
- Reverse proxy configuration
- Firewall rules (UFW or firewalld)
All of the above, plus:
- oauth2-proxy binary (downloaded from GitHub releases)
- oauth2-proxy configuration at
/etc/oauth2-proxy/oauth2-proxy.cfg(chmod 600) - oauth2-proxy systemd service
Browser → http://server-ip:4096 → OpenCode
Browser → https://your.domain.com
↓
Caddy (443) → OpenCode (localhost:4096)
↓
[OpenCode Auth Prompt]
Browser → https://server-ip
↓
Caddy (443, self-signed) → OpenCode (localhost:4096)
↓
[OpenCode Auth Prompt]
Browser → https://your.domain.com
↓
Bunny CDN / Cloudflare (TLS termination)
↓ HTTP
Caddy (80, auto_https off) → OpenCode (localhost:4096)
Browser → https://your.domain.com
↓
Caddy (443)
├── /oauth2/* → oauth2-proxy (localhost:4180)
└── /* → forward_auth oauth2-proxy → OpenCode (localhost:4096)
↓ (unauthenticated)
[Google Login Page] → Google OAuth2 → callback
Terminal Mode:
cd ~/projects && opencodeWeb Interface:
opencode-webThe web interface will be accessible at:
- Without Caddy:
http://<server-ip>:4096 - With Caddy + Domain:
https://your.domain.com - With Caddy + IP-only:
https://<server-ip>
When --password is provided during setup, the following environment variables are automatically saved to ~/.opencode_env (with chmod 600 for security) and sourced from ~/.bashrc:
| Variable | Description | Required |
|---|---|---|
OPENCODE_SERVER_PASSWORD |
Password for the OpenCode web server | Yes (with --install-caddy) |
OPENCODE_SERVER_USERNAME |
Username for the OpenCode web server | No (defaults to opencode) |
These variables are picked up by OpenCode when the opencode-web alias is run.
Check Status:
sudo systemctl status oauth2-proxyRestart oauth2-proxy:
sudo systemctl restart oauth2-proxyView Logs:
sudo journalctl -u oauth2-proxy -fUpdate Configuration:
sudo nano /etc/oauth2-proxy/oauth2-proxy.cfg
# After editing, restart:
sudo systemctl restart oauth2-proxyCheck Status:
sudo systemctl status caddyRestart Caddy:
sudo systemctl restart caddyReload Configuration:
sudo systemctl reload caddyView Logs:
sudo journalctl -u caddy -fUpdate Caddyfile:
sudo nano /etc/caddy/Caddyfile
# After editing, validate and reload:
caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddyTo change the OpenCode web server password:
- Edit
~/.opencode_envand update theOPENCODE_SERVER_PASSWORDvalue:
nano ~/.opencode_env
# Update the line:
# export OPENCODE_SERVER_PASSWORD='NewPassword123!'- Re-source the file (or open a new terminal session):
source ~/.opencode_env- Restart the OpenCode web server (stop the current
opencode-webprocess and start it again):
opencode-webWhen using Caddy:
- ✅ HTTPS Encryption: All traffic is encrypted
- ✅ Password Protection: OpenCode authentication protects access (without
--oauth2-proxy) - ✅ Google OAuth2: Google login via oauth2-proxy (with
--oauth2-proxy) - ✅ Port Isolation: OpenCode port not exposed publicly
- ✅ Auto Certificate Renewal: Let's Encrypt certificates auto-renew (domain mode)
- ✅ Firewall Configuration: Only HTTPS/HTTP ports are opened
Before running the setup script with --oauth2-proxy, you need to create OAuth2 credentials in the Google Cloud Console.
- Go to https://console.cloud.google.com/
- Click Select a project → New Project
- Enter a project name (e.g.,
opencode-server) and click Create
- In the left menu, go to APIs & Services → OAuth consent screen
- Select External (for personal use) or Internal (for Google Workspace organizations)
- Fill in the required fields:
- App name: e.g.,
OpenCode - User support email: your email
- Developer contact email: your email
- App name: e.g.,
- Click Save and Continue through the remaining steps
- On the Test users step (for External apps), add the Google accounts that should have access
- Go to APIs & Services → Credentials
- Click Create Credentials → OAuth client ID
- Set Application type to Web application
- Enter a Name (e.g.,
OpenCode Caddy) - Under Authorized redirect URIs, add:
Replace
https://your.domain.com/oauth2/callbackyour.domain.comwith your actual domain. This must exactly match theredirect_urlconfigured by the setup script. - Click Create
- Copy the Client ID and Client Secret — you will pass these to the setup script
The cookie secret must be exactly 16, 24, or 32 characters. Generate one with:
python3 -c "import secrets; print(secrets.token_hex(16)[:16])"Or use any random 16/24/32-character string.
sudo bash setup.sh \
--install-caddy \
--domain your.domain.com \
--oauth2-proxy \
--google-client-id 'YOUR_CLIENT_ID.apps.googleusercontent.com' \
--google-client-secret 'YOUR_CLIENT_SECRET' \
--cookie-secret 'YOUR16BYTESECRET' \
--allowed-email-domain 'yourcompany.com'Options for restricting access:
| Goal | Flag |
|---|---|
| Allow any Google account | (omit both flags) |
Allow only @yourcompany.com accounts |
--allowed-email-domain yourcompany.com |
| Allow a single specific email | --allowed-email user@gmail.com |
Note for IP-only mode (
--ip-only --oauth2-proxy): Google OAuth2 does not accept raw IP addresses as redirect URIs. You must use a domain. If you need IP-only access, consider using password authentication (--password) instead.
Error: Browser shows "Too many redirects" or the site loops with 308 Permanent Redirect responses
Cause: This happens when Caddy is behind a CDN or reverse proxy (e.g., BunnyCDN, Cloudflare) that terminates TLS and forwards requests to the origin over HTTP. Caddy's automatic HTTP→HTTPS redirect creates an infinite loop: CDN → HTTP → Caddy → 308 to HTTPS → CDN follows → HTTP → Caddy → 308... and so on.
Solution: Re-run the setup script with --behind-proxy to configure Caddy for HTTP-only mode with auto_https off. This completely disables Caddy's TLS certificate acquisition and its HTTP→HTTPS redirect, so that the CDN/proxy can handle all TLS termination:
sudo bash setup.sh \
--install-caddy \
--domain your.domain.com \
--behind-proxy \
--password 'YourPassword'Or manually update the Caddyfile to add the global auto_https off block and use the http:// scheme:
sudo nano /etc/caddy/Caddyfile
# Replace with:
# {
# auto_https off
# }
#
# http://your.domain.com {
# reverse_proxy localhost:4096
# }
caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddyBunny CDN-specific checklist:
- In your Bunny CDN Pull Zone settings, set the Origin URL to
http://your-server-ip(plain HTTP, port 80) - Enable Force HTTPS on the Bunny CDN side (Bunny handles the TLS, not Caddy)
- Ensure your server firewall allows port 80 from Bunny CDN edge nodes
- Use
--behind-proxywhen running setup so Caddy runs withauto_https off
Error: Certificate issuance fails Solution: Ensure your domain's DNS A record points to the server's IP before running the script
Error: Can't access the site Solution: Check firewall rules:
# UFW
sudo ufw status
sudo ufw allow 443/tcp
sudo ufw allow 80/tcp
# firewalld
sudo firewall-cmd --list-all
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reloadError: Caddy service fails Solution: Check logs and configuration:
sudo journalctl -u caddy -n 50
caddy validate --config /etc/caddy/CaddyfileError: Port 443 or 4096 already in use Solution: Check what's using the port:
sudo lsof -i :443
sudo lsof -i :4096Error: oauth2-proxy service fails Solution: Check logs for errors:
sudo journalctl -u oauth2-proxy -n 50Common causes:
- Invalid
client_idorclient_secret— double-check the values from Google Cloud Console - Wrong
cookie_secretlength — it must be exactly 16, 24, or 32 characters redirect_urlmismatch — the URL in/etc/oauth2-proxy/oauth2-proxy.cfgmust exactly match the Authorized redirect URI registered in Google Cloud Console
Error: After Google login, redirected to an error page
Cause: The redirect_url in the oauth2-proxy config does not match the Authorized redirect URI in Google Cloud Console.
Solution:
- Check the configured redirect URL:
grep redirect_url /etc/oauth2-proxy/oauth2-proxy.cfg
- Go to Google Cloud Console → APIs & Services → Credentials
- Edit your OAuth 2.0 Client ID and ensure the Authorized redirect URIs list contains exactly the URL from step 1.
Error: oauth2-proxy shows "Access Denied" or "Unauthorized" after successful Google login Cause: The logged-in email does not match the configured restrictions.
Solution: Check the email restriction in /etc/oauth2-proxy/oauth2-proxy.cfg:
sudo cat /etc/oauth2-proxy/oauth2-proxy.cfg- If you used
--allowed-email-domain, ensure the user's email ends with@thatdomain.com - If you used
--allowed-email, ensure the exact email address matches - To allow all Google accounts, remove the restriction:
sudo nano /etc/oauth2-proxy/oauth2-proxy.cfg # Change to: email_domains = [ "*" ] sudo systemctl restart oauth2-proxy
- ✅ Ubuntu/Debian (apt-based)
- ✅ RHEL/Fedora/CentOS (dnf-based)
- Root or sudo access
- Clean server instance (recommended)
- For domain-based HTTPS: Valid domain with DNS configured
- Internet connection
Contributions are welcome! Please feel free to submit a Pull Request.
See LICENSE file for details.
For issues or questions:
- Open an issue on GitHub
- Check the troubleshooting section above
- Review Caddy documentation: https://caddyserver.com/docs/
- Review OpenCode documentation: https://opencode.ai/docs/