Merge pull request #38 from bnema/docs/wiki-cleanup-and-guides
docs(wiki): cleanup broken links and add secure-vps-setup guide
7 files · +415 -27
M docs/cli/index.md +1 -1
| @@ -25,7 +25,7 @@ Commands are organized by where they run: | |||
| 25 | 25 | | `gordon deploy` | Manually deploy or redeploy a route | [serve](./serve.md#gordon-deploy) | | |
| 26 | 26 | | `gordon reload` | Reload configuration and sync containers | [serve](./serve.md#gordon-reload) | | |
| 27 | 27 | | `gordon logs` | Display Gordon process or container logs | [serve](./serve.md#gordon-logs) | | |
| 28 | - | | `gordon status` | Show Gordon server status | - | | |
| 28 | + | | `gordon status` | Show Gordon server status | [status](./status.md) | | |
| 29 | 29 | ||
| 30 | 30 | ## Client Commands | |
| 31 | 31 | ||
| @@ -25,7 +25,7 @@ Commands are organized by where they run: | |||||
| 25 | | `gordon deploy` | Manually deploy or redeploy a route | [serve](./serve.md#gordon-deploy) | | 25 | | `gordon deploy` | Manually deploy or redeploy a route | [serve](./serve.md#gordon-deploy) | | ||
| 26 | | `gordon reload` | Reload configuration and sync containers | [serve](./serve.md#gordon-reload) | | 26 | | `gordon reload` | Reload configuration and sync containers | [serve](./serve.md#gordon-reload) | | ||
| 27 | | `gordon logs` | Display Gordon process or container logs | [serve](./serve.md#gordon-logs) | | 27 | | `gordon logs` | Display Gordon process or container logs | [serve](./serve.md#gordon-logs) | | ||
| 28 | - | | `gordon status` | Show Gordon server status | - | | 28 | + | | `gordon status` | Show Gordon server status | [status](./status.md) | |
| 29 | 29 | ||||
| 30 | ## Client Commands | 30 | ## Client Commands | ||
| 31 | 31 | ||||
A docs/cli/status.md +105 -0
| @@ -0,0 +1,105 @@ | |||
| 1 | + | # Status Command | |
| 2 | + | ||
| 3 | + | Show Gordon server status and container health. | |
| 4 | + | ||
| 5 | + | ## gordon status | |
| 6 | + | ||
| 7 | + | Display server configuration and container status for all routes. | |
| 8 | + | ||
| 9 | + | ```bash | |
| 10 | + | gordon status --remote https://gordon.mydomain.com --token $TOKEN | |
| 11 | + | ``` | |
| 12 | + | ||
| 13 | + | > **Note:** The status command requires remote mode. Use `--remote` flag or set `GORDON_REMOTE` environment variable. | |
| 14 | + | ||
| 15 | + | ### Output | |
| 16 | + | ||
| 17 | + | ``` | |
| 18 | + | Gordon Status | |
| 19 | + | ||
| 20 | + | Domain: registry.example.com | |
| 21 | + | Registry Port: 5000 | |
| 22 | + | Server Port: 8080 | |
| 23 | + | Routes: 3 | |
| 24 | + | Auto-Route: true | |
| 25 | + | Network Isolation: false | |
| 26 | + | ||
| 27 | + | Container Status: | |
| 28 | + | app.example.com: running | |
| 29 | + | api.example.com: running | |
| 30 | + | worker.example.com: stopped | |
| 31 | + | ``` | |
| 32 | + | ||
| 33 | + | ### Information Displayed | |
| 34 | + | ||
| 35 | + | | Field | Description | | |
| 36 | + | |-------|-------------| | |
| 37 | + | | Domain | Registry domain from configuration | | |
| 38 | + | | Registry Port | Docker registry port | | |
| 39 | + | | Server Port | Gordon admin API port | | |
| 40 | + | | Routes | Total configured routes | | |
| 41 | + | | Auto-Route | Whether auto-routing is enabled | | |
| 42 | + | | Network Isolation | Whether network isolation is enabled | | |
| 43 | + | | Container Status | Status of each route's container | | |
| 44 | + | ||
| 45 | + | ### Container States | |
| 46 | + | ||
| 47 | + | | State | Description | | |
| 48 | + | |-------|-------------| | |
| 49 | + | | running | Container is running and healthy | | |
| 50 | + | | stopped | Container was stopped | | |
| 51 | + | | exited | Container exited (check logs for errors) | | |
| 52 | + | | paused | Container is paused | | |
| 53 | + | | unknown | Unable to determine container state | | |
| 54 | + | ||
| 55 | + | ## Flags | |
| 56 | + | ||
| 57 | + | The status command uses global flags for remote access: | |
| 58 | + | ||
| 59 | + | | Flag | Description | | |
| 60 | + | |------|-------------| | |
| 61 | + | | `--remote` | Remote Gordon URL | | |
| 62 | + | | `--token` | Authentication token | | |
| 63 | + | ||
| 64 | + | ## Environment Variables | |
| 65 | + | ||
| 66 | + | | Variable | Description | | |
| 67 | + | |----------|-------------| | |
| 68 | + | | `GORDON_REMOTE` | Remote Gordon URL | | |
| 69 | + | | `GORDON_TOKEN` | Authentication token | | |
| 70 | + | ||
| 71 | + | ## Examples | |
| 72 | + | ||
| 73 | + | ### Check Remote Server Status | |
| 74 | + | ||
| 75 | + | ```bash | |
| 76 | + | # Using flags | |
| 77 | + | gordon status --remote https://gordon.mydomain.com --token $TOKEN | |
| 78 | + | ||
| 79 | + | # Using environment variables | |
| 80 | + | export GORDON_REMOTE=https://gordon.mydomain.com | |
| 81 | + | export GORDON_TOKEN=your-token | |
| 82 | + | gordon status | |
| 83 | + | ``` | |
| 84 | + | ||
| 85 | + | ### Quick Health Check | |
| 86 | + | ||
| 87 | + | ```bash | |
| 88 | + | # Check if all containers are running | |
| 89 | + | gordon status --remote https://gordon.mydomain.com --token $TOKEN | grep -E "(running|stopped|exited)" | |
| 90 | + | ``` | |
| 91 | + | ||
| 92 | + | ## Required Permissions | |
| 93 | + | ||
| 94 | + | The status endpoint requires `admin:status:read` scope in the authentication token. | |
| 95 | + | ||
| 96 | + | ```bash | |
| 97 | + | # Generate token with required scope | |
| 98 | + | gordon auth token generate --subject admin --scopes admin:status:read | |
| 99 | + | ``` | |
| 100 | + | ||
| 101 | + | ## Related | |
| 102 | + | ||
| 103 | + | - [Serve Command](./serve.md) | |
| 104 | + | - [Routes Command](./routes.md) | |
| 105 | + | - [Remote CLI Management](/wiki/guides/remote-cli.md) | |
| @@ -0,0 +1,105 @@ | |||||
| 1 | + | # Status Command | |||
| 2 | + | ||||
| 3 | + | Show Gordon server status and container health. | |||
| 4 | + | ||||
| 5 | + | ## gordon status | |||
| 6 | + | ||||
| 7 | + | Display server configuration and container status for all routes. | |||
| 8 | + | ||||
| 9 | + | ```bash | |||
| 10 | + | gordon status --remote https://gordon.mydomain.com --token $TOKEN | |||
| 11 | + | ``` | |||
| 12 | + | ||||
| 13 | + | > **Note:** The status command requires remote mode. Use `--remote` flag or set `GORDON_REMOTE` environment variable. | |||
| 14 | + | ||||
| 15 | + | ### Output | |||
| 16 | + | ||||
| 17 | + | ``` | |||
| 18 | + | Gordon Status | |||
| 19 | + | ||||
| 20 | + | Domain: registry.example.com | |||
| 21 | + | Registry Port: 5000 | |||
| 22 | + | Server Port: 8080 | |||
| 23 | + | Routes: 3 | |||
| 24 | + | Auto-Route: true | |||
| 25 | + | Network Isolation: false | |||
| 26 | + | ||||
| 27 | + | Container Status: | |||
| 28 | + | app.example.com: running | |||
| 29 | + | api.example.com: running | |||
| 30 | + | worker.example.com: stopped | |||
| 31 | + | ``` | |||
| 32 | + | ||||
| 33 | + | ### Information Displayed | |||
| 34 | + | ||||
| 35 | + | | Field | Description | | |||
| 36 | + | |-------|-------------| | |||
| 37 | + | | Domain | Registry domain from configuration | | |||
| 38 | + | | Registry Port | Docker registry port | | |||
| 39 | + | | Server Port | Gordon admin API port | | |||
| 40 | + | | Routes | Total configured routes | | |||
| 41 | + | | Auto-Route | Whether auto-routing is enabled | | |||
| 42 | + | | Network Isolation | Whether network isolation is enabled | | |||
| 43 | + | | Container Status | Status of each route's container | | |||
| 44 | + | ||||
| 45 | + | ### Container States | |||
| 46 | + | ||||
| 47 | + | | State | Description | | |||
| 48 | + | |-------|-------------| | |||
| 49 | + | | running | Container is running and healthy | | |||
| 50 | + | | stopped | Container was stopped | | |||
| 51 | + | | exited | Container exited (check logs for errors) | | |||
| 52 | + | | paused | Container is paused | | |||
| 53 | + | | unknown | Unable to determine container state | | |||
| 54 | + | ||||
| 55 | + | ## Flags | |||
| 56 | + | ||||
| 57 | + | The status command uses global flags for remote access: | |||
| 58 | + | ||||
| 59 | + | | Flag | Description | | |||
| 60 | + | |------|-------------| | |||
| 61 | + | | `--remote` | Remote Gordon URL | | |||
| 62 | + | | `--token` | Authentication token | | |||
| 63 | + | ||||
| 64 | + | ## Environment Variables | |||
| 65 | + | ||||
| 66 | + | | Variable | Description | | |||
| 67 | + | |----------|-------------| | |||
| 68 | + | | `GORDON_REMOTE` | Remote Gordon URL | | |||
| 69 | + | | `GORDON_TOKEN` | Authentication token | | |||
| 70 | + | ||||
| 71 | + | ## Examples | |||
| 72 | + | ||||
| 73 | + | ### Check Remote Server Status | |||
| 74 | + | ||||
| 75 | + | ```bash | |||
| 76 | + | # Using flags | |||
| 77 | + | gordon status --remote https://gordon.mydomain.com --token $TOKEN | |||
| 78 | + | ||||
| 79 | + | # Using environment variables | |||
| 80 | + | export GORDON_REMOTE=https://gordon.mydomain.com | |||
| 81 | + | export GORDON_TOKEN=your-token | |||
| 82 | + | gordon status | |||
| 83 | + | ``` | |||
| 84 | + | ||||
| 85 | + | ### Quick Health Check | |||
| 86 | + | ||||
| 87 | + | ```bash | |||
| 88 | + | # Check if all containers are running | |||
| 89 | + | gordon status --remote https://gordon.mydomain.com --token $TOKEN | grep -E "(running|stopped|exited)" | |||
| 90 | + | ``` | |||
| 91 | + | ||||
| 92 | + | ## Required Permissions | |||
| 93 | + | ||||
| 94 | + | The status endpoint requires `admin:status:read` scope in the authentication token. | |||
| 95 | + | ||||
| 96 | + | ```bash | |||
| 97 | + | # Generate token with required scope | |||
| 98 | + | gordon auth token generate --subject admin --scopes admin:status:read | |||
| 99 | + | ``` | |||
| 100 | + | ||||
| 101 | + | ## Related | |||
| 102 | + | ||||
| 103 | + | - [Serve Command](./serve.md) | |||
| 104 | + | - [Routes Command](./routes.md) | |||
| 105 | + | - [Remote CLI Management](/wiki/guides/remote-cli.md) | |||
M wiki/examples/index.md +0 -18
| @@ -6,7 +6,6 @@ Annotated Gordon configuration examples for common scenarios. | |||
| 6 | 6 | ||
| 7 | 7 | - [Minimal Configuration](./minimal.md) - Simplest working setup | |
| 8 | 8 | - [Production Configuration](./production.md) - Full production setup | |
| 9 | - | - [Development Configuration](./development.md) - Local development setup | |
| 10 | 9 | ||
| 11 | 10 | ## Quick Reference | |
| 12 | 11 | ||
| @@ -20,23 +19,6 @@ gordon_domain = "gordon.local" | |||
| 20 | 19 | "app.local" = "myapp:latest" | |
| 21 | 20 | ``` | |
| 22 | 21 | ||
| 23 | - | ### Development | |
| 24 | - | ||
| 25 | - | ```toml | |
| 26 | - | [server] | |
| 27 | - | port = 8080 | |
| 28 | - | gordon_domain = "gordon.local" | |
| 29 | - | ||
| 30 | - | [auth] | |
| 31 | - | enabled = false | |
| 32 | - | ||
| 33 | - | [auto_route] | |
| 34 | - | enabled = true | |
| 35 | - | ||
| 36 | - | [routes] | |
| 37 | - | "app.local" = "myapp:latest" | |
| 38 | - | ``` | |
| 39 | - | ||
| 40 | 22 | ### Production | |
| 41 | 23 | ||
| 42 | 24 | ```toml | |
| @@ -6,7 +6,6 @@ Annotated Gordon configuration examples for common scenarios. | |||||
| 6 | 6 | ||||
| 7 | - [Minimal Configuration](./minimal.md) - Simplest working setup | 7 | - [Minimal Configuration](./minimal.md) - Simplest working setup | ||
| 8 | - [Production Configuration](./production.md) - Full production setup | 8 | - [Production Configuration](./production.md) - Full production setup | ||
| 9 | - | - [Development Configuration](./development.md) - Local development setup | |||
| 10 | 9 | ||||
| 11 | ## Quick Reference | 10 | ## Quick Reference | ||
| 12 | 11 | ||||
| @@ -20,23 +19,6 @@ gordon_domain = "gordon.local" | |||||
| 20 | "app.local" = "myapp:latest" | 19 | "app.local" = "myapp:latest" | ||
| 21 | ``` | 20 | ``` | ||
| 22 | 21 | ||||
| 23 | - | ### Development | |||
| 24 | - | ||||
| 25 | - | ```toml | |||
| 26 | - | [server] | |||
| 27 | - | port = 8080 | |||
| 28 | - | gordon_domain = "gordon.local" | |||
| 29 | - | ||||
| 30 | - | [auth] | |||
| 31 | - | enabled = false | |||
| 32 | - | ||||
| 33 | - | [auto_route] | |||
| 34 | - | enabled = true | |||
| 35 | - | ||||
| 36 | - | [routes] | |||
| 37 | - | "app.local" = "myapp:latest" | |||
| 38 | - | ``` | |||
| 39 | - | ||||
| 40 | ### Production | 22 | ### Production | ||
| 41 | 23 | ||||
| 42 | ```toml | 24 | ```toml | ||
M wiki/guides/index.md +1 -1
| @@ -4,12 +4,12 @@ In-depth guides for specific Gordon setups and integrations. | |||
| 4 | 4 | ||
| 5 | 5 | ## Server Setup | |
| 6 | 6 | ||
| 7 | + | - [Secure VPS Setup](./secure-vps-setup.md) - Tailscale SSH, firewalld, and CrowdSec | |
| 7 | 8 | - [Running Gordon in a Container](./running-in-container.md) - Deploy Gordon itself in Docker or Podman | |
| 8 | 9 | - [Podman Rootless Setup](./podman-rootless.md) - Enhanced security with rootless containers | |
| 9 | 10 | ||
| 10 | 11 | ## TLS & Load Balancing | |
| 11 | 12 | ||
| 12 | - | - [Cloudflare DNS & Proxy Setup](./cloudflare-setup.md) - HTTPS termination with Cloudflare (recommended) | |
| 13 | 13 | - [AWS Application Load Balancer](./aws-alb.md) - Deploy behind AWS ALB | |
| 14 | 14 | - [Kubernetes Ingress](./kubernetes-ingress.md) - Deploy behind Kubernetes ingress controller | |
| 15 | 15 | ||
| @@ -4,12 +4,12 @@ In-depth guides for specific Gordon setups and integrations. | |||||
| 4 | 4 | ||||
| 5 | ## Server Setup | 5 | ## Server Setup | ||
| 6 | 6 | ||||
| 7 | + | - [Secure VPS Setup](./secure-vps-setup.md) - Tailscale SSH, firewalld, and CrowdSec | |||
| 7 | - [Running Gordon in a Container](./running-in-container.md) - Deploy Gordon itself in Docker or Podman | 8 | - [Running Gordon in a Container](./running-in-container.md) - Deploy Gordon itself in Docker or Podman | ||
| 8 | - [Podman Rootless Setup](./podman-rootless.md) - Enhanced security with rootless containers | 9 | - [Podman Rootless Setup](./podman-rootless.md) - Enhanced security with rootless containers | ||
| 9 | 10 | ||||
| 10 | ## TLS & Load Balancing | 11 | ## TLS & Load Balancing | ||
| 11 | 12 | ||||
| 12 | - | - [Cloudflare DNS & Proxy Setup](./cloudflare-setup.md) - HTTPS termination with Cloudflare (recommended) | |||
| 13 | - [AWS Application Load Balancer](./aws-alb.md) - Deploy behind AWS ALB | 13 | - [AWS Application Load Balancer](./aws-alb.md) - Deploy behind AWS ALB | ||
| 14 | - [Kubernetes Ingress](./kubernetes-ingress.md) - Deploy behind Kubernetes ingress controller | 14 | - [Kubernetes Ingress](./kubernetes-ingress.md) - Deploy behind Kubernetes ingress controller | ||
| 15 | 15 | ||||
A wiki/guides/secure-vps-setup.md +301 -0
| @@ -0,0 +1,301 @@ | |||
| 1 | + | # Secure VPS Setup | |
| 2 | + | ||
| 3 | + | A production-ready VPS security configuration with Tailscale-only SSH access and CrowdSec intrusion prevention. | |
| 4 | + | ||
| 5 | + | ## Overview | |
| 6 | + | ||
| 7 | + | This guide configures three security layers: | |
| 8 | + | ||
| 9 | + | | Layer | Protection | | |
| 10 | + | |-------|------------| | |
| 11 | + | | SSH | Tailscale only (invisible to internet) | | |
| 12 | + | | Firewall | firewalld with DROP policy | | |
| 13 | + | | IPS | CrowdSec with community blocklists | | |
| 14 | + | ||
| 15 | + | ## Prerequisites | |
| 16 | + | ||
| 17 | + | - Fresh Ubuntu 24.04 VPS | |
| 18 | + | - Tailscale account | |
| 19 | + | - Initial SSH access via public IP (temporary) | |
| 20 | + | - Cloudflare account for HTTPS termination | |
| 21 | + | ||
| 22 | + | --- | |
| 23 | + | ||
| 24 | + | ## Step 1: Initial System Hardening | |
| 25 | + | ||
| 26 | + | Before anything else, enable automatic security updates and time sync: | |
| 27 | + | ||
| 28 | + | ```bash | |
| 29 | + | # Automatic security updates | |
| 30 | + | apt update && apt install -y unattended-upgrades | |
| 31 | + | dpkg-reconfigure -plow unattended-upgrades | |
| 32 | + | ||
| 33 | + | # Verify time sync (enabled by default on Ubuntu 24.04) | |
| 34 | + | timedatectl set-ntp true | |
| 35 | + | timedatectl status | |
| 36 | + | ||
| 37 | + | # Basic kernel hardening | |
| 38 | + | cat > /etc/sysctl.d/99-security.conf << 'EOF' | |
| 39 | + | # SYN flood protection | |
| 40 | + | net.ipv4.tcp_syncookies = 1 | |
| 41 | + | ||
| 42 | + | # Reverse path filtering (anti-spoofing) | |
| 43 | + | net.ipv4.conf.all.rp_filter = 1 | |
| 44 | + | net.ipv4.conf.default.rp_filter = 1 | |
| 45 | + | ||
| 46 | + | # Disable ICMP redirects | |
| 47 | + | net.ipv4.conf.all.accept_redirects = 0 | |
| 48 | + | net.ipv4.conf.all.send_redirects = 0 | |
| 49 | + | net.ipv6.conf.all.accept_redirects = 0 | |
| 50 | + | ||
| 51 | + | # Ignore ICMP broadcasts | |
| 52 | + | net.ipv4.icmp_echo_ignore_broadcasts = 1 | |
| 53 | + | EOF | |
| 54 | + | ||
| 55 | + | sysctl --system | |
| 56 | + | ``` | |
| 57 | + | ||
| 58 | + | ## Step 2: Install Tailscale | |
| 59 | + | ||
| 60 | + | > **Important**: Use the official repository, NOT the Snap package (has AppArmor issues with Tailscale SSH on Ubuntu 24.04). | |
| 61 | + | ||
| 62 | + | ```bash | |
| 63 | + | # Add official Tailscale repository | |
| 64 | + | curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.noarmor.gpg | \ | |
| 65 | + | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null | |
| 66 | + | curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.tailscale-keyring.list | \ | |
| 67 | + | tee /etc/apt/sources.list.d/tailscale.list | |
| 68 | + | ||
| 69 | + | # Install | |
| 70 | + | apt update | |
| 71 | + | apt install -y tailscale | |
| 72 | + | ``` | |
| 73 | + | ||
| 74 | + | ## Step 3: Connect Tailscale with SSH | |
| 75 | + | ||
| 76 | + | ```bash | |
| 77 | + | tailscale up --ssh | |
| 78 | + | ``` | |
| 79 | + | ||
| 80 | + | This outputs an authentication URL. Open it and authenticate with your Tailscale account. | |
| 81 | + | ||
| 82 | + | **Important**: After connecting, go to https://login.tailscale.com/admin/machines and **disable key expiry** for this server to prevent lockouts. | |
| 83 | + | ||
| 84 | + | ## Step 4: Configure firewalld | |
| 85 | + | ||
| 86 | + | ```bash | |
| 87 | + | # Install firewalld | |
| 88 | + | apt install -y firewalld | |
| 89 | + | ||
| 90 | + | # Enable and start | |
| 91 | + | systemctl enable --now firewalld | |
| 92 | + | ||
| 93 | + | # Trust Tailscale interface (allows all traffic from your tailnet) | |
| 94 | + | firewall-cmd --permanent --zone=trusted --add-interface=tailscale0 | |
| 95 | + | ||
| 96 | + | # Allow Tailscale UDP port (WireGuard) | |
| 97 | + | firewall-cmd --permanent --add-port=41641/udp | |
| 98 | + | ||
| 99 | + | # Allow web traffic (for Gordon reverse proxy) | |
| 100 | + | firewall-cmd --permanent --add-service=http | |
| 101 | + | firewall-cmd --permanent --add-service=https | |
| 102 | + | ||
| 103 | + | # Apply changes | |
| 104 | + | firewall-cmd --reload | |
| 105 | + | ``` | |
| 106 | + | ||
| 107 | + | > **Note**: Gordon's registry is proxied through port 80/443 via `gordon_domain`. Port 5000 does NOT need to be exposed publicly. | |
| 108 | + | ||
| 109 | + | ## Step 5: Verify Tailscale SSH Works | |
| 110 | + | ||
| 111 | + | From another machine on your tailnet: | |
| 112 | + | ||
| 113 | + | ```bash | |
| 114 | + | # Get the server's Tailscale hostname | |
| 115 | + | tailscale status | |
| 116 | + | ||
| 117 | + | # Connect from your local machine | |
| 118 | + | ssh root@<tailscale-hostname> | |
| 119 | + | # or | |
| 120 | + | tailscale ssh root@<tailscale-hostname> | |
| 121 | + | ``` | |
| 122 | + | ||
| 123 | + | ## Step 6: Block Public SSH | |
| 124 | + | ||
| 125 | + | **Only proceed once Tailscale SSH is confirmed working.** | |
| 126 | + | ||
| 127 | + | ```bash | |
| 128 | + | # Remove SSH from public zone | |
| 129 | + | firewall-cmd --permanent --remove-service=ssh | |
| 130 | + | ||
| 131 | + | # Set default policy to DROP all unmatched traffic | |
| 132 | + | firewall-cmd --permanent --zone=public --set-target=DROP | |
| 133 | + | ||
| 134 | + | # Apply changes | |
| 135 | + | firewall-cmd --reload | |
| 136 | + | ``` | |
| 137 | + | ||
| 138 | + | Verify public SSH is blocked: | |
| 139 | + | ||
| 140 | + | ```bash | |
| 141 | + | # From another machine (not on tailnet) | |
| 142 | + | ssh -o ConnectTimeout=5 root@<public-ip> # Should timeout | |
| 143 | + | ``` | |
| 144 | + | ||
| 145 | + | --- | |
| 146 | + | ||
| 147 | + | ## Step 7: Install CrowdSec | |
| 148 | + | ||
| 149 | + | ```bash | |
| 150 | + | # Add CrowdSec repo (NOT Ubuntu's outdated version) | |
| 151 | + | curl -s https://install.crowdsec.net | bash | |
| 152 | + | ||
| 153 | + | # Install CrowdSec | |
| 154 | + | apt update | |
| 155 | + | apt install -y crowdsec | |
| 156 | + | ``` | |
| 157 | + | ||
| 158 | + | ## Step 8: Install nftables Bouncer | |
| 159 | + | ||
| 160 | + | Ubuntu 24.04 uses nftables by default: | |
| 161 | + | ||
| 162 | + | ```bash | |
| 163 | + | apt install -y crowdsec-firewall-bouncer-nftables | |
| 164 | + | systemctl enable --now crowdsec-firewall-bouncer | |
| 165 | + | ``` | |
| 166 | + | ||
| 167 | + | ## Step 9: Install Security Collections | |
| 168 | + | ||
| 169 | + | ```bash | |
| 170 | + | # HTTP protection | |
| 171 | + | cscli collections install crowdsecurity/http-cve | |
| 172 | + | cscli collections install crowdsecurity/base-http-scenarios | |
| 173 | + | ||
| 174 | + | # Reload to apply | |
| 175 | + | systemctl reload crowdsec | |
| 176 | + | ``` | |
| 177 | + | ||
| 178 | + | ## Step 10: Whitelist Tailscale IPs | |
| 179 | + | ||
| 180 | + | Prevent CrowdSec from blocking Tailscale traffic: | |
| 181 | + | ||
| 182 | + | ```bash | |
| 183 | + | cat > /etc/crowdsec/parsers/s02-enrich/tailscale-whitelist.yaml << 'EOF' | |
| 184 | + | name: tailscale-whitelist | |
| 185 | + | description: "Whitelist Tailscale CGNAT range" | |
| 186 | + | whitelist: | |
| 187 | + | reason: "Tailscale internal traffic" | |
| 188 | + | cidr: | |
| 189 | + | - "100.64.0.0/10" | |
| 190 | + | EOF | |
| 191 | + | ||
| 192 | + | systemctl reload crowdsec | |
| 193 | + | ``` | |
| 194 | + | ||
| 195 | + | ## Step 11: Enroll in CrowdSec Console | |
| 196 | + | ||
| 197 | + | This enables community blocklists - IPs blocked across all CrowdSec users globally (15,000+ malicious IPs). | |
| 198 | + | ||
| 199 | + | 1. Create account at https://app.crowdsec.net | |
| 200 | + | 2. Go to **Security Engines** → **Add Security Engine** | |
| 201 | + | 3. Copy enrollment key and run: | |
| 202 | + | ||
| 203 | + | ```bash | |
| 204 | + | cscli console enroll <your-enrollment-key> | |
| 205 | + | ``` | |
| 206 | + | ||
| 207 | + | 4. Accept the enrollment in the CrowdSec console | |
| 208 | + | 5. Enable console management: | |
| 209 | + | ||
| 210 | + | ```bash | |
| 211 | + | systemctl restart crowdsec | |
| 212 | + | cscli console enable console_management | |
| 213 | + | systemctl reload crowdsec | |
| 214 | + | ``` | |
| 215 | + | ||
| 216 | + | --- | |
| 217 | + | ||
| 218 | + | ## Verification | |
| 219 | + | ||
| 220 | + | ```bash | |
| 221 | + | # Firewall status | |
| 222 | + | firewall-cmd --state | |
| 223 | + | firewall-cmd --list-all | |
| 224 | + | firewall-cmd --list-all --zone=trusted | |
| 225 | + | ||
| 226 | + | # CrowdSec status | |
| 227 | + | cscli metrics | |
| 228 | + | cscli bouncers list | |
| 229 | + | cscli console status | |
| 230 | + | ||
| 231 | + | # Verify nftables has CrowdSec rules | |
| 232 | + | nft list ruleset | grep -A5 'crowdsec' | |
| 233 | + | ||
| 234 | + | # View blocked IPs count | |
| 235 | + | cscli decisions list | wc -l | |
| 236 | + | ``` | |
| 237 | + | ||
| 238 | + | --- | |
| 239 | + | ||
| 240 | + | ## Port Summary | |
| 241 | + | ||
| 242 | + | | Port | Protocol | Status | Purpose | | |
| 243 | + | |------|----------|--------|---------| | |
| 244 | + | | 22 | TCP | Closed | SSH (use Tailscale instead) | | |
| 245 | + | | 80 | TCP | Open | HTTP (Gordon proxy) | | |
| 246 | + | | 443 | TCP | Open | HTTPS (Gordon proxy via Cloudflare) | | |
| 247 | + | | 5000 | TCP | Closed | Registry (proxied through 80/443) | | |
| 248 | + | | 41641 | UDP | Open | Tailscale WireGuard | | |
| 249 | + | ||
| 250 | + | --- | |
| 251 | + | ||
| 252 | + | ## Troubleshooting | |
| 253 | + | ||
| 254 | + | ### Can't SSH via Tailscale | |
| 255 | + | ||
| 256 | + | Check for ACL issues: | |
| 257 | + | ||
| 258 | + | ```bash | |
| 259 | + | journalctl -u tailscaled --since "10 minutes ago" | grep -i "rejected\|acl" | |
| 260 | + | ``` | |
| 261 | + | ||
| 262 | + | If you see `rejected due to acl`, check your Tailscale ACLs at https://login.tailscale.com/admin/acls | |
| 263 | + | ||
| 264 | + | ### Locked out completely | |
| 265 | + | ||
| 266 | + | Use your VPS provider's console access (e.g., Hetzner Cloud Console): | |
| 267 | + | ||
| 268 | + | ```bash | |
| 269 | + | firewall-cmd --add-service=ssh # Temporarily re-enable public SSH | |
| 270 | + | ``` | |
| 271 | + | ||
| 272 | + | ### CrowdSec not blocking | |
| 273 | + | ||
| 274 | + | Verify bouncer is running: | |
| 275 | + | ||
| 276 | + | ```bash | |
| 277 | + | systemctl status crowdsec-firewall-bouncer | |
| 278 | + | cscli bouncers list # Should show "Valid" | |
| 279 | + | ``` | |
| 280 | + | ||
| 281 | + | ### Check what's being blocked | |
| 282 | + | ||
| 283 | + | ```bash | |
| 284 | + | cscli decisions list | |
| 285 | + | cscli alerts list | |
| 286 | + | ``` | |
| 287 | + | ||
| 288 | + | --- | |
| 289 | + | ||
| 290 | + | ## Next Steps | |
| 291 | + | ||
| 292 | + | - [Install Gordon](/docs/getting-started.md) | |
| 293 | + | - [Podman Rootless Setup](./podman-rootless.md) - Enhanced container isolation | |
| 294 | + | - [Cloudflare DNS & Proxy Setup](#) - HTTPS termination (coming soon) | |
| 295 | + | ||
| 296 | + | ## References | |
| 297 | + | ||
| 298 | + | - [Tailscale SSH Documentation](https://tailscale.com/kb/1193/tailscale-ssh) | |
| 299 | + | - [firewalld Documentation](https://firewalld.org/documentation/) | |
| 300 | + | - [CrowdSec Documentation](https://docs.crowdsec.net/) | |
| 301 | + | - [CrowdSec Console](https://app.crowdsec.net) | |
| @@ -0,0 +1,301 @@ | |||||
| 1 | + | # Secure VPS Setup | |||
| 2 | + | ||||
| 3 | + | A production-ready VPS security configuration with Tailscale-only SSH access and CrowdSec intrusion prevention. | |||
| 4 | + | ||||
| 5 | + | ## Overview | |||
| 6 | + | ||||
| 7 | + | This guide configures three security layers: | |||
| 8 | + | ||||
| 9 | + | | Layer | Protection | | |||
| 10 | + | |-------|------------| | |||
| 11 | + | | SSH | Tailscale only (invisible to internet) | | |||
| 12 | + | | Firewall | firewalld with DROP policy | | |||
| 13 | + | | IPS | CrowdSec with community blocklists | | |||
| 14 | + | ||||
| 15 | + | ## Prerequisites | |||
| 16 | + | ||||
| 17 | + | - Fresh Ubuntu 24.04 VPS | |||
| 18 | + | - Tailscale account | |||
| 19 | + | - Initial SSH access via public IP (temporary) | |||
| 20 | + | - Cloudflare account for HTTPS termination | |||
| 21 | + | ||||
| 22 | + | --- | |||
| 23 | + | ||||
| 24 | + | ## Step 1: Initial System Hardening | |||
| 25 | + | ||||
| 26 | + | Before anything else, enable automatic security updates and time sync: | |||
| 27 | + | ||||
| 28 | + | ```bash | |||
| 29 | + | # Automatic security updates | |||
| 30 | + | apt update && apt install -y unattended-upgrades | |||
| 31 | + | dpkg-reconfigure -plow unattended-upgrades | |||
| 32 | + | ||||
| 33 | + | # Verify time sync (enabled by default on Ubuntu 24.04) | |||
| 34 | + | timedatectl set-ntp true | |||
| 35 | + | timedatectl status | |||
| 36 | + | ||||
| 37 | + | # Basic kernel hardening | |||
| 38 | + | cat > /etc/sysctl.d/99-security.conf << 'EOF' | |||
| 39 | + | # SYN flood protection | |||
| 40 | + | net.ipv4.tcp_syncookies = 1 | |||
| 41 | + | ||||
| 42 | + | # Reverse path filtering (anti-spoofing) | |||
| 43 | + | net.ipv4.conf.all.rp_filter = 1 | |||
| 44 | + | net.ipv4.conf.default.rp_filter = 1 | |||
| 45 | + | ||||
| 46 | + | # Disable ICMP redirects | |||
| 47 | + | net.ipv4.conf.all.accept_redirects = 0 | |||
| 48 | + | net.ipv4.conf.all.send_redirects = 0 | |||
| 49 | + | net.ipv6.conf.all.accept_redirects = 0 | |||
| 50 | + | ||||
| 51 | + | # Ignore ICMP broadcasts | |||
| 52 | + | net.ipv4.icmp_echo_ignore_broadcasts = 1 | |||
| 53 | + | EOF | |||
| 54 | + | ||||
| 55 | + | sysctl --system | |||
| 56 | + | ``` | |||
| 57 | + | ||||
| 58 | + | ## Step 2: Install Tailscale | |||
| 59 | + | ||||
| 60 | + | > **Important**: Use the official repository, NOT the Snap package (has AppArmor issues with Tailscale SSH on Ubuntu 24.04). | |||
| 61 | + | ||||
| 62 | + | ```bash | |||
| 63 | + | # Add official Tailscale repository | |||
| 64 | + | curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.noarmor.gpg | \ | |||
| 65 | + | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null | |||
| 66 | + | curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.tailscale-keyring.list | \ | |||
| 67 | + | tee /etc/apt/sources.list.d/tailscale.list | |||
| 68 | + | ||||
| 69 | + | # Install | |||
| 70 | + | apt update | |||
| 71 | + | apt install -y tailscale | |||
| 72 | + | ``` | |||
| 73 | + | ||||
| 74 | + | ## Step 3: Connect Tailscale with SSH | |||
| 75 | + | ||||
| 76 | + | ```bash | |||
| 77 | + | tailscale up --ssh | |||
| 78 | + | ``` | |||
| 79 | + | ||||
| 80 | + | This outputs an authentication URL. Open it and authenticate with your Tailscale account. | |||
| 81 | + | ||||
| 82 | + | **Important**: After connecting, go to https://login.tailscale.com/admin/machines and **disable key expiry** for this server to prevent lockouts. | |||
| 83 | + | ||||
| 84 | + | ## Step 4: Configure firewalld | |||
| 85 | + | ||||
| 86 | + | ```bash | |||
| 87 | + | # Install firewalld | |||
| 88 | + | apt install -y firewalld | |||
| 89 | + | ||||
| 90 | + | # Enable and start | |||
| 91 | + | systemctl enable --now firewalld | |||
| 92 | + | ||||
| 93 | + | # Trust Tailscale interface (allows all traffic from your tailnet) | |||
| 94 | + | firewall-cmd --permanent --zone=trusted --add-interface=tailscale0 | |||
| 95 | + | ||||
| 96 | + | # Allow Tailscale UDP port (WireGuard) | |||
| 97 | + | firewall-cmd --permanent --add-port=41641/udp | |||
| 98 | + | ||||
| 99 | + | # Allow web traffic (for Gordon reverse proxy) | |||
| 100 | + | firewall-cmd --permanent --add-service=http | |||
| 101 | + | firewall-cmd --permanent --add-service=https | |||
| 102 | + | ||||
| 103 | + | # Apply changes | |||
| 104 | + | firewall-cmd --reload | |||
| 105 | + | ``` | |||
| 106 | + | ||||
| 107 | + | > **Note**: Gordon's registry is proxied through port 80/443 via `gordon_domain`. Port 5000 does NOT need to be exposed publicly. | |||
| 108 | + | ||||
| 109 | + | ## Step 5: Verify Tailscale SSH Works | |||
| 110 | + | ||||
| 111 | + | From another machine on your tailnet: | |||
| 112 | + | ||||
| 113 | + | ```bash | |||
| 114 | + | # Get the server's Tailscale hostname | |||
| 115 | + | tailscale status | |||
| 116 | + | ||||
| 117 | + | # Connect from your local machine | |||
| 118 | + | ssh root@<tailscale-hostname> | |||
| 119 | + | # or | |||
| 120 | + | tailscale ssh root@<tailscale-hostname> | |||
| 121 | + | ``` | |||
| 122 | + | ||||
| 123 | + | ## Step 6: Block Public SSH | |||
| 124 | + | ||||
| 125 | + | **Only proceed once Tailscale SSH is confirmed working.** | |||
| 126 | + | ||||
| 127 | + | ```bash | |||
| 128 | + | # Remove SSH from public zone | |||
| 129 | + | firewall-cmd --permanent --remove-service=ssh | |||
| 130 | + | ||||
| 131 | + | # Set default policy to DROP all unmatched traffic | |||
| 132 | + | firewall-cmd --permanent --zone=public --set-target=DROP | |||
| 133 | + | ||||
| 134 | + | # Apply changes | |||
| 135 | + | firewall-cmd --reload | |||
| 136 | + | ``` | |||
| 137 | + | ||||
| 138 | + | Verify public SSH is blocked: | |||
| 139 | + | ||||
| 140 | + | ```bash | |||
| 141 | + | # From another machine (not on tailnet) | |||
| 142 | + | ssh -o ConnectTimeout=5 root@<public-ip> # Should timeout | |||
| 143 | + | ``` | |||
| 144 | + | ||||
| 145 | + | --- | |||
| 146 | + | ||||
| 147 | + | ## Step 7: Install CrowdSec | |||
| 148 | + | ||||
| 149 | + | ```bash | |||
| 150 | + | # Add CrowdSec repo (NOT Ubuntu's outdated version) | |||
| 151 | + | curl -s https://install.crowdsec.net | bash | |||
| 152 | + | ||||
| 153 | + | # Install CrowdSec | |||
| 154 | + | apt update | |||
| 155 | + | apt install -y crowdsec | |||
| 156 | + | ``` | |||
| 157 | + | ||||
| 158 | + | ## Step 8: Install nftables Bouncer | |||
| 159 | + | ||||
| 160 | + | Ubuntu 24.04 uses nftables by default: | |||
| 161 | + | ||||
| 162 | + | ```bash | |||
| 163 | + | apt install -y crowdsec-firewall-bouncer-nftables | |||
| 164 | + | systemctl enable --now crowdsec-firewall-bouncer | |||
| 165 | + | ``` | |||
| 166 | + | ||||
| 167 | + | ## Step 9: Install Security Collections | |||
| 168 | + | ||||
| 169 | + | ```bash | |||
| 170 | + | # HTTP protection | |||
| 171 | + | cscli collections install crowdsecurity/http-cve | |||
| 172 | + | cscli collections install crowdsecurity/base-http-scenarios | |||
| 173 | + | ||||
| 174 | + | # Reload to apply | |||
| 175 | + | systemctl reload crowdsec | |||
| 176 | + | ``` | |||
| 177 | + | ||||
| 178 | + | ## Step 10: Whitelist Tailscale IPs | |||
| 179 | + | ||||
| 180 | + | Prevent CrowdSec from blocking Tailscale traffic: | |||
| 181 | + | ||||
| 182 | + | ```bash | |||
| 183 | + | cat > /etc/crowdsec/parsers/s02-enrich/tailscale-whitelist.yaml << 'EOF' | |||
| 184 | + | name: tailscale-whitelist | |||
| 185 | + | description: "Whitelist Tailscale CGNAT range" | |||
| 186 | + | whitelist: | |||
| 187 | + | reason: "Tailscale internal traffic" | |||
| 188 | + | cidr: | |||
| 189 | + | - "100.64.0.0/10" | |||
| 190 | + | EOF | |||
| 191 | + | ||||
| 192 | + | systemctl reload crowdsec | |||
| 193 | + | ``` | |||
| 194 | + | ||||
| 195 | + | ## Step 11: Enroll in CrowdSec Console | |||
| 196 | + | ||||
| 197 | + | This enables community blocklists - IPs blocked across all CrowdSec users globally (15,000+ malicious IPs). | |||
| 198 | + | ||||
| 199 | + | 1. Create account at https://app.crowdsec.net | |||
| 200 | + | 2. Go to **Security Engines** → **Add Security Engine** | |||
| 201 | + | 3. Copy enrollment key and run: | |||
| 202 | + | ||||
| 203 | + | ```bash | |||
| 204 | + | cscli console enroll <your-enrollment-key> | |||
| 205 | + | ``` | |||
| 206 | + | ||||
| 207 | + | 4. Accept the enrollment in the CrowdSec console | |||
| 208 | + | 5. Enable console management: | |||
| 209 | + | ||||
| 210 | + | ```bash | |||
| 211 | + | systemctl restart crowdsec | |||
| 212 | + | cscli console enable console_management | |||
| 213 | + | systemctl reload crowdsec | |||
| 214 | + | ``` | |||
| 215 | + | ||||
| 216 | + | --- | |||
| 217 | + | ||||
| 218 | + | ## Verification | |||
| 219 | + | ||||
| 220 | + | ```bash | |||
| 221 | + | # Firewall status | |||
| 222 | + | firewall-cmd --state | |||
| 223 | + | firewall-cmd --list-all | |||
| 224 | + | firewall-cmd --list-all --zone=trusted | |||
| 225 | + | ||||
| 226 | + | # CrowdSec status | |||
| 227 | + | cscli metrics | |||
| 228 | + | cscli bouncers list | |||
| 229 | + | cscli console status | |||
| 230 | + | ||||
| 231 | + | # Verify nftables has CrowdSec rules | |||
| 232 | + | nft list ruleset | grep -A5 'crowdsec' | |||
| 233 | + | ||||
| 234 | + | # View blocked IPs count | |||
| 235 | + | cscli decisions list | wc -l | |||
| 236 | + | ``` | |||
| 237 | + | ||||
| 238 | + | --- | |||
| 239 | + | ||||
| 240 | + | ## Port Summary | |||
| 241 | + | ||||
| 242 | + | | Port | Protocol | Status | Purpose | | |||
| 243 | + | |------|----------|--------|---------| | |||
| 244 | + | | 22 | TCP | Closed | SSH (use Tailscale instead) | | |||
| 245 | + | | 80 | TCP | Open | HTTP (Gordon proxy) | | |||
| 246 | + | | 443 | TCP | Open | HTTPS (Gordon proxy via Cloudflare) | | |||
| 247 | + | | 5000 | TCP | Closed | Registry (proxied through 80/443) | | |||
| 248 | + | | 41641 | UDP | Open | Tailscale WireGuard | | |||
| 249 | + | ||||
| 250 | + | --- | |||
| 251 | + | ||||
| 252 | + | ## Troubleshooting | |||
| 253 | + | ||||
| 254 | + | ### Can't SSH via Tailscale | |||
| 255 | + | ||||
| 256 | + | Check for ACL issues: | |||
| 257 | + | ||||
| 258 | + | ```bash | |||
| 259 | + | journalctl -u tailscaled --since "10 minutes ago" | grep -i "rejected\|acl" | |||
| 260 | + | ``` | |||
| 261 | + | ||||
| 262 | + | If you see `rejected due to acl`, check your Tailscale ACLs at https://login.tailscale.com/admin/acls | |||
| 263 | + | ||||
| 264 | + | ### Locked out completely | |||
| 265 | + | ||||
| 266 | + | Use your VPS provider's console access (e.g., Hetzner Cloud Console): | |||
| 267 | + | ||||
| 268 | + | ```bash | |||
| 269 | + | firewall-cmd --add-service=ssh # Temporarily re-enable public SSH | |||
| 270 | + | ``` | |||
| 271 | + | ||||
| 272 | + | ### CrowdSec not blocking | |||
| 273 | + | ||||
| 274 | + | Verify bouncer is running: | |||
| 275 | + | ||||
| 276 | + | ```bash | |||
| 277 | + | systemctl status crowdsec-firewall-bouncer | |||
| 278 | + | cscli bouncers list # Should show "Valid" | |||
| 279 | + | ``` | |||
| 280 | + | ||||
| 281 | + | ### Check what's being blocked | |||
| 282 | + | ||||
| 283 | + | ```bash | |||
| 284 | + | cscli decisions list | |||
| 285 | + | cscli alerts list | |||
| 286 | + | ``` | |||
| 287 | + | ||||
| 288 | + | --- | |||
| 289 | + | ||||
| 290 | + | ## Next Steps | |||
| 291 | + | ||||
| 292 | + | - [Install Gordon](/docs/getting-started.md) | |||
| 293 | + | - [Podman Rootless Setup](./podman-rootless.md) - Enhanced container isolation | |||
| 294 | + | - [Cloudflare DNS & Proxy Setup](#) - HTTPS termination (coming soon) | |||
| 295 | + | ||||
| 296 | + | ## References | |||
| 297 | + | ||||
| 298 | + | - [Tailscale SSH Documentation](https://tailscale.com/kb/1193/tailscale-ssh) | |||
| 299 | + | - [firewalld Documentation](https://firewalld.org/documentation/) | |||
| 300 | + | - [CrowdSec Documentation](https://docs.crowdsec.net/) | |||
| 301 | + | - [CrowdSec Console](https://app.crowdsec.net) | |||
M wiki/index.md +5 -5
| @@ -6,18 +6,19 @@ Community tutorials, guides, and examples for Gordon. | |||
| 6 | 6 | ||
| 7 | 7 | Step-by-step guides for common tasks: | |
| 8 | 8 | ||
| 9 | - | - [Deploy Your First App](./tutorials/first-deploy.md) - Get started with Gordon | |
| 10 | - | - [Deploy a Next.js App](./tutorials/nextjs-deploy.md) - Full-stack JavaScript deployment | |
| 9 | + | - [Deploy Your First App](./tutorials/first-deploy.md) - Deploy a simple web app in minutes | |
| 11 | 10 | - [Add PostgreSQL to Your App](./tutorials/postgres-service.md) - Database attachments | |
| 12 | - | - [Deploy Multi-Service Applications](./tutorials/multi-service-app.md) - Microservices setup | |
| 11 | + | ||
| 12 | + | > **New to Gordon?** Start with the [Getting Started guide](/docs/getting-started.md) in the official docs. | |
| 13 | 13 | ||
| 14 | 14 | ## Guides | |
| 15 | 15 | ||
| 16 | 16 | In-depth guides for specific setups: | |
| 17 | 17 | ||
| 18 | + | - [Secure VPS Setup](./guides/secure-vps-setup.md) - Tailscale SSH, firewalld, and CrowdSec | |
| 18 | 19 | - [Running Gordon in a Container](./guides/running-in-container.md) - Deploy Gordon itself in Docker or Podman | |
| 19 | 20 | - [Podman Rootless Setup](./guides/podman-rootless.md) - Enhanced security with rootless containers | |
| 20 | - | - [Cloudflare DNS & Proxy Setup](./guides/cloudflare-setup.md) - HTTPS termination with Cloudflare | |
| 21 | + | - [Remote CLI Management](./guides/remote-cli.md) - Manage Gordon instances remotely | |
| 21 | 22 | - [Using Pass for Secrets](./guides/secrets-pass.md) - Unix password manager integration | |
| 22 | 23 | - [Using SOPS for Secrets](./guides/secrets-sops.md) - Encrypted secrets with SOPS | |
| 23 | 24 | ||
| @@ -33,7 +34,6 @@ Configuration examples explained: | |||
| 33 | 34 | ||
| 34 | 35 | - [Minimal Configuration](./examples/minimal.md) - Simplest working setup | |
| 35 | 36 | - [Production Configuration](./examples/production.md) - Full production setup | |
| 36 | - | - [Development Configuration](./examples/development.md) - Local development setup | |
| 37 | 37 | ||
| 38 | 38 | ## Quick Links | |
| 39 | 39 | ||
| @@ -6,18 +6,19 @@ Community tutorials, guides, and examples for Gordon. | |||||
| 6 | 6 | ||||
| 7 | Step-by-step guides for common tasks: | 7 | Step-by-step guides for common tasks: | ||
| 8 | 8 | ||||
| 9 | - | - [Deploy Your First App](./tutorials/first-deploy.md) - Get started with Gordon | 9 | + | - [Deploy Your First App](./tutorials/first-deploy.md) - Deploy a simple web app in minutes |
| 10 | - | - [Deploy a Next.js App](./tutorials/nextjs-deploy.md) - Full-stack JavaScript deployment | |||
| 11 | - [Add PostgreSQL to Your App](./tutorials/postgres-service.md) - Database attachments | 10 | - [Add PostgreSQL to Your App](./tutorials/postgres-service.md) - Database attachments | ||
| 12 | - | - [Deploy Multi-Service Applications](./tutorials/multi-service-app.md) - Microservices setup | 11 | + | |
| 12 | + | > **New to Gordon?** Start with the [Getting Started guide](/docs/getting-started.md) in the official docs. | |||
| 13 | 13 | ||||
| 14 | ## Guides | 14 | ## Guides | ||
| 15 | 15 | ||||
| 16 | In-depth guides for specific setups: | 16 | In-depth guides for specific setups: | ||
| 17 | 17 | ||||
| 18 | + | - [Secure VPS Setup](./guides/secure-vps-setup.md) - Tailscale SSH, firewalld, and CrowdSec | |||
| 18 | - [Running Gordon in a Container](./guides/running-in-container.md) - Deploy Gordon itself in Docker or Podman | 19 | - [Running Gordon in a Container](./guides/running-in-container.md) - Deploy Gordon itself in Docker or Podman | ||
| 19 | - [Podman Rootless Setup](./guides/podman-rootless.md) - Enhanced security with rootless containers | 20 | - [Podman Rootless Setup](./guides/podman-rootless.md) - Enhanced security with rootless containers | ||
| 20 | - | - [Cloudflare DNS & Proxy Setup](./guides/cloudflare-setup.md) - HTTPS termination with Cloudflare | 21 | + | - [Remote CLI Management](./guides/remote-cli.md) - Manage Gordon instances remotely |
| 21 | - [Using Pass for Secrets](./guides/secrets-pass.md) - Unix password manager integration | 22 | - [Using Pass for Secrets](./guides/secrets-pass.md) - Unix password manager integration | ||
| 22 | - [Using SOPS for Secrets](./guides/secrets-sops.md) - Encrypted secrets with SOPS | 23 | - [Using SOPS for Secrets](./guides/secrets-sops.md) - Encrypted secrets with SOPS | ||
| 23 | 24 | ||||
| @@ -33,7 +34,6 @@ Configuration examples explained: | |||||
| 33 | 34 | ||||
| 34 | - [Minimal Configuration](./examples/minimal.md) - Simplest working setup | 35 | - [Minimal Configuration](./examples/minimal.md) - Simplest working setup | ||
| 35 | - [Production Configuration](./examples/production.md) - Full production setup | 36 | - [Production Configuration](./examples/production.md) - Full production setup | ||
| 36 | - | - [Development Configuration](./examples/development.md) - Local development setup | |||
| 37 | 37 | ||||
| 38 | ## Quick Links | 38 | ## Quick Links | ||
| 39 | 39 | ||||
M wiki/tutorials/index.md +2 -2
| @@ -1,8 +1,8 @@ | |||
| 1 | 1 | # Tutorials | |
| 2 | 2 | ||
| 3 | - | Step-by-step guides to help you get started with Gordon. | |
| 3 | + | Step-by-step guides for common Gordon tasks. | |
| 4 | 4 | ||
| 5 | - | ## Getting Started | |
| 5 | + | > **New to Gordon?** Start with the [Getting Started guide](/docs/getting-started.md) in the official docs. | |
| 6 | 6 | ||
| 7 | 7 | - [Deploy Your First App](./first-deploy.md) - Deploy a simple web app in minutes | |
| 8 | 8 | ||
| @@ -1,8 +1,8 @@ | |||||
| 1 | # Tutorials | 1 | # Tutorials | ||
| 2 | 2 | ||||
| 3 | - | Step-by-step guides to help you get started with Gordon. | 3 | + | Step-by-step guides for common Gordon tasks. |
| 4 | 4 | ||||
| 5 | - | ## Getting Started | 5 | + | > **New to Gordon?** Start with the [Getting Started guide](/docs/getting-started.md) in the official docs. |
| 6 | 6 | ||||
| 7 | - [Deploy Your First App](./first-deploy.md) - Deploy a simple web app in minutes | 7 | - [Deploy Your First App](./first-deploy.md) - Deploy a simple web app in minutes | ||
| 8 | 8 | ||||