Add Sigvild Gallery wedding photo application with automated deployment and improve Caddy plugin management
This commit is contained in:
@@ -4,6 +4,7 @@ host_key_checking = False
|
|||||||
remote_user = root
|
remote_user = root
|
||||||
deprecation_warnings = False
|
deprecation_warnings = False
|
||||||
vault_password_file = vault-password-file
|
vault_password_file = vault-password-file
|
||||||
|
roles_path = roles
|
||||||
|
|
||||||
[ssh_connection]
|
[ssh_connection]
|
||||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
||||||
|
|||||||
262
docs/sigvild-gallery-deployment.md
Normal file
262
docs/sigvild-gallery-deployment.md
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
# Sigvild Gallery Deployment Guide
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Deploy the complete Sigvild Wedding Gallery with PocketBase API and SvelteKit frontend.
|
||||||
|
|
||||||
|
## Prerequisites Setup
|
||||||
|
|
||||||
|
### 1. Vault Password Configuration
|
||||||
|
|
||||||
|
Create encrypted passwords for the gallery authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create vault passwords (run from rick-infra directory)
|
||||||
|
ansible-vault encrypt_string 'your-host-password-here' --name 'vault_sigvild_host_password'
|
||||||
|
ansible-vault encrypt_string 'your-guest-password-here' --name 'vault_sigvild_guest_password'
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the encrypted strings to `host_vars/arch-vps/main.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Add to host_vars/arch-vps/main.yml
|
||||||
|
vault_sigvild_host_password: !vault |
|
||||||
|
$ANSIBLE_VAULT;1.1;AES256
|
||||||
|
66386439653765386...
|
||||||
|
|
||||||
|
vault_sigvild_guest_password: !vault |
|
||||||
|
$ANSIBLE_VAULT;1.1;AES256
|
||||||
|
33663065383834313...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. DNS Configuration
|
||||||
|
|
||||||
|
Ensure these domains point to your server:
|
||||||
|
- `sigvild.no` → Frontend static site
|
||||||
|
- `api.sigvild.no` → API backend proxy
|
||||||
|
|
||||||
|
### 3. Project Structure
|
||||||
|
|
||||||
|
Ensure the sigvild-gallery project is adjacent to rick-infra:
|
||||||
|
|
||||||
|
```
|
||||||
|
~/
|
||||||
|
├── rick-infra/ # This repository
|
||||||
|
└── sigvild-gallery/ # Sigvild gallery project
|
||||||
|
├── build_tmp/ # Production builds
|
||||||
|
├── sigvild-kit/ # Frontend source
|
||||||
|
└── main.go # Backend source
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment Commands
|
||||||
|
|
||||||
|
### Full Infrastructure + Gallery
|
||||||
|
|
||||||
|
Deploy everything including Sigvild Gallery:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook site.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gallery Only
|
||||||
|
|
||||||
|
Deploy just the Sigvild Gallery service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook playbooks/deploy-sigvild.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Selective Updates
|
||||||
|
|
||||||
|
Update specific components:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Frontend only (quick static file updates)
|
||||||
|
ansible-playbook site.yml --tags="frontend"
|
||||||
|
|
||||||
|
# Backend only (API service updates)
|
||||||
|
ansible-playbook site.yml --tags="backend"
|
||||||
|
|
||||||
|
# Caddy configuration only
|
||||||
|
ansible-playbook site.yml --tags="caddy"
|
||||||
|
|
||||||
|
# Just build process (development)
|
||||||
|
ansible-playbook site.yml --tags="build"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet
|
||||||
|
↓
|
||||||
|
Caddy (Auto HTTPS)
|
||||||
|
├── sigvild.no → /var/www/sigvild-gallery/ (Static Files)
|
||||||
|
└── api.sigvild.no → localhost:8090 (PocketBase API)
|
||||||
|
↓
|
||||||
|
Go Binary (sigvild-gallery-server)
|
||||||
|
↓
|
||||||
|
SQLite Database + File Storage
|
||||||
|
```
|
||||||
|
|
||||||
|
## Service Management
|
||||||
|
|
||||||
|
### Status Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Gallery API service
|
||||||
|
systemctl status sigvild-gallery
|
||||||
|
|
||||||
|
# Caddy web server
|
||||||
|
systemctl status caddy
|
||||||
|
|
||||||
|
# View gallery logs
|
||||||
|
journalctl -u sigvild-gallery -f
|
||||||
|
|
||||||
|
# View Caddy logs
|
||||||
|
journalctl -u caddy -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Operations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restart gallery service
|
||||||
|
systemctl restart sigvild-gallery
|
||||||
|
|
||||||
|
# Reload Caddy configuration
|
||||||
|
systemctl reload caddy
|
||||||
|
|
||||||
|
# Check API health
|
||||||
|
curl https://api.sigvild.no/api/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Build Issues
|
||||||
|
|
||||||
|
**Problem**: Go build fails
|
||||||
|
```bash
|
||||||
|
# Ensure Go is installed locally
|
||||||
|
go version
|
||||||
|
|
||||||
|
# Check if you're in the right directory
|
||||||
|
ls sigvild-gallery/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Frontend build fails
|
||||||
|
```bash
|
||||||
|
# Check Node.js and npm
|
||||||
|
node --version && npm --version
|
||||||
|
|
||||||
|
# Ensure dependencies are installed
|
||||||
|
cd sigvild-gallery/sigvild-kit
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Issues
|
||||||
|
|
||||||
|
**Problem**: Service won't start
|
||||||
|
```bash
|
||||||
|
# Check service status
|
||||||
|
systemctl status sigvild-gallery
|
||||||
|
|
||||||
|
# Check service logs
|
||||||
|
journalctl -u sigvild-gallery --no-pager
|
||||||
|
|
||||||
|
# Verify binary permissions
|
||||||
|
ls -la /opt/sigvild-gallery/sigvild-gallery-server
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Database permissions
|
||||||
|
```bash
|
||||||
|
# Check data directory ownership
|
||||||
|
ls -la /opt/sigvild-gallery/data/
|
||||||
|
|
||||||
|
# Fix ownership if needed
|
||||||
|
sudo chown -R sigvild:sigvild /opt/sigvild-gallery/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network Issues
|
||||||
|
|
||||||
|
**Problem**: Domain not resolving
|
||||||
|
```bash
|
||||||
|
# Test DNS resolution
|
||||||
|
dig sigvild.no
|
||||||
|
dig api.sigvild.no
|
||||||
|
|
||||||
|
# Test local connectivity
|
||||||
|
curl -H "Host: sigvild.no" http://localhost
|
||||||
|
curl -H "Host: api.sigvild.no" http://localhost
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: HTTPS certificate issues
|
||||||
|
```bash
|
||||||
|
# Check Caddy logs for ACME errors
|
||||||
|
journalctl -u caddy | grep -i "acme\|certificate"
|
||||||
|
|
||||||
|
# Verify DNS challenge credentials
|
||||||
|
# (Check Cloudflare API token in vault)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Features
|
||||||
|
|
||||||
|
### Environment Protection
|
||||||
|
- **No .env files**: Secrets stored in systemd environment variables only
|
||||||
|
- **Vault encryption**: All passwords encrypted with ansible-vault
|
||||||
|
- **Memory isolation**: Secrets only exist in process memory
|
||||||
|
|
||||||
|
### SystemD Sandboxing
|
||||||
|
- **Read-only filesystem**: Application cannot modify system files
|
||||||
|
- **Isolated temporary**: Private /tmp directory
|
||||||
|
- **Limited capabilities**: No privilege escalation possible
|
||||||
|
- **Data directory only**: Write access restricted to /opt/sigvild-gallery/data/
|
||||||
|
|
||||||
|
### Web Security
|
||||||
|
- **Automatic HTTPS**: Let's Encrypt certificates via DNS challenge
|
||||||
|
- **Security headers**: XSS protection, frame options, content type sniffing prevention
|
||||||
|
- **CORS restrictions**: API access limited to frontend domain
|
||||||
|
- **Rate limiting**: API endpoint protection
|
||||||
|
|
||||||
|
## File Locations
|
||||||
|
|
||||||
|
### Application Files
|
||||||
|
- **Binary**: `/opt/sigvild-gallery/sigvild-gallery-server`
|
||||||
|
- **Database**: `/opt/sigvild-gallery/data/data.db`
|
||||||
|
- **File uploads**: `/opt/sigvild-gallery/data/storage/`
|
||||||
|
- **Frontend**: `/var/www/sigvild-gallery/`
|
||||||
|
|
||||||
|
### Configuration Files
|
||||||
|
- **Service**: `/etc/systemd/system/sigvild-gallery.service`
|
||||||
|
- **Caddy frontend**: `/etc/caddy/sites-enabled/sigvild-frontend.caddy`
|
||||||
|
- **Caddy API**: `/etc/caddy/sites-enabled/sigvild-api.caddy`
|
||||||
|
|
||||||
|
### Log Files
|
||||||
|
- **Service logs**: `journalctl -u sigvild-gallery`
|
||||||
|
- **Caddy logs**: `journalctl -u caddy`
|
||||||
|
- **Access logs**: `/var/log/caddy/sigvild-*.log`
|
||||||
|
|
||||||
|
## Next Steps After Deployment
|
||||||
|
|
||||||
|
1. **Verify services**: Check that both domains are accessible
|
||||||
|
2. **Test authentication**: Login with host/guest credentials
|
||||||
|
3. **Upload test photo**: Verify file upload functionality
|
||||||
|
4. **Monitor logs**: Watch for any errors in service logs
|
||||||
|
5. **Backup setup**: Configure regular database backups
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
For ongoing development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Make changes to sigvild-gallery project
|
||||||
|
cd ../sigvild-gallery
|
||||||
|
|
||||||
|
# 2. Test locally
|
||||||
|
go run . serve &
|
||||||
|
cd sigvild-kit && npm run dev
|
||||||
|
|
||||||
|
# 3. Deploy updates
|
||||||
|
cd ../rick-infra
|
||||||
|
ansible-playbook site.yml --tags="sigvild"
|
||||||
|
```
|
||||||
|
|
||||||
|
The deployment system builds locally and transfers assets, so you don't need build tools on the server.
|
||||||
@@ -35,9 +35,23 @@ caddy_server_name: "main"
|
|||||||
# service_backend: "localhost:8080"
|
# service_backend: "localhost:8080"
|
||||||
# notify: register service with caddy
|
# notify: register service with caddy
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Sigvild Gallery Configuration
|
||||||
|
# =================================================================
|
||||||
|
sigvild_gallery_frontend_domain: "sigvild.no"
|
||||||
|
sigvild_gallery_api_domain: "api.sigvild.no"
|
||||||
|
|
||||||
|
sigvild_gallery_local_project_path: "~/sigvild-gallery/"
|
||||||
|
|
||||||
|
# Vault-encrypted passwords (create with ansible-vault)
|
||||||
|
sigvild_gallery_pb_su_email: "{{ vault_pb_su_email}}"
|
||||||
|
sigvild_gallery_pb_su_password: "{{ vault_pb_su_password}}"
|
||||||
|
sigvild_gallery_host_password: "{{ vault_sigvild_host_password }}"
|
||||||
|
sigvild_gallery_guest_password: "{{ vault_sigvild_guest_password }}"
|
||||||
|
|
||||||
# =================================================================
|
# =================================================================
|
||||||
# Security & Logging
|
# Security & Logging
|
||||||
# =================================================================
|
# =================================================================
|
||||||
caddy_log_level: "INFO"
|
caddy_log_level: "INFO"
|
||||||
caddy_log_format: "json"
|
caddy_log_format: "json"
|
||||||
caddy_systemd_security: true
|
caddy_systemd_security: true
|
||||||
|
|||||||
52
playbooks/deploy-sigvild.yml
Normal file
52
playbooks/deploy-sigvild.yml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
# Sigvild Gallery Deployment Playbook
|
||||||
|
|
||||||
|
- name: Deploy Sigvild Wedding Gallery
|
||||||
|
hosts: arch-vps
|
||||||
|
become: yes
|
||||||
|
gather_facts: yes
|
||||||
|
|
||||||
|
vars:
|
||||||
|
# Local project path - adjust as needed
|
||||||
|
sigvild_gallery_local_project_path: "{{ ansible_env.PWD }}/../sigvild-gallery"
|
||||||
|
|
||||||
|
pre_tasks:
|
||||||
|
- name: Verify local sigvild-gallery project exists
|
||||||
|
local_action:
|
||||||
|
module: stat
|
||||||
|
path: "{{ sigvild_gallery_local_project_path }}"
|
||||||
|
register: project_exists
|
||||||
|
become: no
|
||||||
|
|
||||||
|
- name: Fail if project directory doesn't exist
|
||||||
|
fail:
|
||||||
|
msg: "Sigvild Gallery project not found at {{ sigvild_gallery_local_project_path }}"
|
||||||
|
when: not project_exists.stat.exists
|
||||||
|
|
||||||
|
- name: Display deployment information
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "Deploying Sigvild Gallery from: {{ sigvild_gallery_local_project_path }}"
|
||||||
|
- "Frontend domain: {{ sigvild_gallery_frontend_domain }}"
|
||||||
|
- "API domain: {{ sigvild_gallery_api_domain }}"
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: sigvild-gallery
|
||||||
|
tags: ['sigvild', 'gallery', 'wedding']
|
||||||
|
|
||||||
|
post_tasks:
|
||||||
|
- name: Wait for API to be ready
|
||||||
|
wait_for:
|
||||||
|
port: "{{ sigvild_gallery_port }}"
|
||||||
|
host: "{{ sigvild_gallery_host }}"
|
||||||
|
timeout: 60
|
||||||
|
tags: [verify]
|
||||||
|
|
||||||
|
- name: Display deployment results
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "✅ Sigvild Gallery deployment completed!"
|
||||||
|
- "Frontend: https://{{ sigvild_gallery_frontend_domain }}"
|
||||||
|
- "API: https://{{ sigvild_gallery_api_domain }}"
|
||||||
|
- "Service status: systemctl status sigvild-gallery"
|
||||||
|
- "Logs: journalctl -u sigvild-gallery -f"
|
||||||
@@ -3,19 +3,35 @@
|
|||||||
set_fact:
|
set_fact:
|
||||||
dns_challenge_needed: "{{ caddy_dns_provider == 'cloudflare' and cloudflare_api_token != '' }}"
|
dns_challenge_needed: "{{ caddy_dns_provider == 'cloudflare' and cloudflare_api_token != '' }}"
|
||||||
|
|
||||||
|
- name: Check if Caddy is already installed
|
||||||
|
command: /usr/bin/caddy version
|
||||||
|
register: caddy_version_check
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
when: dns_challenge_needed | bool
|
||||||
|
|
||||||
|
- name: Check if installed Caddy has Cloudflare plugin
|
||||||
|
command: /usr/bin/caddy list-modules --packages
|
||||||
|
register: caddy_modules_check
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
when: dns_challenge_needed | bool and caddy_version_check.rc == 0
|
||||||
|
|
||||||
- name: Install standard Caddy (if no DNS challenge needed)
|
- name: Install standard Caddy (if no DNS challenge needed)
|
||||||
pacman:
|
pacman:
|
||||||
name: caddy
|
name: caddy
|
||||||
state: present
|
state: present
|
||||||
when: not dns_challenge_needed | bool
|
when: not dns_challenge_needed and not caddy_version_check | bool
|
||||||
notify: restart caddy
|
notify: restart caddy
|
||||||
|
|
||||||
- name: Download Caddy with Cloudflare plugin (if DNS challenge needed)
|
- name: Download Caddy with Cloudflare plugin (if DNS challenge needed)
|
||||||
get_url:
|
get_url:
|
||||||
url: "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com/caddy-dns/cloudflare"
|
url: "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com/caddy-dns/cloudflare"
|
||||||
dest: /tmp/caddy-with-cloudflare
|
dest: /tmp/caddy-with-cloudflare
|
||||||
mode: '0755'
|
mode: '0755'
|
||||||
when: dns_challenge_needed | bool
|
when:
|
||||||
|
- dns_challenge_needed | bool
|
||||||
|
- caddy_version_check.rc != 0 or 'github.com/caddy-dns/cloudflare' not in caddy_modules_check.stdout | default('')
|
||||||
|
|
||||||
- name: Install Caddy with Cloudflare plugin
|
- name: Install Caddy with Cloudflare plugin
|
||||||
copy:
|
copy:
|
||||||
@@ -24,7 +40,7 @@
|
|||||||
mode: '0755'
|
mode: '0755'
|
||||||
remote_src: yes
|
remote_src: yes
|
||||||
backup: yes
|
backup: yes
|
||||||
when: dns_challenge_needed | bool
|
when: dns_challenge_needed and caddy_version_check | bool
|
||||||
notify: restart caddy
|
notify: restart caddy
|
||||||
|
|
||||||
- name: Clean up temporary Caddy binary
|
- name: Clean up temporary Caddy binary
|
||||||
|
|||||||
180
roles/sigvild-gallery/README.md
Normal file
180
roles/sigvild-gallery/README.md
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# Sigvild Gallery Ansible Role
|
||||||
|
|
||||||
|
Deploys the Sigvild Wedding Gallery application with PocketBase API backend and SvelteKit frontend.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- **Backend**: PocketBase-based Go application serving API on localhost:8090
|
||||||
|
- **Frontend**: SvelteKit static site served by Caddy
|
||||||
|
- **Database**: SQLite via PocketBase (file-based storage)
|
||||||
|
- **Authentication**: Shared password system (host/guest users)
|
||||||
|
- **Domains**:
|
||||||
|
- `sigvild.no` → Frontend static files
|
||||||
|
- `api.sigvild.no` → Backend API proxy
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Caddy role deployed and configured
|
||||||
|
- Local sigvild-gallery project with built assets in `build_tmp/`
|
||||||
|
- Vault-encrypted passwords configured in inventory
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
### Required Variables
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Domains
|
||||||
|
sigvild_gallery_frontend_domain: "sigvild.no"
|
||||||
|
sigvild_gallery_api_domain: "api.sigvild.no"
|
||||||
|
|
||||||
|
# Vault-encrypted passwords
|
||||||
|
vault_sigvild_host_password: "your-encrypted-host-password"
|
||||||
|
vault_sigvild_guest_password: "your-encrypted-guest-password"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optional Variables
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Service configuration
|
||||||
|
sigvild_gallery_user: "sigvild"
|
||||||
|
sigvild_gallery_port: 8090
|
||||||
|
sigvild_gallery_host: "127.0.0.1"
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
sigvild_gallery_home: "/opt/sigvild-gallery"
|
||||||
|
sigvild_gallery_web_root: "/var/www/sigvild-gallery"
|
||||||
|
sigvild_gallery_local_project_path: "{{ ansible_env.PWD }}/sigvild-gallery"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Full Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy complete infrastructure including Sigvild Gallery
|
||||||
|
ansible-playbook site.yml
|
||||||
|
|
||||||
|
# Deploy just Sigvild Gallery
|
||||||
|
ansible-playbook playbooks/deploy-sigvild.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Selective Updates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update just the frontend
|
||||||
|
ansible-playbook site.yml --tags="frontend"
|
||||||
|
|
||||||
|
# Update just the backend API
|
||||||
|
ansible-playbook site.yml --tags="backend"
|
||||||
|
|
||||||
|
# Update Caddy configuration
|
||||||
|
ansible-playbook site.yml --tags="caddy"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Features
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
- **No .env files**: Secrets managed via systemd Environment directives
|
||||||
|
- **Vault encrypted**: Passwords stored in Ansible vault
|
||||||
|
- **Memory-only**: Environment variables only exist in process memory
|
||||||
|
|
||||||
|
### SystemD Sandboxing
|
||||||
|
- `NoNewPrivileges=yes`: Prevents privilege escalation
|
||||||
|
- `PrivateTmp=yes`: Isolated temporary directory
|
||||||
|
- `ProtectSystem=strict`: Read-only filesystem protection
|
||||||
|
- `ProtectHome=yes`: Home directory protection
|
||||||
|
- `ReadWritePaths`: Only data directory is writable
|
||||||
|
|
||||||
|
### Caddy Security
|
||||||
|
- **Security headers**: XSS protection, frame options, content type sniffing prevention
|
||||||
|
- **CORS configuration**: Restricted to frontend domain
|
||||||
|
- **Rate limiting**: API endpoint protection
|
||||||
|
- **HTTPS only**: Automatic TLS with Let's Encrypt
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
/opt/sigvild-gallery/ # Application home
|
||||||
|
├── sigvild-gallery-server # Go binary
|
||||||
|
└── data/ # PocketBase data directory
|
||||||
|
├── data.db # SQLite database
|
||||||
|
└── storage/ # File uploads
|
||||||
|
|
||||||
|
/var/www/sigvild-gallery/ # Frontend web root
|
||||||
|
├── index.html # SvelteKit build
|
||||||
|
├── _app/ # Application assets
|
||||||
|
└── assets/ # Static assets
|
||||||
|
|
||||||
|
/etc/systemd/system/
|
||||||
|
└── sigvild-gallery.service # SystemD service
|
||||||
|
|
||||||
|
/etc/caddy/sites-enabled/
|
||||||
|
├── sigvild-frontend.caddy # Frontend configuration
|
||||||
|
└── sigvild-api.caddy # API proxy configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Process
|
||||||
|
|
||||||
|
The role performs local builds then transfers assets:
|
||||||
|
|
||||||
|
1. **Backend**: `GOOS=linux GOARCH=amd64 go build -o sigvild-gallery-server .`
|
||||||
|
2. **Frontend**: `npm run build` in `sigvild-kit/` directory
|
||||||
|
3. **Transfer**: Copy binary and sync frontend build to server
|
||||||
|
4. **Deploy**: Update systemd service and Caddy configuration
|
||||||
|
|
||||||
|
## Service Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check service status
|
||||||
|
systemctl status sigvild-gallery
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
journalctl -u sigvild-gallery -f
|
||||||
|
|
||||||
|
# Restart service
|
||||||
|
systemctl restart sigvild-gallery
|
||||||
|
|
||||||
|
# Reload Caddy configuration
|
||||||
|
systemctl reload caddy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Build Failures
|
||||||
|
- Ensure Go toolchain is available locally
|
||||||
|
- Verify `sigvild-kit/` directory exists with `package.json`
|
||||||
|
- Check Node.js and npm are installed for frontend builds
|
||||||
|
|
||||||
|
### Service Startup Issues
|
||||||
|
- Check systemd logs: `journalctl -u sigvild-gallery`
|
||||||
|
- Verify binary permissions and ownership
|
||||||
|
- Ensure data directory is writable by service user
|
||||||
|
|
||||||
|
### Domain Resolution
|
||||||
|
- Verify DNS records point to server IP
|
||||||
|
- Check Caddy logs: `journalctl -u caddy`
|
||||||
|
- Test local connectivity: `curl -H "Host: api.sigvild.no" http://localhost:8090`
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **caddy**: Required for web server and reverse proxy
|
||||||
|
- **systemd**: Service management
|
||||||
|
- **Local build tools**: Go compiler, Node.js/npm
|
||||||
|
|
||||||
|
## Files Created
|
||||||
|
|
||||||
|
- `/etc/systemd/system/sigvild-gallery.service`
|
||||||
|
- `/etc/caddy/sites-enabled/sigvild-frontend.caddy`
|
||||||
|
- `/etc/caddy/sites-enabled/sigvild-api.caddy`
|
||||||
|
- `/opt/sigvild-gallery/` (application directory)
|
||||||
|
- `/var/www/sigvild-gallery/` (frontend files)
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
- `sigvild`: Complete Sigvild Gallery deployment
|
||||||
|
- `backend`: API service deployment
|
||||||
|
- `frontend`: Static site deployment
|
||||||
|
- `build`: Local build processes
|
||||||
|
- `service`: SystemD service management
|
||||||
|
- `caddy`: Caddy configuration
|
||||||
|
- `verify`: Post-deployment verification
|
||||||
36
roles/sigvild-gallery/defaults/main.yml
Normal file
36
roles/sigvild-gallery/defaults/main.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
# Sigvild Gallery Ansible Role - Default Variables
|
||||||
|
|
||||||
|
# Service Configuration
|
||||||
|
sigvild_gallery_user: sigvild
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
sigvild_gallery_home: /opt/sigvild-gallery
|
||||||
|
sigvild_gallery_web_root: /var/www/sigvild-gallery
|
||||||
|
sigvild_gallery_binary: "{{ sigvild_gallery_home }}/sigvild-gallery"
|
||||||
|
sigvild_gallery_data_dir: "{{ sigvild_gallery_home }}/pb_data"
|
||||||
|
|
||||||
|
# Domains
|
||||||
|
sigvild_gallery_frontend_domain: sigvild.no
|
||||||
|
sigvild_gallery_api_domain: api.sigvild.no
|
||||||
|
|
||||||
|
# Backend Service
|
||||||
|
sigvild_gallery_port: 8090
|
||||||
|
sigvild_gallery_host: "127.0.0.1"
|
||||||
|
|
||||||
|
# Environment Variables (for SystemD service)
|
||||||
|
sigvild_gallery_host_username: host
|
||||||
|
sigvild_gallery_host_password: "{{ vault_sigvild_host_password }}"
|
||||||
|
sigvild_gallery_guest_username: guest
|
||||||
|
sigvild_gallery_guest_password: "{{ vault_sigvild_guest_password }}"
|
||||||
|
|
||||||
|
# Build configuration
|
||||||
|
sigvild_gallery_local_project_path: "{{ ansible_env.PWD }}/sigvild-gallery"
|
||||||
|
|
||||||
|
# Service configuration
|
||||||
|
sigvild_gallery_service_enabled: true
|
||||||
|
sigvild_gallery_service_state: started
|
||||||
|
|
||||||
|
# Caddy integration (assumes caddy role provides these)
|
||||||
|
# caddy_sites_enabled_dir: /etc/caddy/sites-enabled
|
||||||
|
# caddy_user: caddy
|
||||||
16
roles/sigvild-gallery/handlers/main.yml
Normal file
16
roles/sigvild-gallery/handlers/main.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
# Sigvild Gallery Handlers
|
||||||
|
|
||||||
|
- name: reload systemd
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: restart sigvild-gallery
|
||||||
|
systemd:
|
||||||
|
name: sigvild-gallery
|
||||||
|
state: restarted
|
||||||
|
|
||||||
|
- name: reload caddy
|
||||||
|
systemd:
|
||||||
|
name: caddy
|
||||||
|
state: reloaded
|
||||||
20
roles/sigvild-gallery/meta/main.yml
Normal file
20
roles/sigvild-gallery/meta/main.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
# Role Dependencies
|
||||||
|
dependencies:
|
||||||
|
- role: caddy
|
||||||
|
|
||||||
|
galaxy_info:
|
||||||
|
role_name: sigvild-gallery
|
||||||
|
author: "Rick Infrastructure Team"
|
||||||
|
description: "Deploys Sigvild Wedding Gallery with PocketBase API and SvelteKit frontend"
|
||||||
|
company: ""
|
||||||
|
license: "license (MIT)"
|
||||||
|
min_ansible_version: "2.9"
|
||||||
|
|
||||||
|
platforms:
|
||||||
|
- name: Archlinux
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
|
- name: Ubuntu
|
||||||
|
versions:
|
||||||
|
- all
|
||||||
43
roles/sigvild-gallery/tasks/deploy_backend.yml
Normal file
43
roles/sigvild-gallery/tasks/deploy_backend.yml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
# Backend Deployment Tasks
|
||||||
|
|
||||||
|
- name: Build Go binary locally
|
||||||
|
local_action:
|
||||||
|
module: shell
|
||||||
|
cmd: GOOS=linux GOARCH=amd64 go build -o sigvild-gallery .
|
||||||
|
chdir: "{{ sigvild_gallery_local_project_path }}"
|
||||||
|
become: no
|
||||||
|
tags: [backend, build]
|
||||||
|
|
||||||
|
- name: Check if binary was built successfully
|
||||||
|
local_action:
|
||||||
|
module: stat
|
||||||
|
path: "{{ sigvild_gallery_local_project_path }}/sigvild-gallery"
|
||||||
|
register: binary_stat
|
||||||
|
become: no
|
||||||
|
tags: [backend, build]
|
||||||
|
|
||||||
|
- name: Fail if binary doesn't exist
|
||||||
|
fail:
|
||||||
|
msg: "Failed to build sigvild-gallery binary"
|
||||||
|
when: not binary_stat.stat.exists
|
||||||
|
tags: [backend, build]
|
||||||
|
|
||||||
|
- name: Transfer Go binary
|
||||||
|
copy:
|
||||||
|
src: "{{ sigvild_gallery_local_project_path }}/sigvild-gallery"
|
||||||
|
dest: "{{ sigvild_gallery_binary }}"
|
||||||
|
owner: "{{ sigvild_gallery_user }}"
|
||||||
|
group: "{{ sigvild_gallery_user }}"
|
||||||
|
mode: '0755'
|
||||||
|
notify: restart sigvild-gallery
|
||||||
|
tags: [backend]
|
||||||
|
|
||||||
|
- name: Create data directory for PocketBase
|
||||||
|
file:
|
||||||
|
path: "{{ sigvild_gallery_data_dir }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ sigvild_gallery_user }}"
|
||||||
|
group: "{{ sigvild_gallery_user }}"
|
||||||
|
mode: '0755'
|
||||||
|
tags: [backend]
|
||||||
57
roles/sigvild-gallery/tasks/deploy_frontend.yml
Normal file
57
roles/sigvild-gallery/tasks/deploy_frontend.yml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
# Frontend Deployment Tasks
|
||||||
|
|
||||||
|
- name: Check if frontend source exists
|
||||||
|
local_action:
|
||||||
|
module: stat
|
||||||
|
path: "{{ sigvild_gallery_local_project_path }}/sigvild-kit"
|
||||||
|
register: frontend_source
|
||||||
|
become: no
|
||||||
|
tags: [frontend, build]
|
||||||
|
|
||||||
|
- name: Fail if frontend source doesn't exist
|
||||||
|
fail:
|
||||||
|
msg: "Frontend source directory not found at {{ sigvild_gallery_local_project_path }}/sigvild-kit"
|
||||||
|
when: not frontend_source.stat.exists
|
||||||
|
tags: [frontend, build]
|
||||||
|
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
local_action:
|
||||||
|
module: shell
|
||||||
|
cmd: npm install
|
||||||
|
chdir: "{{ sigvild_gallery_local_project_path }}/sigvild-kit"
|
||||||
|
become: no
|
||||||
|
tags: [frontend, build]
|
||||||
|
|
||||||
|
- name: Build frontend for production
|
||||||
|
local_action:
|
||||||
|
module: shell
|
||||||
|
cmd: npm run build:production
|
||||||
|
chdir: "{{ sigvild_gallery_local_project_path }}/sigvild-kit"
|
||||||
|
become: no
|
||||||
|
tags: [frontend, build]
|
||||||
|
|
||||||
|
- name: Check if frontend build exists
|
||||||
|
local_action:
|
||||||
|
module: stat
|
||||||
|
path: "{{ sigvild_gallery_local_project_path }}/sigvild-kit/build"
|
||||||
|
register: frontend_build
|
||||||
|
become: no
|
||||||
|
tags: [frontend, build]
|
||||||
|
|
||||||
|
- name: Fail if frontend build doesn't exist
|
||||||
|
fail:
|
||||||
|
msg: "Frontend build failed - build directory not found"
|
||||||
|
when: not frontend_build.stat.exists
|
||||||
|
become: no
|
||||||
|
tags: [frontend, build]
|
||||||
|
|
||||||
|
- name: Sync frontend files to web root
|
||||||
|
synchronize:
|
||||||
|
src: "{{ sigvild_gallery_local_project_path }}/sigvild-kit/build/"
|
||||||
|
dest: "{{ sigvild_gallery_web_root }}/"
|
||||||
|
delete: yes
|
||||||
|
rsync_opts:
|
||||||
|
- "--exclude=.git"
|
||||||
|
- "--chown={{ sigvild_gallery_user }}:{{ sigvild_gallery_user }}"
|
||||||
|
tags: [frontend]
|
||||||
91
roles/sigvild-gallery/tasks/main.yml
Normal file
91
roles/sigvild-gallery/tasks/main.yml
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
# Sigvild Gallery Deployment Tasks
|
||||||
|
|
||||||
|
- name: Install required packages
|
||||||
|
pacman:
|
||||||
|
name:
|
||||||
|
- rsync
|
||||||
|
state: present
|
||||||
|
- name: Create sigvild gallery user
|
||||||
|
user:
|
||||||
|
name: "{{ sigvild_gallery_user }}"
|
||||||
|
system: yes
|
||||||
|
shell: /bin/bash
|
||||||
|
home: "{{ sigvild_gallery_home }}"
|
||||||
|
create_home: yes
|
||||||
|
|
||||||
|
- name: Create directories
|
||||||
|
file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ sigvild_gallery_user }}"
|
||||||
|
group: "{{ sigvild_gallery_user }}"
|
||||||
|
mode: '0755'
|
||||||
|
loop:
|
||||||
|
- "{{ sigvild_gallery_home }}"
|
||||||
|
- "{{ sigvild_gallery_data_dir }}"
|
||||||
|
- "{{ sigvild_gallery_web_root }}"
|
||||||
|
|
||||||
|
- name: Build and deploy backend
|
||||||
|
include_tasks: deploy_backend.yml
|
||||||
|
tags: [backend, build]
|
||||||
|
|
||||||
|
- name: Build and deploy frontend
|
||||||
|
include_tasks: deploy_frontend.yml
|
||||||
|
tags: [frontend, build]
|
||||||
|
|
||||||
|
- name: Deploy systemd service
|
||||||
|
template:
|
||||||
|
src: sigvild-gallery.service.j2
|
||||||
|
dest: /etc/systemd/system/sigvild-gallery.service
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
notify:
|
||||||
|
- reload systemd
|
||||||
|
- restart sigvild-gallery
|
||||||
|
tags: [backend, service]
|
||||||
|
|
||||||
|
- name: Deploy Caddy configurations
|
||||||
|
template:
|
||||||
|
src: "{{ item.src }}"
|
||||||
|
dest: "{{ caddy_sites_enabled_dir }}/{{ item.dest }}"
|
||||||
|
owner: root
|
||||||
|
group: "{{ caddy_user }}"
|
||||||
|
mode: '0644'
|
||||||
|
loop:
|
||||||
|
- { src: 'frontend.caddy.j2', dest: 'sigvild-frontend.caddy' }
|
||||||
|
- { src: 'api.caddy.j2', dest: 'sigvild-api.caddy' }
|
||||||
|
notify: reload caddy
|
||||||
|
tags: [caddy, frontend, backend]
|
||||||
|
|
||||||
|
- name: Enable and start sigvild-gallery service
|
||||||
|
systemd:
|
||||||
|
name: sigvild-gallery
|
||||||
|
enabled: "{{ sigvild_gallery_service_enabled }}"
|
||||||
|
state: "{{ sigvild_gallery_service_state }}"
|
||||||
|
daemon_reload: yes
|
||||||
|
tags: [backend, service]
|
||||||
|
|
||||||
|
- name: Create superuser account
|
||||||
|
command: >
|
||||||
|
{{ sigvild_gallery_binary }} superuser upsert
|
||||||
|
"{{ vault_pb_su_email }}"
|
||||||
|
"{{ vault_pb_su_password }}"
|
||||||
|
args:
|
||||||
|
chdir: "{{ sigvild_gallery_home }}"
|
||||||
|
become: yes
|
||||||
|
become_user: "{{ sigvild_gallery_user }}"
|
||||||
|
register: superuser_result
|
||||||
|
failed_when: superuser_result.rc != 0
|
||||||
|
|
||||||
|
- name: Verify gallery health
|
||||||
|
uri:
|
||||||
|
url: "https://{{ sigvild_gallery_api_domain }}/api/health"
|
||||||
|
method: GET
|
||||||
|
status_code: [200, 404] # 404 is ok if health endpoint doesn't exist yet
|
||||||
|
timeout: 15
|
||||||
|
retries: 5
|
||||||
|
delay: 5
|
||||||
|
ignore_errors: yes
|
||||||
|
tags: [verify]
|
||||||
45
roles/sigvild-gallery/templates/api.caddy.j2
Normal file
45
roles/sigvild-gallery/templates/api.caddy.j2
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{{ sigvild_gallery_api_domain }} {
|
||||||
|
reverse_proxy {{ sigvild_gallery_host }}:{{ sigvild_gallery_port }} {
|
||||||
|
header_up Host {upstream_hostport}
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
header_up X-Forwarded-Proto https
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
health_uri /api/health
|
||||||
|
health_timeout 5s
|
||||||
|
health_interval 30s
|
||||||
|
}
|
||||||
|
|
||||||
|
# CORS headers for frontend domain
|
||||||
|
@cors {
|
||||||
|
header Origin https://{{ sigvild_gallery_frontend_domain }}
|
||||||
|
}
|
||||||
|
header @cors {
|
||||||
|
Access-Control-Allow-Origin "https://{{ sigvild_gallery_frontend_domain }}"
|
||||||
|
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH, OPTIONS"
|
||||||
|
Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
|
||||||
|
Access-Control-Allow-Credentials true
|
||||||
|
Access-Control-Max-Age 86400
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle preflight requests
|
||||||
|
@preflight {
|
||||||
|
method OPTIONS
|
||||||
|
}
|
||||||
|
respond @preflight 204
|
||||||
|
|
||||||
|
# Security headers for API
|
||||||
|
header {
|
||||||
|
X-Frame-Options DENY
|
||||||
|
X-Content-Type-Options nosniff
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
Referrer-Policy strict-origin-when-cross-origin
|
||||||
|
}
|
||||||
|
|
||||||
|
# API logging
|
||||||
|
log {
|
||||||
|
output file /var/log/caddy/sigvild-api.log
|
||||||
|
level INFO
|
||||||
|
format json
|
||||||
|
}
|
||||||
|
}
|
||||||
42
roles/sigvild-gallery/templates/frontend.caddy.j2
Normal file
42
roles/sigvild-gallery/templates/frontend.caddy.j2
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{{ sigvild_gallery_frontend_domain }} {
|
||||||
|
root * {{ sigvild_gallery_web_root }}
|
||||||
|
file_server
|
||||||
|
|
||||||
|
# SPA routing - serve index.html for all routes
|
||||||
|
try_files {path} /index.html
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
header {
|
||||||
|
X-Frame-Options DENY
|
||||||
|
X-Content-Type-Options nosniff
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
Referrer-Policy strict-origin-when-cross-origin
|
||||||
|
Permissions-Policy "geolocation=(), microphone=(), camera=()"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache static assets aggressively
|
||||||
|
@static {
|
||||||
|
path /_app/* /assets/* /icons/* *.ico *.png *.jpg *.jpeg *.svg *.webp *.woff *.woff2
|
||||||
|
}
|
||||||
|
header @static {
|
||||||
|
Cache-Control "public, max-age=31536000, immutable"
|
||||||
|
Vary "Accept-Encoding"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache HTML with shorter duration
|
||||||
|
@html {
|
||||||
|
path *.html /
|
||||||
|
}
|
||||||
|
header @html {
|
||||||
|
Cache-Control "public, max-age=3600, must-revalidate"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enable compression
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
# Logging for debugging (can be removed in production)
|
||||||
|
log {
|
||||||
|
output file /var/log/caddy/sigvild-frontend.log
|
||||||
|
level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
36
roles/sigvild-gallery/templates/sigvild-gallery.service.j2
Normal file
36
roles/sigvild-gallery/templates/sigvild-gallery.service.j2
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Sigvild Wedding Gallery API
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User={{ sigvild_gallery_user }}
|
||||||
|
Group={{ sigvild_gallery_user }}
|
||||||
|
WorkingDirectory={{ sigvild_gallery_home }}
|
||||||
|
ExecStart={{ sigvild_gallery_binary }} serve --http={{ sigvild_gallery_host }}:{{ sigvild_gallery_port }}
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
Environment="SIGVILD_ENVIRONMENT"="production" # Lets caddy handle CORS
|
||||||
|
Environment="HOST_USERNAME={{ sigvild_gallery_host_username }}"
|
||||||
|
Environment="HOST_PASSWORD={{ sigvild_gallery_host_password }}"
|
||||||
|
Environment="HOST_DISPLAY_NAME=Wedding Host"
|
||||||
|
Environment="GUEST_USERNAME={{ sigvild_gallery_guest_username }}"
|
||||||
|
Environment="GUEST_PASSWORD={{ sigvild_gallery_guest_password }}"
|
||||||
|
Environment="GUEST_DISPLAY_NAME=Wedding Guest"
|
||||||
|
|
||||||
|
# Restart configuration
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
# Security sandboxing
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
ReadWritePaths={{ sigvild_gallery_data_dir }}
|
||||||
|
|
||||||
|
# Allow binding to port (if needed)
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
8
site.yml
8
site.yml
@@ -2,7 +2,7 @@
|
|||||||
# Core infrastructure deployment with security hardening first
|
# Core infrastructure deployment with security hardening first
|
||||||
|
|
||||||
# Security hardening establishes secure foundation before web services
|
# Security hardening establishes secure foundation before web services
|
||||||
- import_playbook: playbooks/security.yml
|
# - import_playbook: playbooks/security.yml
|
||||||
|
|
||||||
- name: Deploy Core Infrastructure
|
- name: Deploy Core Infrastructure
|
||||||
hosts: arch-vps
|
hosts: arch-vps
|
||||||
@@ -10,8 +10,10 @@
|
|||||||
gather_facts: yes
|
gather_facts: yes
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
- role: caddy
|
# - role: caddy
|
||||||
tags: ['caddy', 'infrastructure', 'web']
|
# tags: ['caddy', 'infrastructure', 'web']
|
||||||
|
- role: sigvild-gallery
|
||||||
|
tags: ['sigvild', 'gallery', 'wedding']
|
||||||
|
|
||||||
post_tasks:
|
post_tasks:
|
||||||
- name: Verify Caddy API is accessible
|
- name: Verify Caddy API is accessible
|
||||||
|
|||||||
Reference in New Issue
Block a user