# Runbook: Self-hosted Pilot ## Gaussian Wellworks ## 1. Runbook Objective ## 2. Critical Dependencies ### 2.1 Business Domain Ownership ### 2.2 Administrative Access ### 2.3 Decision Checklist ## 3. Detailed Runbook ### 3.1 Create Digital Ocean Account ### 3.2 Create SSH Key Pair (Windows 11) **Generate key** ```Powershell Powershell ssh-keygen -t ed25519 -C "admin@company.com" ``` Accept default path: ``` C:\Users\\.ssh\id_ed25519 ``` **Copy public key** ```Powershell Powershell Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | Set-Clipboard ``` **Upload to DigitalOcean** - Settings → Security → SSH Keys → Add - Name: admin-windows11 **Validation** [✔] Key visible in DigitalOcean ### 3.3 Provision Droplet **Configuration** ``` Image: Ubuntu 22.04 LTS Plan: Premium Intel Size: 4 vCPU / 8GB RAM Region: closest to users Auth: SSH key Backups: Enabled Hostname: gw-drive-01 ``` **Test SSH** ```Powershell ssh root@ ``` **Validation** [✔] SSH login works [✔] IP recorded ### 3.4 Initial Server Configuration **Create admin user** ```Bash Bash adduser adminuser usermod -aG sudo adminuser ``` **Copy SSH key** ```Bash Bash rsync --archive --chown=adminuser:adminuser ~/.ssh /home/adminuser ``` **Disable root login** ```Bash Bash sudo tee /etc/ssh/sshd_config > /dev/null << 'EOF' PermitRootLogin no PasswordAuthentication no EOF ``` ```Bash Bash sudo systemctl restart ssh ``` **Test login** ```Powershell Powershell ssh adminuser@ ``` **Update system** ```Bash Bash sudo apt update sudo apt upgrade -y sudo apt autoremove -y ``` **Validation** [✔] adminuser login works [✔] root login blocked [✔] system updated ### 3.5 Configure Firewall **DigitalOcean Firewall** Allow: ``` 22 (SSH) → your IP 80 (HTTP) → all 443 (HTTPS) → all ``` Attach to droplet. **UFW** ```Bash Bash sudo apt install ufw -y sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable ``` **Validation** ```Bash Bash sudo ufw status ``` ### 3.6 Configure DNS **Create A record** ``` Host: drive Value: TTL: 300 ``` **Validate** ```Powershell Powershell nslookup drive.company.com ``` *Note* ⚠ DNS propagation required before SSL ### 3.7 Install Docker ```Bash Bash sudo apt install ca-certificates curl gnupg -y sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg ``` ```Bash Bash echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null ``` ```Bash Bash sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y ``` **Enable Docker** ```Bash Bash sudo systemctl enable docker sudo systemctl start docker sudo usermod -aG docker adminuser ``` Reconnect SSH. **Test** ```Bash Bash docker run hello-world ``` ### 3.10 Deploy Nextcloud **Create directory** ```Bash Bash mkdir -p ~/apps/nextcloud cd ~/apps/nextcloud ``` **Create .env** ```Bash Bash sudo tee .env > /dev/null << 'EOF' MYSQL_ROOT_PASSWORD=StrongRootPassword MYSQL_PASSWORD=StrongDBPassword MYSQL_DATABASE=nextcloud MYSQL_USER=nextcloud NEXTCLOUD_ADMIN_USER=admin NEXTCLOUD_ADMIN_PASSWORD=StrongAdminPassword MYSQL_HOST=db REDIS_HOST=redis NEXTCLOUD_TRUSTED_DOMAINS= EOF ``` **Create docker-compose** ```Bash Bash sudo tee docker-compose.yml > /dev/null << 'EOF' version: '3.9' services: db: image: mariadb:10.6 restart: always env_file: - .env volumes: - db_data:/var/lib/mysql redis: image: redis:7-alpine restart: always app: image: nextcloud:apache restart: always ports: - "8080:80" env_file: - .env depends_on: - db - redis volumes: - nextcloud_data:/var/www/html volumes: db_data: nextcloud_data: EOF ``` **Start** ```Bash Bash docker compose up -d ``` **Validate** ``` http://:8080 ``` ### 3.11 Validate Internal Access **Test:** - [ ] Login - [ ] Upload file - [ ] Create folder - [ ] Restart containers - [ ] Data persists ### 3.12 Install Nginx ```Bash Bash sudo apt install nginx -y sudo systemctl enable nginx sudo systemctl start nginx ``` **Test:** ``` http:// → nginx welcome page ``` ### 3.13 Configure HTTP Reverse Proxy (Staging Only) ```Bash Bash sudo tee /etc/nginx/sites-available/nextcloud > /dev/null << 'EOF' server { listen 80; server_name drive.company.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto http; } } EOF ``` **Enable:** ```Bash Bash sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` *Important* - ⚠ This step is NOT fully usable yet - ⚠ HTTPS may redirect and show 404 (expected) ### 3.14 Install SSL (Certbot) ```Bash Bash sudo apt install certbot python3-certbot-nginx -y sudo certbot --nginx -d drive.company.com ``` **Validate** - ✔ Certificate issued - ✔ HTTPS reachable - ⚠ May still show 404 (expected) ### 3.15 Finalize HTTPS Reverse Proxy **Replace config** ```Bash Bash sudo tee /etc/nginx/sites-available/nextcloud > /dev/null << 'EOF' server { listen 80; server_name drive.company.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name drive.company.com; ssl_certificate /etc/letsencrypt/live/drive.company.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/drive.company.com/privkey.pem; client_max_body_size 10G; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } } EOF ``` **Reload** ```Bash Bash sudo nginx -t sudo systemctl reload nginx ``` **Fix Nextcloud** ```Bash Bash docker exec -u www-data nextcloud-app php occ config:system:set trusted_domains 1 --value="drive.company.com" docker exec -u www-data nextcloud-app php occ config:system:set overwrite.cli.url --value="https://drive.company.com" docker exec -u www-data nextcloud-app php occ config:system:set overwriteprotocol --value="https" ``` **Final Validation** https://drive.company.com - ✔ Login works - ✔ Upload works - ✔ Download works - ✔ No warnings - ✔ HTTPS padlock present **Cleanup** ```Bash Bash sudo ufw delete allow 8080/tcp ``` ✅ Final Outcome - ✔ Secure Nextcloud deployment - ✔ HTTPS reverse proxy functional - ✔ Docker-based architecture - ✔ Ready for users and workflows ### 3.16 Create Email Provider Account (Fastmail) **Objective** Establish a business-grade email platform using Fastmail so that: - domain-based email (user@company.com) - reliable outbound mail (for Nextcloud notifications later) - foundation for SPF/DKIM/DMARC **Architectural Context** Email is not hosted on your droplet. Your infrastructure will later integrate with it for: - Nextcloud notifications - User accounts - Client communication **Prerequisites** - Domain ownership confirmed - Access to DNS registrar - Decision: Fastmail selected (per runbook) **Step 1 - Create Fastmail Account** Go to: https://www.fastmail.com 1.1 Sign up - Click Sign Up - Choose a plan (Standard is sufficient for pilot) - Enter: ``` Name Email (temporary Fastmail domain) Password ``` 1.2 Complete account creation You will land in the Fastmail admin interface. **Step 2 - Add Your Domain** 2.1 Navigate to Domains In Fastmail: ``` Settings → Domains → Add Domain ``` 2.2 Enter your domain ``` company.com ``` Click: ``` Add Domain ``` **Step 3 - Verify Domain Ownership** Fastmail will present a TXT verification record. 3.1 Copy TXT record Example: ``` Type: TXT Name: @ Value: fm-domain-verification=abc123xyz ``` 3.2 Add to DNS (Registrar) In your DNS provider (e.g., GoDaddy or Cloudflare): Add: ``` Type: TXT Host: @ Value: fm-domain-verification=abc123xyz TTL: 300 ``` 3.3 Wait for propagation ``` ~5–15 minutes typical, but may be as much as 24 hours ``` 3.4 Verify in Fastmail Click: ``` Verify Domain ``` Expected Result - Domain verified successfully **Step 4 - Set Domain as Default** In Fastmail: ``` Settings → Domains → company.com → Set as default ``` This ensures: - new users → user@company.com **Step 5 - Create Initial Admin Mailbox** 5.1 Navigate to Users ``` Settings → Users → Add User ``` 5.2 Create admin user Example: ``` Username: admin Email: admin@company.com Password: (strong password) ``` 5.3 Save Expected Result - Mailbox created - Can send/receive (after DNS setup in 3.8) **Step 6 - Basic Access Test (Pre-DNS)** Login via: ``` https://app.fastmail.com ``` Expected - Inbox accessible - Account functional (even before MX setup) - Email is NOT functional yet **Validation Checklist** - [✔] Fastmail account created - [✔] Domain added to Fastmail - [✔] TXT verification record created - [✔] Domain verified - [✔] Default domain set - [✔] Admin mailbox created - [✔] Able to login to Fastmail ### 3.17 Configure Email DNS **Objective** Make your domain fully capable of sending and receiving email by configuring DNS records required by Fastmail: - Incoming mail routing (MX) - Authorized senders (SPF) - Cryptographic signing (DKIM) - Policy & reporting (DMARC) *Changing MX records WILL impact live email.* If the client currently uses another provider: - Schedule a cutover window - Lower TTL beforehand (if possible) - Expect brief delivery delays during propagation **Prerequisites** - Domain added & verified in Fastmail (Task 3.7) - Access to DNS registrar - Admin mailbox created **Step 1 - Gather Fastmail DNS Records** In Fastmail: ``` Settings → Domains → company.com → DNS Settings ``` Fastmail will provide all required records. Use those exact values if they differ from below. **Step 2 - Configure MX Records (Mail Routing)** 2.1 Remove Existing MX Records ⚠️ Only do this at cutover time. 2.2 Add Fastmail MX Records ``` Type: MX Host: @ Priority: 10 Value: in1-smtp.messagingengine.com Type: MX Host: @ Priority: 20 Value: in2-smtp.messagingengine.com ``` **Step 3 - Configure SPF (Sender Authorization)** 3.1 Add SPF record ``` Type: TXT Host: @ Value: v=spf1 include:spf.messagingengine.com ~all TTL: 300 ``` Notes - Only ONE SPF record per domain - Merge with existing SPF if present **Step 4 - Configure DKIM (Email Signing)** 4.1 Get DKIM from Fastmail In Fastmail DNS settings, copy the DKIM record. Example: ``` Type: CNAME Host: fm1._domainkey Value: fm1.company.com.dkim.fmhosted.com Type: CNAME Host: fm2._domainkey Value: fm2.company.com.dkim.fmhosted.com Type: CNAME Host: fm3._domainkey Value: fm3.company.com.dkim.fmhosted.com 4.2 Add all DKIM records ``` These enable: - Email authenticity verification - Better deliverability **Step 5 - Configure DMARC (Policy & Reporting)** 5.1 Add DMARC record ``` Type: TXT Host: _dmarc Value: v=DMARC1; p=none; rua=mailto:admin@company.com TTL: 300 ``` Explanation - p=none → monitor only (safe for pilot) - rua= → aggregate reports sent to admin mailbox Future Hardening Later you can move to: - p=quarantine → soft enforcement - p=reject → full enforcement **Step 6 - Verify DNS Propagation** 6.1 Check MX nslookup -type=MX company.com Expected: in1-smtp.messagingengine.com in2-smtp.messagingengine.com 6.2 Check SPF nslookup -type=TXT company.com 6.3 Check DKIM nslookup -type=CNAME fm1._domainkey.company.com 6.4 Check DMARC nslookup -type=TXT _dmarc.company.com **Step 7 - Functional Email Test** 7.1 Send inbound test From Gmail/Outlook: ``` Send email to: admin@company.com ``` Expected: - Email received in Fastmail inbox 7.2 Send outbound test From Fastmail: ``` Send email to external address ``` Expected: - Email delivered - Not marked as spam **Step 8 - Validate Deliverability (Recommended)** Use: - mail-tester.com - mxtoolbox.com Expected - SPF pass - DKIM pass - DMARC pass - Low spam score **Step 9 - Propagation Expectations** - MX: 5–30 minutes typical - SPF: immediate to 1 hour - DKIM: 15–60 minutes - DMARC: 15–60 minutes Worst case: Up to 24 hours **Validation Checklist** - [✔] MX records updated - [✔] SPF record present - [✔] DKIM records configured (all 3) - [✔] DMARC record created - [✔] DNS resolves correctly - [✔] Email received externally - [✔] Email sent externally - [✔] Deliverability validated