Files
rick-infra/docs/service-domain-configuration.md
Joakim cf71fb3a8d Implement SSH passthrough mode and refactor Gitea domain configuration
Major Changes:
- Add dual SSH mode system (passthrough default, dedicated fallback)
- Refactor domain configuration to use direct specification pattern
- Fix critical fail2ban security gap in dedicated mode
- Separate HTTP and SSH domains for cleaner Git URLs
2025-12-17 21:51:24 +01:00

8.0 KiB

Service Domain Configuration Standard

Standard pattern for domain configuration in rick-infra service roles.

Architecture Philosophy

Rick-infra follows a direct domain specification pattern for service configuration:

# Direct and explicit
service_domain: "subdomain.jnss.me"

# NOT this (complex and inflexible)
service_subdomain: "subdomain"
service_domain: "{{ caddy_domain }}"
service_full_domain: "{{ service_subdomain }}.{{ service_domain }}"

Benefits

  1. Simplicity: One variable instead of three
  2. Flexibility: Can use any domain (subdomain, root, or completely different)
  3. Explicitness: Clear what domain the service uses
  4. No Forced Inheritance: Not tied to infrastructure caddy_domain
  5. Consistency: All services follow the same pattern

Standard Pattern

Basic Service (Single Domain)

For services that only need one domain:

# roles/service/defaults/main.yml
service_domain: "service.jnss.me"

# host_vars/host/main.yml (explicit override)
service_domain: "service.jnss.me"

Examples:

  • Authentik: authentik_domain: "auth.jnss.me"
  • Nextcloud: nextcloud_domain: "cloud.jnss.me"

Advanced Service (Multiple Domains)

For services that need separate domains for different purposes:

# roles/service/defaults/main.yml
service_http_domain: "service.jnss.me"   # Web interface
service_api_domain: "api.jnss.me"        # API endpoint
service_ssh_domain: "jnss.me"            # SSH/CLI operations

# host_vars/host/main.yml (explicit override)
service_http_domain: "service.jnss.me"
service_api_domain: "api.jnss.me"
service_ssh_domain: "jnss.me"

Example:

  • Gitea:
    • gitea_http_domain: "git.jnss.me" (web interface)
    • gitea_ssh_domain: "jnss.me" (Git operations)

Usage in Templates

Caddy Configuration

# roles/service/templates/service.caddy.j2
{{ service_domain }} {
    reverse_proxy 127.0.0.1:{{ service_port }}
}

Application Configuration

# roles/service/templates/service.conf.j2
[server]
DOMAIN = {{ service_domain }}
ROOT_URL = https://{{ service_domain }}/

Task Display Messages

# roles/service/tasks/main.yml
- name: Display service information
  debug:
    msg: |
      🌐 Web Interface: https://{{ service_domain }}
      📍 Access your service at the domain above

Domain Selection Guidelines

Use Root Domain When:

  • Service is the primary purpose of the infrastructure
  • You want cleaner URLs (e.g., SSH: git@jnss.me vs git@git.jnss.me)
  • Industry standard uses root domain (e.g., GitHub uses github.com for SSH)

Use Subdomain When:

  • Service is one of many
  • You want explicit service identification
  • You need clear separation between services

Use Different Domain When:

  • Service needs to be on a different apex domain
  • External service integration requires specific domain
  • Multi-domain setup for geographical distribution

Examples by Service Type

Identity/Auth Service

authentik_domain: "auth.jnss.me"

Rationale: Auth subdomain is an industry standard

Storage Service

nextcloud_domain: "cloud.jnss.me"

Rationale: "cloud" clearly indicates storage/sync service

Git Service

gitea_http_domain: "git.jnss.me"   # Web UI
gitea_ssh_domain: "jnss.me"         # SSH operations

Rationale:

  • HTTP uses git. for clarity
  • SSH uses root domain to avoid git@git.jnss.me redundancy
  • Matches GitHub/GitLab pattern

Monitoring Service

grafana_domain: "monitor.jnss.me"
prometheus_domain: "metrics.jnss.me"

Rationale: Different subdomains for different monitoring tools


