google-agents-cli-scaffold
Project scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.
home-server-role-creator
Complete guide for adding new self-hosted applications to the home-server Ansible infrastructure. Use this skill when the user wants to add a new service, create a new role, or deploy a new self-hosted application. Covers role structure, integration patterns (firewall, NGINX, SELinux, DNS), insta...
Full skill instructions
This skill provides comprehensive guidance for adding new self-hosted applications to the home-server Ansible infrastructure. It documents all established patterns, conventions, and integration requirements to ensure consistent, secure, and maintainable role implementations.
Activate this skill when:
This skill includes detailed reference files for in-depth information:
references/role-examples.md - Complete real-world examples:
references/checklists.md - Comprehensive checklists:
Load these reference files when detailed examples or comprehensive checklists are needed.
Follow this workflow for every new service:
Determine Installation Method:
Is the service containerized?
├─ Yes → Use Podman Quadlet pattern (see references/role-examples.md: Immich)
└─ No → Is it available in DNF/RPM repositories?
├─ Yes → Use Package installation (see references/role-examples.md: Jellyfin)
└─ No → Use Binary download/installation (see references/role-examples.md: FileBrowser)
Identify Required Integrations:
Create the role directory structure:
mkdir -p roles/[service_name]/{defaults,tasks,handlers,templates,meta}
Required directories:
defaults/ - Default variables (always created)tasks/ - Task files (always created)handlers/ - Event handlers (always created)templates/ - Jinja2 templates (if service needs config files or systemd units)meta/ - Role metadata (always created)Define all configurable variables following this pattern:
---
# Default variables for [Service] role
# Service user configuration
service_user: ndelucca
service_group: ndelucca
# Directory configuration
service_base_dir: /opt/service # or /srv/service
service_working_dir: "{{ service_base_dir }}/data"
service_config_dir: "{{ service_base_dir }}/config"
# Service configuration
service_name: service
service_enabled: true
service_state: started
# Network configuration
service_bind_address: 127.0.0.1 # ALWAYS 127.0.0.1 for web services
service_port: 8080
# Firewall settings
service_firewall_enabled: false # false if behind NGINX
service_firewall_zone: FedoraServer
# SELinux configuration
service_manage_selinux: true
See references/checklists.md for complete variable definition checklist.
Orchestration file that imports modular task files:
---
# Main entry point for [Service] role
- name: Include preflight checks
ansible.builtin.import_tasks: preflight.yml
tags: ['service', 'preflight']
- name: Install [Service]
ansible.builtin.import_tasks: install.yml
tags: ['service', 'install']
- name: Configure [Service] application
ansible.builtin.import_tasks: configure.yml
tags: ['service', 'configure']
when: service_use_config_file | bool
- name: Configure systemd service
ansible.builtin.import_tasks: service.yml
tags: ['service', 'systemd']
- name: Configure SELinux
ansible.builtin.import_tasks: selinux.yml
tags: ['service', 'selinux']
when: service_manage_selinux | bool
Create these task files based on service type:
Always Required:
preflight.yml - OS verification, directory creationinstall.yml - Service installation (method varies by type)service.yml - Systemd service managementselinux.yml - SELinux contexts and portsConditional:
configure.yml - If service needs configuration filesrepository.yml - If package needs external repositoryquadlet.yml - If using Podman containersFor detailed implementation examples, see references/role-examples.md.
Standard Services:
---
# Handlers for [Service] role
- name: daemon-reload
ansible.builtin.systemd:
daemon_reload: true
become: true
- name: restart service
ansible.builtin.systemd:
name: "{{ service_name }}"
state: restarted
become: true
- name: apply selinux context
ansible.builtin.command: "restorecon -Rv {{ item }}"
become: true
loop:
- "{{ service_install_dir }}/service"
- "{{ service_working_dir }}"
changed_when: false
Rootless Podman Services:
---
# Handlers for rootless Podman service
- name: daemon-reload-user
ansible.builtin.systemd:
daemon_reload: true
scope: user
become: true
become_user: "{{ service_user }}"
environment:
XDG_RUNTIME_DIR: "/run/user/{{ service_uid }}"
- name: restart service-pod
ansible.builtin.systemd:
name: "{{ service_name }}"
state: restarted
scope: user
become: true
become_user: "{{ service_user }}"
environment:
XDG_RUNTIME_DIR: "/run/user/{{ service_uid }}"
---
galaxy_info:
author: Naza
description: Install and configure [Service] on Fedora
license: MIT
min_ansible_version: '2.13'
platforms:
- name: Fedora
versions:
- all
dependencies: []
collections:
- community.general
- ansible.posix
Create roles/firewall/tasks/[service_name].yml:
Pattern A: Service Behind NGINX (Most Common)
---
# [Service] is behind NGINX reverse proxy
# Access via [subdomain].ndelucca-server.com on ports 80/443
- name: Remove old direct port from firewall
ansible.posix.firewalld:
port: "{{ service_port }}/tcp"
zone: "{{ service_firewall_zone }}"
permanent: true
immediate: true
state: disabled
become: true
notify: reload firewalld
ignore_errors: true
Pattern B: Service Needs Direct Access
---
# Firewall configuration for [Service]
- name: Configure firewall ports
ansible.posix.firewalld:
port: "{{ service_port }}/tcp"
zone: "{{ service_firewall_zone }}"
permanent: true
immediate: true
state: enabled
become: true
notify: reload firewalld
Add import to roles/firewall/tasks/main.yml:
- name: Configure firewall for [Service]
ansible.builtin.import_tasks: service.yml
when: service_firewall_enabled | default(true)
tags: ['firewall-service']
If service has web interface:
Add port variable to roles/nginx/defaults/main.yml:
nginx_service_port: 8080
Create roles/nginx/templates/conf.d/[service].conf.j2:
# HTTP
server {
listen 80;
server_name [subdomain].{{ nginx_domain }};
location / {
proxy_pass http://127.0.0.1:{{ nginx_service_port }};
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 $scheme;
}
}
# HTTPS
server {
listen 443 ssl;
http2 on;
server_name [subdomain].{{ nginx_domain }};
ssl_certificate {{ nginx_ssl_certificate }};
ssl_certificate_key {{ nginx_ssl_certificate_key }};
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://127.0.0.1:{{ nginx_service_port }};
# ... same proxy headers as HTTP
}
}
NGINX Features to Add When Needed:
WebSocket support: For real-time features (Jellyfin, Immich)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
Large upload support: For file/media services (FileBrowser, Immich)
client_max_body_size 50G;
client_body_timeout 600s;
proxy_read_timeout 600s;
proxy_buffering off;
proxy_request_buffering off;
roles/nginx/tasks/configure.yml loopStandard SELinux configuration in tasks/selinux.yml:
---
# Configure SELinux for [Service]
- name: Check SELinux status
ansible.builtin.command: getenforce
register: selinux_status
changed_when: false
- name: Install SELinux packages
ansible.builtin.dnf:
name: policycoreutils-python-utils
state: present
become: true
when: selinux_status.stdout == "Enforcing"
- name: Set SELinux context for binary
community.general.sefcontext:
target: "{{ service_install_dir }}/service"
setype: bin_t
state: present
become: true
when: selinux_status.stdout == "Enforcing"
notify: apply selinux context
- name: Set SELinux context for directories
community.general.sefcontext:
target: "{{ item.path }}(/.*)?"
setype: "{{ item.type }}"
state: present
become: true
loop:
- { path: "{{ service_working_dir }}", type: "var_lib_t" }
- { path: "{{ service_data_dir }}", type: "container_file_t" } # or public_content_rw_t
when: selinux_status.stdout == "Enforcing"
notify: apply selinux context
- name: Allow service to bind to custom port
community.general.seport:
ports: "{{ service_port }}"
proto: tcp
setype: http_port_t
state: present
become: true
when:
- selinux_status.stdout == "Enforcing"
- service_port != 80 and service_port != 443
Common SELinux Types:
bin_t - Executablesvar_lib_t - Service directoriespublic_content_rw_t - Writable contentcontainer_file_t - Container volumeshttp_port_t - HTTP portsAdd to inventory/host_vars/ndelucca-server.yml:
adguard_dns_rewrites:
# ... existing entries ...
- domain: [subdomain].ndelucca-server.com
answer: 192.168.10.10
enabled: true
Subdomain naming: Use short, descriptive names (files, jellyfin, torrent, gallery, cockpit)
Create playbooks/[service].yml:
---
# [Service]-specific playbook
# Usage: ansible-playbook playbooks/[service].yml -l ndelucca-server
- name: Install and configure [Service]
hosts: homeservers
gather_facts: true
roles:
- [service_name]
Add role to playbooks/site.yml:
- role: [service_name]
tags: ['service', 'category']
ansible-playbook playbooks/[service].yml --syntax-check -l ndelucca-server
CRITICAL: Always use ansible-host-limiter skill when running playbooks!
ansible-playbook playbooks/[service].yml -l ndelucca-server
Use references/checklists.md for comprehensive post-deployment verification checklist.
Essential checks:
# Service status
ansible ndelucca-server -m ansible.builtin.systemd -a "name=[service]" --become
# Service listening
ansible ndelucca-server -m shell -a "ss -tlnp | grep [port]"
# Test web access (if applicable)
curl http://[subdomain].ndelucca-server.com
curl https://[subdomain].ndelucca-server.com
Key tasks:
/usr/local/binSee: references/role-examples.md - FileBrowser example
Key tasks:
See: references/role-examples.md - Jellyfin example
Key tasks:
See: references/role-examples.md - Immich example
Always use ansible-host-limiter skill - Every ansible-playbook command MUST include -l ndelucca-server
Service locality - All web services MUST bind to 127.0.0.1, never 0.0.0.0
NGINX as gateway - All web services MUST be accessed through NGINX reverse proxy
Firewall orchestration - Firewall rules live in central roles/firewall/, not in service roles
SELinux is mandatory - Always configure SELinux contexts and ports
User consistency - Default to ndelucca user for all services
Rootless when possible - Prefer rootless Podman over rootful containers
All service role variables follow this pattern:
[service]_user # Service user (default: ndelucca)
[service]_group # Service group (default: ndelucca)
[service]_port # Service port
[service]_bind_address # Bind address (default: 127.0.0.1)
[service]_base_dir # Base directory (/srv or /opt)
[service]_working_dir # Working/data directory
[service]_config_dir # Configuration directory
[service]_service_name # Systemd service name
[service]_service_enabled # Enable on boot (default: true)
[service]_service_state # Service state (default: started)
[service]_firewall_enabled # Enable firewall (default: false if behind NGINX)
[service]_firewall_zone # Firewall zone (default: FedoraServer)
[service]_manage_selinux # Manage SELinux (default: true)
roles/[service_name]/ # Role directory (lowercase, underscores)
playbooks/[service_name].yml # Playbook (matches role name)
roles/firewall/tasks/[service_name].yml # Firewall tasks
roles/nginx/templates/conf.d/[service].conf.j2 # NGINX config (short name)
/etc/systemd/system/[service_name].service # Systemd unit
[subdomain].ndelucca-server.com # DNS subdomain (short, descriptive)
Binary installations:
/usr/local/bin/[service]/opt/[service] or /srv/[service]Package installations:
/var/lib/[service] or system defaultContainer installations:
/srv/[service]/config/srv/[service]/data or custom location/etc/containers/systemd/users/[uid]/For services needing external repository (e.g., RPMFusion):
Create tasks/repository.yml:
---
- name: Check if repository is enabled
ansible.builtin.command: dnf repolist --enabled
register: repo_list
changed_when: false
- name: Install repository
ansible.builtin.dnf:
name: "[repository_rpm_url]"
state: present
disable_gpg_check: true
become: true
when: "'repo-name' not in repo_list.stdout"
For services using custom storage (e.g., external disk):
Define variable in defaults/main.yml:
service_data_location: "{{ service_base_dir }}/data"
Override in host_vars/ndelucca-server.yml:
service_data_location: /srv/disks/D-Draco/media/Service
Apply SELinux context in tasks/selinux.yml:
- name: Set SELinux context for custom storage
community.general.sefcontext:
target: "{{ service_data_location }}(/.*)?"
setype: container_file_t # or public_content_rw_t
state: present
For dependent services (e.g., AdGuard must start before NGINX):
---
# Use 'listen' to chain handlers
- name: restart service
ansible.builtin.systemd:
name: "{{ service_name }}"
state: restarted
become: true
listen: restart service
- name: wait for service
ansible.builtin.wait_for:
host: 127.0.0.1
port: "{{ service_port }}"
listen: restart service
- name: start dependent service
ansible.builtin.systemd:
name: dependent-service
state: started
become: true
listen: restart service
For each new service, expect to create/modify:
ausearch -m avchttpd_can_network_connectss -tlnp# Syntax check
ansible-playbook playbooks/service.yml --syntax-check -l ndelucca-server
# Deploy
ansible-playbook playbooks/service.yml -l ndelucca-server
# Check service
ansible ndelucca-server -m systemd -a "name=service" --become
# Check logs
ansible ndelucca-server -m shell -a "journalctl -u service -n 50" --become
# Check SELinux
ansible ndelucca-server -m shell -a "ausearch -m avc -ts recent" --become
When adding a new service:
-l ndelucca-server, verifyCritical reminders:
For detailed examples, see references/role-examples.md.
For comprehensive checklists, see references/checklists.md.
Project scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.
Set up tracing, logging, and monitoring for deployed ADK agents across Cloud Trace, BigQuery, and third-party platforms.
Enterprise Azure infrastructure architect generating Bicep or Terraform from workload descriptions.
Plan and configure production-ready Azure Kubernetes Service clusters with Day-0 and Day-1 best practices.
Raw mechanical interfaces fusing Swiss typographic print with military terminal aesthetics. Rigid grids, extreme type scale contrast, utilitarian color, analog degradation effects. For data-heavy dashboards, portfolios, or editorial sites that need to feel like declassified blueprints.
Web search, scraping, extraction, crawling, and monitoring via ScrapeGraph AI CLI.
Skill for working with Firebase Hosting (Classic). Use this when you want to deploy static web apps, Single Page Apps (SPAs), or simple microservices. Do NOT use for Firebase App Hosting.
Deploy and manage web apps with Firebase App Hosting. Use this skill when deploying Next.js/Angular apps with backends.
Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment".
Build SEO-optimized pages at scale using templates, data, and proven playbook patterns.
Design and build isolated, reusable Convex backend components with clear boundaries and app-facing wrappers.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Opus.ai: Revolutionize Your Web Experience
Build a no-code AI app in minutes.
An IDE for code migration from legacy to modern frameworks through coding agents.
Automate CGI animation in live-action scenes
Branded artistic QR-code concepts
Launch a website in seconds with AI.
Streamline Your Coding Experience with AI Code Helper
Convert any screenshot or design to clean code.