bashgen/app.py

192 lines
7.6 KiB
Python

# FastAPI backend for bash script generator
from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse, Response
from fastapi.templating import Jinja2Templates
from jinja2 import Environment, FileSystemLoader, StrictUndefined
from datetime import datetime
from pathlib import Path
# Initialize FastAPI app
app = FastAPI(title="Bash Script Generator")
templates = Jinja2Templates(directory="templates")
# Jinja2 environment for script template rendering
jinja_env = Environment(
loader=FileSystemLoader("templates"),
undefined=StrictUndefined,
autoescape=False,
trim_blocks=True,
lstrip_blocks=True,
)
def _bool(v: str | None) -> bool:
# HTML checkbox sends value only when checked
return v is not None
@app.get("/", response_class=HTMLResponse)
def index(request: Request):
"""Serve the main HTML form page"""
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/generate")
def generate(
# feature flags - System Setup
system_update: str | None = Form(default=None),
auto_security_updates: str | None = Form(default=None),
setup_timezone: str | None = Form(default=None),
setup_hostname: str | None = Form(default=None),
setup_ntp: str | None = Form(default=None),
setup_swap: str | None = Form(default=None),
# feature flags - Security
ssh_harden: str | None = Form(default=None),
install_fail2ban: str | None = Form(default=None),
prelogin_banner: str | None = Form(default=None),
banner_type: str = Form(default="default"), # "default" or "dod_cmmc"
postlogin_banner: str | None = Form(default=None),
ssh_2fa: str | None = Form(default=None),
# feature flags - Docker & Services
install_docker: str | None = Form(default=None),
docker_admin_user: str | None = Form(default=None),
open_ports: str | None = Form(default=None),
combine_lan: str | None = Form(default=None),
# feature flags - User Management
create_admin_user: str | None = Form(default=None),
# feature flags - Monitoring
install_monitoring_tools: str | None = Form(default=None),
install_build_tools: str | None = Form(default=None),
# params
admin_username: str = Form(default="datamng"),
ssh_port: int = Form(default=22),
hostname: str = Form(default=""),
timezone: str = Form(default="UTC"),
new_admin_username: str = Form(default="admin"),
swap_size_gb: int = Form(default=2),
docker_data_dir: str = Form(default="/opt/docker"),
# firewall ports (comma-separated)
ports_csv: str = Form(default="22,80,81,443"),
# netplan params
lan_interfaces_csv: str = Form(default="eth0,eth1"),
static_ip_cidr: str = Form(default="192.168.1.9/24"),
gateway_ip: str = Form(default="192.168.1.1"),
dns_csv: str = Form(default="1.1.1.1,8.8.8.8"),
# owner information
owner_name: str = Form(default="Scardus"),
owner_website: str = Form(default="https://scardustech.com"),
owner_email: str = Form(default="info@scardustech.com"),
# SSH keys (multiline)
ssh_public_keys: str = Form(default=""),
):
"""Generate bash script based on form inputs"""
# Load banner templates from markdown files
# Try multiple paths to support both local development and Docker container
app_path = Path(__file__).parent
base_path = app_path.parent
# Determine which pre-login banner to use based on banner_type
banner_filename = "loginbanner_dod_cmmc.md.template" if banner_type == "dod_cmmc" else "loginbanner.md.template"
# Priority order: 1) workingscope directory, 2) templates directory, 3) Docker workingscope
prelogin_banner_path = base_path / "workingscope" / "loginbanner.md"
postlogin_banner_path = base_path / "workingscope" / "postloginbanner.md"
# Fallback to templates directory (for repository templates)
if not prelogin_banner_path.exists():
prelogin_banner_path = app_path / "templates" / banner_filename
if not postlogin_banner_path.exists():
postlogin_banner_path = app_path / "templates" / "postloginbanner.md.template"
# Final fallback: Docker container workingscope directory
if not prelogin_banner_path.exists():
# Try DOD CMMC banner if selected, otherwise default
if banner_type == "dod_cmmc":
prelogin_banner_path = Path("workingscope") / "loginbanner_dod_cmmc.md"
else:
prelogin_banner_path = Path("workingscope") / "loginbanner.md"
if not postlogin_banner_path.exists():
postlogin_banner_path = Path("workingscope") / "postloginbanner.md"
# Read banner content from markdown files
prelogin_text = ""
postlogin_text = ""
if prelogin_banner_path.exists():
with open(prelogin_banner_path, "r", encoding="utf-8") as f:
prelogin_text = f.read()
if postlogin_banner_path.exists():
with open(postlogin_banner_path, "r", encoding="utf-8") as f:
postlogin_text = f.read()
# Build context for Jinja2 template
ctx = {
"meta": {
"generated_at": datetime.utcnow().isoformat() + "Z",
},
"flags": {
# System Setup
"system_update": _bool(system_update),
"auto_security_updates": _bool(auto_security_updates),
"setup_timezone": _bool(setup_timezone),
"setup_hostname": _bool(setup_hostname),
"setup_ntp": _bool(setup_ntp),
"setup_swap": _bool(setup_swap),
# Security
"ssh_harden": _bool(ssh_harden),
"install_fail2ban": _bool(install_fail2ban),
"prelogin_banner": _bool(prelogin_banner),
"postlogin_banner": _bool(postlogin_banner),
"ssh_2fa": _bool(ssh_2fa),
# Docker & Services
"install_docker": _bool(install_docker),
"docker_admin_user": _bool(docker_admin_user),
"open_ports": _bool(open_ports),
"combine_lan": _bool(combine_lan),
# User Management
"create_admin_user": _bool(create_admin_user),
# Monitoring
"install_monitoring_tools": _bool(install_monitoring_tools),
"install_build_tools": _bool(install_build_tools),
},
"params": {
"admin_username": admin_username.strip(),
"ssh_port": int(ssh_port),
"hostname": hostname.strip(),
"timezone": timezone.strip(),
"new_admin_username": new_admin_username.strip(),
"swap_size_gb": int(swap_size_gb),
"docker_data_dir": docker_data_dir.strip(),
"ports": [p.strip() for p in ports_csv.split(",") if p.strip()],
"lan_ifaces": [i.strip() for i in lan_interfaces_csv.split(",") if i.strip()],
"static_ip_cidr": static_ip_cidr.strip(),
"gateway_ip": gateway_ip.strip(),
"dns": [d.strip() for d in dns_csv.split(",") if d.strip()],
"owner_name": owner_name.strip(),
"owner_website": owner_website.strip(),
"owner_email": owner_email.strip(),
"ssh_public_keys": [k.strip() for k in ssh_public_keys.split("\n") if k.strip()],
"prelogin_text": prelogin_text,
"postlogin_text": postlogin_text,
},
}
# Render bash script from template
tpl = jinja_env.get_template("script.sh.j2")
script = tpl.render(**ctx)
# Return as downloadable file
filename = "setup-server.sh"
return Response(
content=script,
media_type="application/x-sh",
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)