Configuration Layers

1. Role Defaults (roles/service/defaults/main.yml)

Provide sensible defaults:

# Option A: Use specific domain (explicit)
service_domain: "service.jnss.me"

# Option B: Use caddy_domain if it makes sense (flexible)
service_domain: "service.{{ caddy_domain | default('localhost') }}"

# Recommendation: Use Option A for clarity

2. Host Variables (host_vars/hostname/main.yml)

Always explicitly set in production:

# =================================================================
# Service Configuration
# =================================================================
service_domain: "service.jnss.me"

Why explicit?

  • Clear what domain is configured
  • Easy to change without understanding defaults
  • Easier to audit configuration
  • Documentation in configuration itself

3. Group Variables (group_vars/production/main.yml)

For settings shared across production hosts:

# Common production settings
service_enable_ssl: true
service_require_auth: true

# Generally avoid setting domains in group_vars
# (domains are usually host-specific)

Anti-Patterns to Avoid

Subdomain Composition

# DON'T DO THIS
service_subdomain: "service"
service_domain: "{{ caddy_domain }}"
service_full_domain: "{{ service_subdomain }}.{{ service_domain }}"

Problems:

  • Complex (3 variables for 1 domain)
  • Inflexible (can't use root or different domains)
  • Forces inheritance from infrastructure variable
  • Inconsistent with other services

Implicit Inheritance

# DON'T DO THIS
service_domain: "{{ caddy_domain }}"

Problems:

  • Not explicit what domain is used
  • Harder to change
  • Hides actual configuration
  • Requires understanding of infrastructure variables

Mixed Patterns

# DON'T DO THIS
authentik_domain: "auth.jnss.me"           # Direct
nextcloud_subdomain: "cloud"                # Composition
service_domain: "{{ caddy_domain }}"       # Inheritance

Problems:

  • Inconsistent
  • Confusing for maintainers
  • Different patterns for same purpose

Migration from Old Pattern

If you have services using the old subdomain composition pattern:

Step 1: Identify Current Variables

# Old pattern
service_subdomain: "service"
service_domain: "{{ caddy_domain }}"
service_full_domain: "{{ service_subdomain }}.{{ service_domain }}"

Step 2: Replace with Direct Domain

# New pattern
service_domain: "service.jnss.me"

Step 3: Update Template References

# Old
{{ service_full_domain }}

# New
{{ service_domain }}

Step 4: Remove Unused Variables

Delete service_subdomain and service_full_domain from defaults.

Step 5: Add Explicit Host Configuration

# host_vars/arch-vps/main.yml
service_domain: "service.jnss.me"

Testing Domain Configuration

Verify Caddy Configuration

# Check generated Caddy config
cat /etc/caddy/sites-enabled/service.caddy

# Test Caddy configuration syntax
caddy validate --config /etc/caddy/Caddyfile

# Check TLS certificate
curl -I https://service.jnss.me

Verify Application Configuration

# Check service configuration
cat /etc/service/config.ini | grep -i domain

# Test service accessibility
curl https://service.jnss.me

Verify DNS Resolution

# Check DNS resolution
dig service.jnss.me

# Test connectivity
nc -zv service.jnss.me 443

Checklist for New Services

When creating a new service role:

  • Use direct domain specification (not subdomain composition)
  • Define domain(s) in roles/service/defaults/main.yml
  • Add explicit domain(s) to host_vars
  • Update all templates to use domain variable(s)
  • Document domain configuration in role README
  • Follow naming convention: service_domain or service_[type]_domain
  • Test with different domain configurations

Summary

Standard Pattern:

# Defaults: Provide reasonable default
service_domain: "service.jnss.me"

# Host vars: Always explicit in production
service_domain: "service.jnss.me"

# Templates: Use variable directly
{{ service_domain }}

Key Principles:

  1. Direct and explicit
  2. One variable per domain
  3. No forced inheritance
  4. Consistent across all services
  5. Flexible for any domain pattern

Rick-Infra Domain Configuration Standard
Simple, flexible, and consistent domain configuration for all services.