Files
resolutionflow/backend/scripts/seed_maintenance_flows.py
chihlasm 058e2c5a23 fix: remove folder icons, add single run for maintenance, fix batch 400
- Remove AddToFolderMenu from grid/list/table library views (folders removed)
- Add single "Run" button to MaintenanceFlowDetailPage alongside Batch Launch
- Change seed script to publish maintenance flows (batch rejects drafts)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:35:52 -05:00

869 lines
44 KiB
Python

#!/usr/bin/env python3
"""
Maintenance Flow Seed Script for ResolutionFlow.
Creates sample maintenance flows (scheduled multi-target tasks) for common MSP
infrastructure maintenance work.
Run from the backend directory with:
python -m scripts.seed_maintenance_flows --email admin@resolutionflow.example.com --password TestPass123!
Requirements:
- Backend server must be running (uvicorn app.main:app)
"""
import asyncio
import argparse
import httpx
from typing import Any
# API Configuration
API_BASE_URL = "http://localhost:8000/api/v1"
ADMIN_EMAIL = None
ADMIN_PASSWORD = None
# =============================================================================
# MAINTENANCE FLOW DEFINITIONS
# =============================================================================
def get_sonicwall_firmware_flow() -> dict[str, Any]:
"""SonicWall Firewall Firmware Update — check and install latest firmware."""
return {
"name": "SonicWall Firmware Update",
"description": "Check for and install the latest firmware on SonicWall firewalls. Includes pre-flight backup, compatibility check, staged install, and post-update verification. Designed for batch execution across multiple client firewalls.",
"tree_type": "maintenance",
"tags": ["sonicwall", "firmware", "firewall", "maintenance", "security"],
"intake_form": [
{
"variable_name": "firewall_ip",
"label": "Firewall Management IP",
"field_type": "ip_address",
"required": True,
"placeholder": "e.g. 192.168.1.1",
"help_text": "Management IP or FQDN of the SonicWall appliance",
"group_name": "Firewall Details",
"display_order": 1,
},
{
"variable_name": "firewall_model",
"label": "SonicWall Model",
"field_type": "select",
"required": True,
"options": [
"TZ270 / TZ270W",
"TZ370 / TZ370W",
"TZ470 / TZ470W",
"TZ570 / TZ570W",
"TZ670",
"NSa 2700",
"NSa 3700",
"NSa 4700",
"NSa 5700",
"NSa 6700",
"NSsp 10700 / 11700 / 13700",
"NSv (Virtual)",
"Other / Legacy",
],
"group_name": "Firewall Details",
"display_order": 2,
},
{
"variable_name": "current_firmware",
"label": "Current Firmware Version",
"field_type": "text",
"required": False,
"placeholder": "e.g. 7.0.1-5145",
"help_text": "Check under System > Status in the SonicWall UI",
"group_name": "Firewall Details",
"display_order": 3,
},
{
"variable_name": "maintenance_window",
"label": "Maintenance Window",
"field_type": "text",
"required": True,
"placeholder": "e.g. Sat 10pm-2am EST",
"help_text": "Approved maintenance window for this client",
"group_name": "Scheduling",
"display_order": 4,
},
{
"variable_name": "client_name",
"label": "Client Name",
"field_type": "text",
"required": True,
"placeholder": "e.g. Contoso Ltd",
"group_name": "Scheduling",
"display_order": 5,
},
{
"variable_name": "ticket_number",
"label": "Ticket Number",
"field_type": "text",
"required": False,
"placeholder": "e.g. TKT-2024-1234",
"group_name": "Scheduling",
"display_order": 6,
},
],
"tree_structure": {
"steps": [
{
"id": "step_1",
"type": "procedure_step",
"title": "Pre-Flight: Verify Access & Current State",
"content_type": "verification",
"estimated_minutes": 5,
"description": (
"Log into the SonicWall at **[VAR:firewall_ip]** for **[VAR:client_name]**.\n\n"
"**Verify:**\n"
"1. Navigate to **System > Status** — note current firmware version\n"
"2. Confirm the appliance model matches **[VAR:firewall_model]**\n"
"3. Check **System > Diagnostics > Tech Support Report** — note any existing issues\n"
"4. Verify HA status if applicable (both units should be in sync)\n"
"5. Check VPN tunnel status — note how many tunnels are active\n"
"6. Confirm you are within the maintenance window: **[VAR:maintenance_window]**"
),
"verification": {
"type": "checkbox",
"prompt": "Logged in, current firmware noted, and within maintenance window?"
},
},
{
"id": "step_2",
"type": "procedure_step",
"title": "Export Configuration Backup",
"content_type": "action",
"estimated_minutes": 3,
"description": (
"**Create a full configuration backup before any changes.**\n\n"
"1. Navigate to **System > Settings > Export Settings**\n"
"2. Click **Export** to download the `.exp` configuration file\n"
"3. Save the file as: `[VAR:client_name]_SonicWall_[VAR:firewall_model]_pre-firmware_YYYY-MM-DD.exp`\n"
"4. Store the backup in the client's documentation folder and password vault\n\n"
"**Also export:**\n"
"- **System > Diagnostics > Tech Support Report** (for rollback reference)\n\n"
"**Critical:** Do NOT proceed without a verified backup file."
),
"verification": {
"type": "checkbox",
"prompt": "Configuration backup exported and stored securely?"
},
},
{
"id": "step_3",
"type": "procedure_step",
"title": "Check for Available Firmware",
"content_type": "action",
"estimated_minutes": 5,
"description": (
"**Check MySonicWall for the latest firmware:**\n\n"
"1. Navigate to **System > Settings > Firmware & Backups**\n"
"2. If connected to MySonicWall, available firmware will show automatically\n"
"3. Alternatively, log into [mysonicwall.com](https://mysonicwall.com) → **Product Management** → select this appliance → **Firmware**\n\n"
"**Before downloading, check:**\n"
"- Read the **Release Notes** for the new firmware version\n"
"- Look for any known issues that affect **[VAR:firewall_model]**\n"
"- Check if the upgrade path requires intermediate firmware versions\n"
"- Verify the firmware is marked **General Release** (not Early Release unless approved)\n\n"
"**Note the target firmware version in your session notes.**"
),
"notes_enabled": True,
"verification": {
"type": "checkbox",
"prompt": "Target firmware identified and release notes reviewed?"
},
},
{
"id": "step_4",
"type": "procedure_step",
"title": "Upload & Install Firmware",
"content_type": "action",
"estimated_minutes": 15,
"warning_text": "The firewall will reboot during this step. All active connections will drop temporarily.",
"description": (
"**Install the firmware:**\n\n"
"1. Navigate to **System > Settings > Firmware & Backups**\n"
"2. Upload the downloaded firmware file (or click **Download** if fetching from MySonicWall)\n"
"3. Select **Upload Firmware (with current settings)**\n"
"4. **Important:** Select **Create backup of current firmware** before proceeding\n"
"5. Click **Boot** to begin the upgrade\n\n"
"**The appliance will:**\n"
"- Save the current firmware to the backup partition\n"
"- Flash the new firmware\n"
"- Reboot (takes 3-8 minutes depending on model)\n\n"
"**For HA pairs:** Update the secondary unit first, verify, then failover and update the primary.\n\n"
"**Wait for the appliance to come back online.** Monitor the management IP with continuous ping."
),
"commands": [
{"language": "bash", "code": "ping -t [VAR:firewall_ip]", "label": "Monitor reboot (Windows)"},
{"language": "bash", "code": "ping [VAR:firewall_ip]", "label": "Monitor reboot (Mac/Linux)"},
],
"verification": {
"type": "checkbox",
"prompt": "Firmware installed and appliance is back online?"
},
},
{
"id": "step_5",
"type": "procedure_step",
"title": "Post-Update Verification",
"content_type": "verification",
"estimated_minutes": 10,
"description": (
"**Verify the firmware update was successful:**\n\n"
"1. Log into **[VAR:firewall_ip]** — confirm the login page loads\n"
"2. Navigate to **System > Status** — confirm new firmware version\n"
"3. Check **System > Diagnostics > Tech Support Report** for errors\n\n"
"**Verify critical services:**\n"
"- [ ] Internet connectivity passing through the firewall\n"
"- [ ] VPN tunnels re-established (compare count from pre-flight)\n"
"- [ ] NAT rules working (test external-facing services)\n"
"- [ ] Content filtering / security services active\n"
"- [ ] DHCP leases being issued (if SonicWall is DHCP server)\n"
"- [ ] SSL VPN / Global VPN Client connectivity\n"
"- [ ] HA sync status (if applicable)\n\n"
"**If anything is broken:** Reboot to the backup firmware partition via **System > Settings > Firmware & Backups** → Boot backup firmware."
),
"verification": {
"type": "checkbox",
"prompt": "All services verified and operational on new firmware?"
},
},
{
"id": "step_6",
"type": "procedure_step",
"title": "Document & Close",
"content_type": "informational",
"estimated_minutes": 5,
"description": (
"**Update documentation:**\n\n"
"1. Update the client's asset record with the new firmware version\n"
"2. Note the upgrade date and any issues encountered\n"
"3. Export a fresh **post-update configuration backup**\n"
"4. Update the ticket **[VAR:ticket_number]** with results\n\n"
"**Summary for [VAR:client_name]:**\n"
"- Appliance: **[VAR:firewall_model]** at **[VAR:firewall_ip]**\n"
"- Previous firmware: **[VAR:current_firmware]**\n"
"- New firmware: *(note in session)*\n"
"- Status: Operational"
),
"verification": {
"type": "checkbox",
"prompt": "Documentation updated and post-upgrade backup stored?"
},
},
{
"id": "step_end",
"type": "procedure_end",
"title": "Firmware Update Complete",
"description": (
"SonicWall firmware update complete for **[VAR:client_name]**.\n\n"
"- Appliance: **[VAR:firewall_model]** at **[VAR:firewall_ip]**\n"
"- All services verified operational\n"
"- Configuration backup stored\n\n"
"**Follow-up:** Monitor for 24-48 hours for any stability issues."
),
},
]
},
}
def get_windows_update_dc_flow() -> dict[str, Any]:
"""Windows Server 2022 Domain Controller — Windows Update maintenance."""
return {
"name": "Windows Updates — Domain Controller (Server 2022)",
"description": "Check for and install Windows updates on a Windows Server 2022 domain controller. Includes AD health checks, DFSR/SYSVOL verification, proper reboot sequencing, and post-update replication validation. Designed for batch execution across multiple DCs.",
"tree_type": "maintenance",
"tags": ["windows-server", "domain-controller", "windows-update", "active-directory", "maintenance"],
"intake_form": [
{
"variable_name": "server_name",
"label": "Server Name",
"field_type": "text",
"required": True,
"placeholder": "e.g. DC01",
"help_text": "Hostname of the domain controller",
"group_name": "Server Details",
"display_order": 1,
},
{
"variable_name": "server_ip",
"label": "Server IP Address",
"field_type": "ip_address",
"required": True,
"placeholder": "e.g. 10.0.1.10",
"group_name": "Server Details",
"display_order": 2,
},
{
"variable_name": "domain_name",
"label": "Domain Name",
"field_type": "text",
"required": True,
"placeholder": "e.g. contoso.local",
"group_name": "Server Details",
"display_order": 3,
},
{
"variable_name": "is_fsmo_holder",
"label": "FSMO Role Holder?",
"field_type": "select",
"required": True,
"options": ["No — standard DC", "Yes — holds one or more FSMO roles", "Unknown — will check"],
"help_text": "FSMO holders need extra care during patching",
"group_name": "Server Details",
"display_order": 4,
},
{
"variable_name": "maintenance_window",
"label": "Maintenance Window",
"field_type": "text",
"required": True,
"placeholder": "e.g. Sun 2am-6am EST",
"group_name": "Scheduling",
"display_order": 5,
},
{
"variable_name": "client_name",
"label": "Client Name",
"field_type": "text",
"required": True,
"placeholder": "e.g. Contoso Ltd",
"group_name": "Scheduling",
"display_order": 6,
},
{
"variable_name": "ticket_number",
"label": "Ticket Number",
"field_type": "text",
"required": False,
"placeholder": "e.g. TKT-2024-1234",
"group_name": "Scheduling",
"display_order": 7,
},
],
"tree_structure": {
"steps": [
{
"id": "step_1",
"type": "procedure_step",
"title": "Pre-Flight: AD Health Check",
"content_type": "verification",
"estimated_minutes": 10,
"description": (
"Connect to **[VAR:server_name]** (**[VAR:server_ip]**) for **[VAR:client_name]** via RDP or remote PowerShell.\n\n"
"**Run the following health checks before patching:**"
),
"commands": [
{"language": "powershell", "code": "# Check AD replication status\nrepadmin /replsummary\n\n# Check for replication failures\nrepadmin /showrepl", "label": "AD Replication Status"},
{"language": "powershell", "code": "# Run DC diagnostics\ndcdiag /v | Select-String -Pattern 'passed|failed'", "label": "DC Diagnostics"},
{"language": "powershell", "code": "# Check SYSVOL/NETLOGON shares\nnet share | Select-String 'SYSVOL|NETLOGON'\n\n# Check DFSR health\nGet-WinEvent -LogName 'DFS Replication' -MaxEvents 10 | Format-Table TimeCreated, Message -Wrap", "label": "SYSVOL & DFSR Check"},
{"language": "powershell", "code": "# Check FSMO roles on this DC\nnetdom query fsmo", "label": "FSMO Role Check"},
{"language": "powershell", "code": "# Check disk space (need at least 10GB free on C:)\nGet-PSDrive C | Select-Object @{N='FreeGB';E={[math]::Round($_.Free/1GB,2)}}", "label": "Disk Space Check"},
],
"verification": {
"type": "checkbox",
"prompt": "AD replication healthy, dcdiag passing, SYSVOL shared, sufficient disk space?"
},
},
{
"id": "step_2",
"type": "procedure_step",
"title": "Create System State Backup",
"content_type": "action",
"estimated_minutes": 15,
"description": (
"**Create a system state backup before patching.**\n\n"
"This captures Active Directory, SYSVOL, registry, and boot files — essential for DC recovery."
),
"commands": [
{"language": "powershell", "code": "# Start system state backup (requires Windows Server Backup feature)\nwbadmin start systemstatebackup -backupTarget:C: -quiet", "label": "System State Backup"},
{"language": "powershell", "code": "# If Windows Server Backup not installed:\nInstall-WindowsFeature Windows-Server-Backup -IncludeManagementTools", "label": "Install Backup Feature (if needed)"},
],
"verification": {
"type": "checkbox",
"prompt": "System state backup completed successfully?"
},
},
{
"id": "step_3",
"type": "procedure_step",
"title": "Check for & Review Available Updates",
"content_type": "action",
"estimated_minutes": 10,
"description": (
"**Scan for available updates and review before installing:**"
),
"commands": [
{"language": "powershell", "code": "# Install PSWindowsUpdate module if not present\nif (-not (Get-Module -ListAvailable PSWindowsUpdate)) {\n Install-Module PSWindowsUpdate -Force -Confirm:$false\n}\nImport-Module PSWindowsUpdate\n\n# Check for available updates\nGet-WindowsUpdate -MicrosoftUpdate | Format-Table -AutoSize KB, Size, Title", "label": "List Available Updates"},
],
"notes_enabled": True,
"verification": {
"type": "checkbox",
"prompt": "Available updates reviewed — no known bad patches in the list?"
},
},
{
"id": "step_4",
"type": "procedure_step",
"title": "Install Updates & Reboot",
"content_type": "action",
"estimated_minutes": 30,
"warning_text": "The server will reboot. All users authenticating against this DC will failover to another DC. Ensure at least one other DC is available.",
"description": (
"**Install all approved updates:**\n\n"
"**Important for [VAR:domain_name]:**\n"
"- If this is the **only DC**, schedule downtime — users will lose authentication during reboot\n"
"- If FSMO holder: verify other DCs can service requests during reboot\n"
"- Reboot will take 5-20 minutes depending on update count"
),
"commands": [
{"language": "powershell", "code": "# Install all updates and auto-reboot\nInstall-WindowsUpdate -MicrosoftUpdate -AcceptAll -AutoReboot", "label": "Install Updates (auto-reboot)"},
{"language": "powershell", "code": "# Or install without auto-reboot (manual control)\nInstall-WindowsUpdate -MicrosoftUpdate -AcceptAll -IgnoreReboot\n\n# Then reboot manually when ready\nRestart-Computer -Force", "label": "Install Updates (manual reboot)"},
{"language": "bash", "code": "ping -t [VAR:server_ip]", "label": "Monitor reboot (Windows)"},
],
"verification": {
"type": "checkbox",
"prompt": "Updates installed and server is back online?"
},
},
{
"id": "step_5",
"type": "procedure_step",
"title": "Post-Update: Verify AD Health",
"content_type": "verification",
"estimated_minutes": 10,
"description": (
"**After the server comes back online, verify everything is healthy:**"
),
"commands": [
{"language": "powershell", "code": "# Verify replication is working\nrepadmin /replsummary\nrepadmin /showrepl", "label": "AD Replication Check"},
{"language": "powershell", "code": "# Run DC diagnostics again\ndcdiag /v | Select-String -Pattern 'passed|failed'", "label": "DC Diagnostics"},
{"language": "powershell", "code": "# Verify SYSVOL/NETLOGON shares\nnet share | Select-String 'SYSVOL|NETLOGON'\n\n# Verify DNS is resolving\nnslookup [VAR:domain_name] [VAR:server_ip]", "label": "Shares & DNS Check"},
{"language": "powershell", "code": "# Check for pending reboots\nGet-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired' -ErrorAction SilentlyContinue\nGet-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending' -ErrorAction SilentlyContinue", "label": "Pending Reboot Check"},
{"language": "powershell", "code": "# Verify installed updates\nGet-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 10 | Format-Table HotFixID, InstalledOn, Description", "label": "Verify Installed Updates"},
],
"verification": {
"type": "checkbox",
"prompt": "AD replication healthy, dcdiag passing, DNS resolving, shares accessible?"
},
},
{
"id": "step_6",
"type": "procedure_step",
"title": "Document & Close",
"content_type": "informational",
"estimated_minutes": 5,
"description": (
"**Update documentation:**\n\n"
"1. Record which KB updates were installed\n"
"2. Note the new OS build number: `winver` or `[System.Environment]::OSVersion`\n"
"3. Update the client's patching log\n"
"4. Update ticket **[VAR:ticket_number]**\n\n"
"**Summary for [VAR:client_name]:**\n"
"- Server: **[VAR:server_name]** (**[VAR:server_ip]**)\n"
"- Domain: **[VAR:domain_name]**\n"
"- FSMO: **[VAR:is_fsmo_holder]**\n"
"- Status: Patched and operational"
),
"verification": {
"type": "checkbox",
"prompt": "Patching log updated and ticket documented?"
},
},
{
"id": "step_end",
"type": "procedure_end",
"title": "Windows Updates Complete",
"description": (
"Windows updates installed on **[VAR:server_name]** for **[VAR:client_name]**.\n\n"
"- Domain: **[VAR:domain_name]**\n"
"- AD replication: Healthy\n"
"- All services verified operational\n\n"
"**Follow-up:** Monitor event logs for 24 hours for any post-patch issues."
),
},
]
},
}
def get_ssl_cert_renewal_flow() -> dict[str, Any]:
"""SSL Certificate Renewal — renew and install SSL certs for client websites."""
return {
"name": "SSL Certificate Renewal",
"description": "Renew and install SSL/TLS certificates for client websites and web applications. Covers CSR generation, certificate purchase/renewal, installation, and validation. Designed for batch execution across multiple client domains.",
"tree_type": "maintenance",
"tags": ["ssl", "certificate", "tls", "website", "maintenance", "security"],
"intake_form": [
{
"variable_name": "domain_name",
"label": "Domain Name",
"field_type": "text",
"required": True,
"placeholder": "e.g. www.contoso.com",
"help_text": "Primary domain the certificate covers",
"group_name": "Certificate Details",
"display_order": 1,
},
{
"variable_name": "cert_type",
"label": "Certificate Type",
"field_type": "select",
"required": True,
"options": [
"Single Domain (DV)",
"Wildcard (*.domain.com)",
"Multi-Domain / SAN",
"Extended Validation (EV)",
"Let's Encrypt (Free/Auto)",
],
"group_name": "Certificate Details",
"display_order": 2,
},
{
"variable_name": "hosting_type",
"label": "Hosting / Server Type",
"field_type": "select",
"required": True,
"options": [
"IIS (Windows Server)",
"Apache (Linux)",
"Nginx (Linux)",
"cPanel / Plesk",
"Azure App Service",
"AWS (ELB / CloudFront)",
"Cloudflare",
"Other",
],
"group_name": "Server Details",
"display_order": 3,
},
{
"variable_name": "server_ip",
"label": "Server IP / Hostname",
"field_type": "text",
"required": True,
"placeholder": "e.g. 203.0.113.50 or web01.contoso.com",
"help_text": "IP or hostname of the web server hosting this site",
"group_name": "Server Details",
"display_order": 4,
},
{
"variable_name": "cert_provider",
"label": "Certificate Provider",
"field_type": "select",
"required": True,
"options": [
"Let's Encrypt (Certbot)",
"DigiCert",
"Sectigo / Comodo",
"GoDaddy",
"GlobalSign",
"Namecheap",
"Other",
],
"group_name": "Certificate Details",
"display_order": 5,
},
{
"variable_name": "expiry_date",
"label": "Current Cert Expiry Date",
"field_type": "text",
"required": False,
"placeholder": "e.g. 2026-03-15",
"help_text": "When the current certificate expires (YYYY-MM-DD)",
"group_name": "Certificate Details",
"display_order": 6,
},
{
"variable_name": "client_name",
"label": "Client Name",
"field_type": "text",
"required": True,
"placeholder": "e.g. Contoso Ltd",
"group_name": "Project Info",
"display_order": 7,
},
{
"variable_name": "ticket_number",
"label": "Ticket Number",
"field_type": "text",
"required": False,
"placeholder": "e.g. TKT-2024-1234",
"group_name": "Project Info",
"display_order": 8,
},
],
"tree_structure": {
"steps": [
{
"id": "step_1",
"type": "procedure_step",
"title": "Pre-Flight: Check Current Certificate",
"content_type": "verification",
"estimated_minutes": 5,
"description": (
"**Verify the current certificate status for [VAR:domain_name] ([VAR:client_name]):**\n\n"
"Check the current certificate from the command line or browser:"
),
"commands": [
{"language": "bash", "code": "# Check current certificate details and expiry\nopenssl s_client -connect [VAR:domain_name]:443 -servername [VAR:domain_name] 2>/dev/null | openssl x509 -noout -dates -subject -issuer", "label": "Check Current Cert (Linux/Mac)"},
{"language": "powershell", "code": "# Check current certificate from Windows\n$uri = 'https://[VAR:domain_name]'\n$req = [Net.HttpWebRequest]::Create($uri)\n$req.AllowAutoRedirect = $false\ntry { $req.GetResponse() | Out-Null } catch {}\n$cert = $req.ServicePoint.Certificate\nWrite-Host \"Subject: $($cert.Subject)\"\nWrite-Host \"Issuer: $($cert.Issuer)\"\nWrite-Host \"Expires: $($cert.GetExpirationDateString())\"", "label": "Check Current Cert (Windows)"},
{"language": "bash", "code": "# Quick SSL test via curl\ncurl -vI https://[VAR:domain_name] 2>&1 | grep -E 'expire|subject|issuer|SSL'", "label": "Quick SSL Check"},
],
"verification": {
"type": "checkbox",
"prompt": "Current certificate status verified — expiry date confirmed?"
},
},
{
"id": "step_2",
"type": "procedure_step",
"title": "Generate CSR (if required)",
"content_type": "action",
"estimated_minutes": 10,
"description": (
"**Generate a Certificate Signing Request (CSR) if needed.**\n\n"
"Skip this step if using **Let's Encrypt** (certbot handles CSR automatically) or if the provider generates it for you.\n\n"
"Generate the CSR on the target server (**[VAR:server_ip]**):"
),
"commands": [
{"language": "bash", "code": "# Generate private key and CSR (Linux/Apache/Nginx)\nopenssl req -new -newkey rsa:2048 -nodes \\\n -keyout [VAR:domain_name].key \\\n -out [VAR:domain_name].csr \\\n -subj \"/C=US/ST=State/L=City/O=[VAR:client_name]/CN=[VAR:domain_name]\"", "label": "Generate CSR (OpenSSL)"},
{"language": "powershell", "code": "# Generate CSR on IIS (Windows)\n# Open IIS Manager > Server Certificates > Create Certificate Request\n# Or via PowerShell:\n$params = @{\n Subject = 'CN=[VAR:domain_name],O=[VAR:client_name]'\n KeyLength = 2048\n HashAlgorithm = 'SHA256'\n CertStoreLocation = 'Cert:\\LocalMachine\\My'\n}\n$cert = New-SelfSignedCertificate @params\n# Then export CSR via certreq or IIS Manager", "label": "Generate CSR (IIS/Windows)"},
],
"verification": {
"type": "checkbox",
"prompt": "CSR generated (or skipped if using auto-renewal)?"
},
},
{
"id": "step_3",
"type": "procedure_step",
"title": "Purchase / Renew Certificate",
"content_type": "action",
"estimated_minutes": 15,
"description": (
"**Renew or purchase the certificate from [VAR:cert_provider]:**\n\n"
"**For Let's Encrypt:**"
),
"commands": [
{"language": "bash", "code": "# Let's Encrypt renewal via Certbot\nsudo certbot renew --cert-name [VAR:domain_name]\n\n# Or for a new cert:\nsudo certbot certonly --webroot -w /var/www/html -d [VAR:domain_name]", "label": "Certbot Renewal"},
],
"notes_enabled": True,
"verification": {
"type": "checkbox",
"prompt": "New certificate obtained — cert files downloaded/generated?"
},
},
{
"id": "step_4",
"type": "procedure_step",
"title": "Install Certificate on Server",
"content_type": "action",
"estimated_minutes": 10,
"description": (
"**Install the new certificate on [VAR:hosting_type] at [VAR:server_ip]:**\n\n"
"Follow the appropriate process for your hosting type. After installation, restart the web service to load the new certificate."
),
"commands": [
{"language": "bash", "code": "# Apache — update ssl.conf and restart\nsudo cp [VAR:domain_name].crt /etc/ssl/certs/\nsudo cp [VAR:domain_name].key /etc/ssl/private/\nsudo systemctl restart apache2", "label": "Install on Apache"},
{"language": "bash", "code": "# Nginx — update server block and restart\nsudo cp [VAR:domain_name].crt /etc/nginx/ssl/\nsudo cp [VAR:domain_name].key /etc/nginx/ssl/\nsudo nginx -t && sudo systemctl restart nginx", "label": "Install on Nginx"},
{"language": "powershell", "code": "# IIS — Import certificate via PowerShell\n$cert = Import-PfxCertificate -FilePath 'C:\\certs\\[VAR:domain_name].pfx' -CertStoreLocation 'Cert:\\LocalMachine\\My' -Password (ConvertTo-SecureString -String 'YourPfxPassword' -AsPlainText -Force)\n\n# Then bind in IIS Manager:\n# Sites > [site] > Bindings > Edit HTTPS > Select new certificate", "label": "Install on IIS"},
],
"verification": {
"type": "checkbox",
"prompt": "Certificate installed and web service restarted?"
},
},
{
"id": "step_5",
"type": "procedure_step",
"title": "Validate New Certificate",
"content_type": "verification",
"estimated_minutes": 5,
"description": (
"**Verify the new certificate is active and valid:**"
),
"commands": [
{"language": "bash", "code": "# Verify the new cert is serving\nopenssl s_client -connect [VAR:domain_name]:443 -servername [VAR:domain_name] 2>/dev/null | openssl x509 -noout -dates -subject\n\n# Verify certificate chain is complete\nopenssl s_client -connect [VAR:domain_name]:443 -servername [VAR:domain_name] 2>/dev/null | grep -E 'Verify return|depth'", "label": "Verify New Cert"},
{"language": "bash", "code": "# Test HTTPS response\ncurl -I https://[VAR:domain_name]", "label": "Test HTTPS"},
],
"reference_url": "https://www.ssllabs.com/ssltest/",
"verification": {
"type": "checkbox",
"prompt": "New certificate verified — correct domain, valid dates, complete chain?"
},
},
{
"id": "step_6",
"type": "procedure_step",
"title": "Document & Close",
"content_type": "informational",
"estimated_minutes": 3,
"description": (
"**Update documentation:**\n\n"
"1. Record the new certificate expiry date\n"
"2. Update the client's certificate inventory\n"
"3. Set a calendar reminder for 30 days before the new expiry\n"
"4. Store any private keys securely in the password vault\n"
"5. Update ticket **[VAR:ticket_number]**\n\n"
"**Summary for [VAR:client_name]:**\n"
"- Domain: **[VAR:domain_name]**\n"
"- Type: **[VAR:cert_type]**\n"
"- Provider: **[VAR:cert_provider]**\n"
"- Server: **[VAR:hosting_type]** at **[VAR:server_ip]**\n"
"- Previous expiry: **[VAR:expiry_date]**\n"
"- New expiry: *(note in session)*\n"
"- Status: Renewed and operational"
),
"verification": {
"type": "checkbox",
"prompt": "Certificate inventory updated and renewal reminder set?"
},
},
{
"id": "step_end",
"type": "procedure_end",
"title": "SSL Certificate Renewed",
"description": (
"SSL certificate renewed for **[VAR:domain_name]** (**[VAR:client_name]**).\n\n"
"- Certificate type: **[VAR:cert_type]**\n"
"- Provider: **[VAR:cert_provider]**\n"
"- Hosting: **[VAR:hosting_type]**\n"
"- All validations passed\n\n"
"**Follow-up:** Verify no mixed-content warnings and test from multiple browsers."
),
},
]
},
}
# =============================================================================
# SEEDING FUNCTIONS
# =============================================================================
async def get_admin_token(client: httpx.AsyncClient) -> str:
"""Authenticate and get admin token."""
response = await client.post(
f"{API_BASE_URL}/auth/login",
data={"username": ADMIN_EMAIL, "password": ADMIN_PASSWORD},
)
if response.status_code != 200:
raise Exception(f"Login failed ({response.status_code}): {response.text}")
return response.json()["access_token"]
async def create_maintenance_flow(client: httpx.AsyncClient, token: str, flow_data: dict) -> dict | None:
"""Create a maintenance flow via the API. Returns None if it already exists."""
headers = {"Authorization": f"Bearer {token}"}
flow_data["is_default"] = True
flow_data["is_public"] = True
flow_data["status"] = "published"
# Check if flow with same name exists
list_response = await client.get(f"{API_BASE_URL}/trees", headers=headers, params={"tree_type": "maintenance"})
if list_response.status_code == 200:
existing = list_response.json()
for tree in existing:
if tree["name"] == flow_data["name"]:
print(f" [SKIP] Flow '{flow_data['name']}' already exists (ID: {tree['id']})")
return None
response = await client.post(
f"{API_BASE_URL}/trees",
json=flow_data,
headers=headers,
)
if response.status_code not in (200, 201):
raise Exception(f"Failed to create flow '{flow_data['name']}': {response.text}")
tree = response.json()
print(f" [OK] Created flow '{flow_data['name']}' (ID: {tree['id']})")
return tree
async def seed_maintenance_flows():
"""Main seeding function."""
print("\n" + "=" * 60)
print(" RESOLUTIONFLOW - Maintenance Flow Templates Seeder")
print("=" * 60)
async with httpx.AsyncClient(timeout=60.0) as client:
try:
health_check = await client.get(f"{API_BASE_URL.replace('/api/v1', '')}/health")
if health_check.status_code != 200:
print(f"\n[ERROR] API health check failed: {health_check.status_code}")
return False
except httpx.ConnectError:
print("\n[ERROR] Cannot connect to API server")
print(f" Make sure the server is running at {API_BASE_URL}")
return False
print("\n[1/3] Authenticating...")
try:
token = await get_admin_token(client)
print(f" Logged in as {ADMIN_EMAIL}")
except Exception as e:
print(f" [ERROR] Failed to authenticate: {e}")
return False
print("\n[2/3] Preparing maintenance flows...")
flows_to_create = [
get_sonicwall_firmware_flow(),
get_windows_update_dc_flow(),
get_ssl_cert_renewal_flow(),
]
print(f" Found {len(flows_to_create)} flows to seed\n")
print("[3/3] Creating maintenance flows...")
created_count = 0
skipped_count = 0
for flow_data in flows_to_create:
try:
result = await create_maintenance_flow(client, token, flow_data)
if result:
created_count += 1
else:
skipped_count += 1
except Exception as e:
print(f" [FAIL] Failed to create '{flow_data['name']}': {e}")
print("\n" + "=" * 60)
print(" SEEDING COMPLETE")
print("=" * 60)
print(f" Flows created: {created_count}")
print(f" Flows skipped (already exist): {skipped_count}")
print()
return True
def main():
global ADMIN_EMAIL, ADMIN_PASSWORD, API_BASE_URL
parser = argparse.ArgumentParser(description="Seed maintenance flow templates")
parser.add_argument("--email", required=True, help="Admin email for authentication")
parser.add_argument("--password", required=True, help="Admin password")
parser.add_argument("--api-url", default=API_BASE_URL, help="API base URL")
args = parser.parse_args()
ADMIN_EMAIL = args.email
ADMIN_PASSWORD = args.password
API_BASE_URL = args.api_url
asyncio.run(seed_maintenance_flows())
if __name__ == "__main__":
main()