Initial commit: Bash script generator application
This commit is contained in:
parent
079e0c031c
commit
39248f754c
39
.dockerignore
Normal file
39
.dockerignore
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Dockerfile
|
||||||
|
docker-compose.yml
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
*.md
|
||||||
|
README*
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Dockerfile for bash script generator FastAPI app
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy requirements first for better layer caching
|
||||||
|
COPY bashgen/requirements.txt .
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY bashgen/app.py .
|
||||||
|
COPY bashgen/templates/ ./templates/
|
||||||
|
|
||||||
|
# Copy banner markdown files
|
||||||
|
COPY workingscope/loginbanner.md workingscope/postloginbanner.md workingscope/
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
|
||||||
174
app.py
Normal file
174
app.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
# 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),
|
||||||
|
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
|
||||||
|
base_path = Path(__file__).parent.parent
|
||||||
|
prelogin_banner_path = base_path / "workingscope" / "loginbanner.md"
|
||||||
|
postlogin_banner_path = base_path / "workingscope" / "postloginbanner.md"
|
||||||
|
|
||||||
|
# If not found, try relative to current working directory (for Docker)
|
||||||
|
if not prelogin_banner_path.exists():
|
||||||
|
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}"'},
|
||||||
|
)
|
||||||
18
docker-compose.yml
Normal file
18
docker-compose.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Docker Compose configuration for bash script generator
|
||||||
|
services:
|
||||||
|
bashgen:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: bashgen/Dockerfile
|
||||||
|
container_name: bashgen-app
|
||||||
|
ports:
|
||||||
|
- "8083:8080" # Map host port 8083 to container port 8080
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- PYTHONUNBUFFERED=1
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/')"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
fastapi==0.115.0
|
||||||
|
uvicorn==0.30.6
|
||||||
|
jinja2==3.1.4
|
||||||
|
python-multipart==0.0.9
|
||||||
160
templates/index.html
Normal file
160
templates/index.html
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Bash Script Generator</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; max-width: 980px; margin: 40px auto; padding: 0 16px; }
|
||||||
|
fieldset { margin: 18px 0; padding: 14px; }
|
||||||
|
label { display: block; margin: 8px 0; }
|
||||||
|
input[type="text"], input[type="number"], textarea { width: 100%; padding: 8px; }
|
||||||
|
textarea { height: 120px; }
|
||||||
|
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||||
|
.btn { padding: 10px 14px; cursor: pointer; }
|
||||||
|
.note { color: #555; font-size: 0.95em; line-height: 1.5; }
|
||||||
|
code { background: #f4f4f4; padding: 2px 4px; font-family: 'Courier New', monospace; }
|
||||||
|
.note code { background: #e8e8e8; padding: 2px 6px; border-radius: 3px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Server Setup Script Generator</h1>
|
||||||
|
<p class="note">Select options, then download a single <code>.sh</code> script.</p>
|
||||||
|
|
||||||
|
<form action="/generate" method="post">
|
||||||
|
<fieldset>
|
||||||
|
<legend>System Setup</legend>
|
||||||
|
<label><input type="checkbox" name="system_update" checked /> Initial system update & upgrade</label>
|
||||||
|
<label><input type="checkbox" name="auto_security_updates" checked /> Enable automatic security updates</label>
|
||||||
|
<label><input type="checkbox" name="setup_timezone" /> Configure timezone</label>
|
||||||
|
<label><input type="checkbox" name="setup_hostname" /> Set hostname</label>
|
||||||
|
<label><input type="checkbox" name="setup_ntp" checked /> Configure NTP time sync</label>
|
||||||
|
<label><input type="checkbox" name="setup_swap" /> Configure swap file</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Security & Hardening</legend>
|
||||||
|
<label><input type="checkbox" name="ssh_harden" checked /> SSH hardening (disable root, key-only option)</label>
|
||||||
|
<label><input type="checkbox" name="install_fail2ban" checked /> Install Fail2ban (intrusion prevention)</label>
|
||||||
|
<label><input type="checkbox" name="prelogin_banner" /> Pre-login banner (SSH)</label>
|
||||||
|
<label><input type="checkbox" name="postlogin_banner" /> Post-login banner (MOTD)</label>
|
||||||
|
<label><input type="checkbox" name="ssh_2fa" /> SSH 2FA (Google Authenticator PAM)</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Docker & Services</legend>
|
||||||
|
<label><input type="checkbox" name="install_docker" checked /> Install Docker + Docker Compose</label>
|
||||||
|
<label><input type="checkbox" name="docker_admin_user" checked /> Create admin user for docker operations (non-login)</label>
|
||||||
|
<label><input type="checkbox" name="open_ports" checked /> Allow ports (UFW)</label>
|
||||||
|
<label><input type="checkbox" name="combine_lan" /> Combine LAN ports (netplan bond + bridge, static IP)</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>User Management</legend>
|
||||||
|
<label><input type="checkbox" name="create_admin_user" /> Create admin user with sudo access</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Monitoring & Utilities</legend>
|
||||||
|
<label><input type="checkbox" name="install_monitoring_tools" checked /> Install monitoring tools (htop, iotop, netstat, etc.)</label>
|
||||||
|
<label><input type="checkbox" name="install_build_tools" /> Install build tools (build-essential, git, etc.)</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>General Configuration</legend>
|
||||||
|
<div class="row">
|
||||||
|
<label>
|
||||||
|
Docker admin username
|
||||||
|
<input type="text" name="admin_username" value="datamng" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
SSH port
|
||||||
|
<input type="number" name="ssh_port" value="22" min="1" max="65535" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label>
|
||||||
|
Hostname
|
||||||
|
<input type="text" name="hostname" value="" placeholder="Leave empty to skip" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Timezone
|
||||||
|
<input type="text" name="timezone" value="UTC" placeholder="e.g. America/New_York" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label>
|
||||||
|
Admin username (for new user)
|
||||||
|
<input type="text" name="new_admin_username" value="admin" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Swap size (GB)
|
||||||
|
<input type="number" name="swap_size_gb" value="2" min="1" max="32" />
|
||||||
|
<span class="note">Swap is virtual memory - used when RAM is full. Prevents out-of-memory crashes.</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label>
|
||||||
|
Docker data directory path
|
||||||
|
<input type="text" name="docker_data_dir" value="/opt/docker" placeholder="/opt/docker" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label>
|
||||||
|
Firewall ports (CSV)
|
||||||
|
<input type="text" name="ports_csv" value="22,80,81,443" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
SSH public key (for admin user, one per line)
|
||||||
|
<textarea name="ssh_public_keys" placeholder="ssh-rsa AAAAB3NzaC1yc2E... user@host ssh-ed25519 AAAAC3NzaC1lZDI1... user@host" rows="4"></textarea>
|
||||||
|
<div class="note" style="margin-top: 8px;">
|
||||||
|
<strong>How to generate SSH keys:</strong><br>
|
||||||
|
1. On your local computer, run: <code>ssh-keygen -t ed25519 -C "your_email@example.com"</code><br>
|
||||||
|
2. Press Enter to accept default location (~/.ssh/id_ed25519)<br>
|
||||||
|
3. Copy your PUBLIC key: <code>cat ~/.ssh/id_ed25519.pub</code> (or <code>id_rsa.pub</code> for RSA)<br>
|
||||||
|
4. Paste the PUBLIC key here (starts with ssh-rsa or ssh-ed25519)<br>
|
||||||
|
<strong>Note:</strong> You generate keys on YOUR computer, then paste the PUBLIC key here. The server will use it to authenticate you.
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>LAN bonding/bridging (only if enabled)</legend>
|
||||||
|
<label>LAN interfaces (CSV, e.g. eth0,eth1)
|
||||||
|
<input type="text" name="lan_interfaces_csv" value="eth0,eth1" />
|
||||||
|
</label>
|
||||||
|
<div class="row">
|
||||||
|
<label>Static IP (CIDR)
|
||||||
|
<input type="text" name="static_ip_cidr" value="192.168.1.9/24" />
|
||||||
|
</label>
|
||||||
|
<label>Gateway
|
||||||
|
<input type="text" name="gateway_ip" value="192.168.1.1" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label>DNS (CSV)
|
||||||
|
<input type="text" name="dns_csv" value="1.1.1.1,8.8.8.8" />
|
||||||
|
</label>
|
||||||
|
<p class="note">This will write a netplan file and apply it. Test carefully (risk of remote lockout).</p>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>System Owner Information</legend>
|
||||||
|
<div class="row">
|
||||||
|
<label>
|
||||||
|
Owner Name
|
||||||
|
<input type="text" name="owner_name" value="Scardus" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Owner Website
|
||||||
|
<input type="text" name="owner_website" value="https://scardustech.com" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label>
|
||||||
|
Owner Email
|
||||||
|
<input type="text" name="owner_email" value="info@scardustech.com" />
|
||||||
|
</label>
|
||||||
|
<p class="note">Banners will use templates from loginbanner.md and postloginbanner.md</p>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<button class="btn" type="submit">Download .sh</button>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
613
templates/script.sh.j2
Normal file
613
templates/script.sh.j2
Normal file
@ -0,0 +1,613 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Generated at: {{ meta.generated_at }}
|
||||||
|
|
||||||
|
if [[ "${EUID}" -ne 0 ]]; then
|
||||||
|
echo "Run as root: sudo bash $0"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log() { echo "[setup] $*"; }
|
||||||
|
|
||||||
|
ensure_pkg() {
|
||||||
|
local pkg="$1"
|
||||||
|
if ! dpkg -s "$pkg" >/dev/null 2>&1; then
|
||||||
|
apt-get update -y
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y "$pkg"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_file() {
|
||||||
|
local f="$1"
|
||||||
|
if [[ -f "$f" ]]; then
|
||||||
|
cp -a "$f" "${f}.bak.$(date +%Y%m%d%H%M%S)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
{% if flags.install_docker %}
|
||||||
|
install_docker() {
|
||||||
|
log "Installing Docker + compose plugin"
|
||||||
|
ensure_pkg ca-certificates
|
||||||
|
ensure_pkg curl
|
||||||
|
ensure_pkg gnupg
|
||||||
|
ensure_pkg lsb-release
|
||||||
|
ensure_pkg acl
|
||||||
|
|
||||||
|
install -m 0755 -d /etc/apt/keyrings
|
||||||
|
if [[ ! -f /etc/apt/keyrings/docker.gpg ]]; then
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||||
|
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||||
|
fi
|
||||||
|
|
||||||
|
local codename
|
||||||
|
codename="$(. /etc/os-release && echo "$VERSION_CODENAME")"
|
||||||
|
|
||||||
|
cat >/etc/apt/sources.list.d/docker.list <<EOF
|
||||||
|
deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${codename} stable
|
||||||
|
EOF
|
||||||
|
|
||||||
|
apt-get update -y
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
|
|
||||||
|
# Configure Docker to use custom data directory if specified
|
||||||
|
{% if flags.docker_admin_user %}
|
||||||
|
local docker_dir="{{ params.docker_data_dir }}"
|
||||||
|
if [[ -d "${docker_dir}" ]]; then
|
||||||
|
log "Configuring Docker to use custom data directory: ${docker_dir}"
|
||||||
|
mkdir -p /etc/docker
|
||||||
|
backup_file /etc/docker/daemon.json
|
||||||
|
|
||||||
|
# Create or update daemon.json
|
||||||
|
if [[ -f /etc/docker/daemon.json ]]; then
|
||||||
|
# Use jq if available, otherwise use sed/python fallback
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
jq ". + {\"data-root\": \"${docker_dir}\"}" /etc/docker/daemon.json > /tmp/daemon.json.tmp && mv /tmp/daemon.json.tmp /etc/docker/daemon.json
|
||||||
|
else
|
||||||
|
# Fallback: create new daemon.json with data-root
|
||||||
|
cat >/etc/docker/daemon.json <<EOF
|
||||||
|
{
|
||||||
|
"data-root": "${docker_dir}"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
cat >/etc/docker/daemon.json <<EOF
|
||||||
|
{
|
||||||
|
"data-root": "${docker_dir}"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
chmod 644 /etc/docker/daemon.json
|
||||||
|
log "Docker daemon.json configured. Restart Docker to apply: systemctl restart docker"
|
||||||
|
fi
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
systemctl enable --now docker
|
||||||
|
log "Docker installed"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.docker_admin_user %}
|
||||||
|
create_docker_admin_user() {
|
||||||
|
local user="{{ params.admin_username }}"
|
||||||
|
log "Creating docker operator user: ${user} (non-login)"
|
||||||
|
if ! id "${user}" >/dev/null 2>&1; then
|
||||||
|
# system user, no home by default; create home only if you want it
|
||||||
|
useradd --system --create-home --home-dir "/home/${user}" --shell /usr/sbin/nologin "${user}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# docker group exists after docker install; create if missing
|
||||||
|
if ! getent group docker >/dev/null 2>&1; then
|
||||||
|
groupadd docker
|
||||||
|
fi
|
||||||
|
|
||||||
|
usermod -aG docker "${user}"
|
||||||
|
|
||||||
|
# Create docker data directory with restricted permissions
|
||||||
|
local docker_dir="{{ params.docker_data_dir }}"
|
||||||
|
log "Creating docker data directory: ${docker_dir}"
|
||||||
|
|
||||||
|
# Create directory if it doesn't exist
|
||||||
|
if [[ ! -d "${docker_dir}" ]]; then
|
||||||
|
mkdir -p "${docker_dir}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set ownership to docker user and docker group
|
||||||
|
chown -R "${user}:docker" "${docker_dir}"
|
||||||
|
|
||||||
|
# Set permissions: owner (rwx), group (rwx), others (no access)
|
||||||
|
chmod 770 "${docker_dir}"
|
||||||
|
|
||||||
|
# Set ACL to ensure only docker user and docker group have access
|
||||||
|
ensure_pkg acl
|
||||||
|
setfacl -R -m u:"${user}":rwx "${docker_dir}"
|
||||||
|
setfacl -R -m g:docker:rwx "${docker_dir}"
|
||||||
|
setfacl -R -m o::--- "${docker_dir}"
|
||||||
|
setfacl -R -d -m u:"${user}":rwx "${docker_dir}"
|
||||||
|
setfacl -R -d -m g:docker:rwx "${docker_dir}"
|
||||||
|
setfacl -R -d -m o::--- "${docker_dir}"
|
||||||
|
|
||||||
|
log "Docker directory ${docker_dir} created with restricted permissions for ${user}"
|
||||||
|
|
||||||
|
# Optional: allow admins to run docker as that user using sudo
|
||||||
|
# This does NOT allow interactive login to the user.
|
||||||
|
cat >/etc/sudoers.d/90-docker-operator <<EOF
|
||||||
|
# Allow members of sudo group to run docker commands as {{ params.admin_username }}
|
||||||
|
%sudo ALL=( {{ params.admin_username }} ) NOPASSWD: /usr/bin/docker, /usr/bin/docker-compose, /usr/bin/docker-*
|
||||||
|
EOF
|
||||||
|
chmod 0440 /etc/sudoers.d/90-docker-operator
|
||||||
|
|
||||||
|
log "Docker operator user configured"
|
||||||
|
log "NOTE: To use custom docker directory, configure Docker daemon.json:"
|
||||||
|
log " { \"data-root\": \"${docker_dir}\" }"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.open_ports %}
|
||||||
|
setup_firewall() {
|
||||||
|
log "Configuring UFW firewall"
|
||||||
|
ensure_pkg ufw
|
||||||
|
|
||||||
|
ufw --force reset
|
||||||
|
ufw default deny incoming
|
||||||
|
ufw default allow outgoing
|
||||||
|
|
||||||
|
{% for p in params.ports %}
|
||||||
|
ufw allow {{ p }}/tcp
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
ufw --force enable
|
||||||
|
ufw status verbose || true
|
||||||
|
log "UFW configured"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.combine_lan %}
|
||||||
|
setup_netplan_bond_bridge() {
|
||||||
|
log "Writing netplan for bond+bridge (static IP)"
|
||||||
|
|
||||||
|
# WARNING: This can lock you out if done over SSH on a remote host.
|
||||||
|
# Make sure you have console/iLO/IPMI access or test carefully.
|
||||||
|
|
||||||
|
local ifaces=( {% for i in params.lan_ifaces %}"{{ i }}"{% if not loop.last %} {% endif %}{% endfor %} )
|
||||||
|
|
||||||
|
# Basic validation
|
||||||
|
for i in "${ifaces[@]}"; do
|
||||||
|
if ! ip link show "$i" >/dev/null 2>&1; then
|
||||||
|
echo "Interface not found: $i"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
backup_file /etc/netplan/01-lan.yaml
|
||||||
|
|
||||||
|
cat >/etc/netplan/01-lan.yaml <<'EOF'
|
||||||
|
network:
|
||||||
|
version: 2
|
||||||
|
renderer: networkd
|
||||||
|
|
||||||
|
ethernets:
|
||||||
|
{% for i in params.lan_ifaces %}
|
||||||
|
{{ i }}:
|
||||||
|
dhcp4: false
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
bonds:
|
||||||
|
bond0:
|
||||||
|
interfaces:
|
||||||
|
{% for i in params.lan_ifaces %}
|
||||||
|
- {{ i }}
|
||||||
|
{% endfor %}
|
||||||
|
parameters:
|
||||||
|
mode: 802.3ad
|
||||||
|
mii-monitor-interval: 100
|
||||||
|
|
||||||
|
bridges:
|
||||||
|
br0:
|
||||||
|
interfaces:
|
||||||
|
- bond0
|
||||||
|
addresses:
|
||||||
|
- {{ params.static_ip_cidr }}
|
||||||
|
routes:
|
||||||
|
- to: default
|
||||||
|
via: {{ params.gateway_ip }}
|
||||||
|
nameservers:
|
||||||
|
addresses:
|
||||||
|
{% for d in params.dns %}
|
||||||
|
- {{ d }}
|
||||||
|
{% endfor %}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
netplan generate
|
||||||
|
netplan apply
|
||||||
|
log "Netplan applied"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.prelogin_banner %}
|
||||||
|
setup_prelogin_banner() {
|
||||||
|
log "Configuring SSH pre-login banner"
|
||||||
|
ensure_pkg openssh-server
|
||||||
|
|
||||||
|
# Use /etc/issue.net for SSH Banner
|
||||||
|
cat >/etc/issue.net <<'EOF'
|
||||||
|
{{ params.prelogin_text }}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Replace owner placeholders with actual values
|
||||||
|
sed -i "s|\[OWNER_NAME\]|{{ params.owner_name }}|g" /etc/issue.net
|
||||||
|
sed -i "s|\[OWNER_WEBSITE\]|{{ params.owner_website }}|g" /etc/issue.net
|
||||||
|
sed -i "s|\[OWNER_EMAIL\]|{{ params.owner_email }}|g" /etc/issue.net
|
||||||
|
|
||||||
|
backup_file /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
# Ensure Banner points to /etc/issue.net
|
||||||
|
if grep -qE '^\s*Banner\s+' /etc/ssh/sshd_config; then
|
||||||
|
sed -i 's|^\s*Banner\s\+.*|Banner /etc/issue.net|' /etc/ssh/sshd_config
|
||||||
|
else
|
||||||
|
echo "Banner /etc/issue.net" >> /etc/ssh/sshd_config
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl restart ssh
|
||||||
|
log "Pre-login banner configured"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.postlogin_banner %}
|
||||||
|
setup_postlogin_banner() {
|
||||||
|
log "Configuring post-login MOTD"
|
||||||
|
# Simple approach: overwrite /etc/motd
|
||||||
|
cat >/etc/motd <<'EOF'
|
||||||
|
{{ params.postlogin_text }}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Replace owner placeholders with actual values
|
||||||
|
sed -i "s|\[OWNER_NAME\]|{{ params.owner_name }}|g" /etc/motd
|
||||||
|
sed -i "s|\[OWNER_WEBSITE\]|{{ params.owner_website }}|g" /etc/motd
|
||||||
|
sed -i "s|\[OWNER_EMAIL\]|{{ params.owner_email }}|g" /etc/motd
|
||||||
|
|
||||||
|
log "Post-login banner configured"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.ssh_2fa %}
|
||||||
|
install_google_authenticator() {
|
||||||
|
log "Installing Google Authenticator (PAM module)"
|
||||||
|
|
||||||
|
# Only install the package, do not configure it
|
||||||
|
ensure_pkg libpam-google-authenticator
|
||||||
|
|
||||||
|
log "Google Authenticator installed. Manual configuration required."
|
||||||
|
log "To configure 2FA, users must run: google-authenticator"
|
||||||
|
log "Then configure PAM and SSH settings manually."
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.system_update %}
|
||||||
|
system_update() {
|
||||||
|
log "Updating system packages"
|
||||||
|
apt-get update -y
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade -y
|
||||||
|
apt-get autoremove -y
|
||||||
|
apt-get autoclean -y
|
||||||
|
log "System updated"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.auto_security_updates %}
|
||||||
|
setup_auto_security_updates() {
|
||||||
|
log "Configuring automatic security updates"
|
||||||
|
ensure_pkg unattended-upgrades
|
||||||
|
|
||||||
|
# Enable automatic security updates
|
||||||
|
cat >/etc/apt/apt.conf.d/50unattended-upgrades <<'EOF'
|
||||||
|
Unattended-Upgrade::Allowed-Origins {
|
||||||
|
"${distro_id}:${distro_codename}-security";
|
||||||
|
"${distro_id}ESMApps:${distro_codename}-apps-security";
|
||||||
|
"${distro_id}ESM:${distro_codename}-infra-security";
|
||||||
|
};
|
||||||
|
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
||||||
|
Unattended-Upgrade::MinimalSteps "true";
|
||||||
|
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
||||||
|
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||||||
|
Unattended-Upgrade::Automatic-Reboot "false";
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Enable automatic updates
|
||||||
|
echo 'APT::Periodic::Update-Package-Lists "1";' > /etc/apt/apt.conf.d/20auto-upgrades
|
||||||
|
echo 'APT::Periodic::Unattended-Upgrade "1";' >> /etc/apt/apt.conf.d/20auto-upgrades
|
||||||
|
|
||||||
|
systemctl enable unattended-upgrades
|
||||||
|
systemctl restart unattended-upgrades
|
||||||
|
log "Automatic security updates configured"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.setup_timezone %}
|
||||||
|
setup_timezone() {
|
||||||
|
log "Setting timezone to {{ params.timezone }}"
|
||||||
|
ensure_pkg tzdata
|
||||||
|
timedatectl set-timezone {{ params.timezone }}
|
||||||
|
log "Timezone configured"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.setup_hostname %}
|
||||||
|
setup_hostname() {
|
||||||
|
{% if params.hostname %}
|
||||||
|
log "Setting hostname to {{ params.hostname }}"
|
||||||
|
hostnamectl set-hostname {{ params.hostname }}
|
||||||
|
echo "127.0.0.1 {{ params.hostname }}" >> /etc/hosts
|
||||||
|
log "Hostname configured"
|
||||||
|
{% else %}
|
||||||
|
log "Skipping hostname setup (not specified)"
|
||||||
|
{% endif %}
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.setup_ntp %}
|
||||||
|
setup_ntp() {
|
||||||
|
log "Configuring NTP time synchronization"
|
||||||
|
ensure_pkg chrony
|
||||||
|
|
||||||
|
# Configure chrony with reliable time servers
|
||||||
|
backup_file /etc/chrony/chrony.conf
|
||||||
|
cat >/etc/chrony/chrony.conf <<'EOF'
|
||||||
|
pool 0.pool.ntp.org iburst
|
||||||
|
pool 1.pool.ntp.org iburst
|
||||||
|
pool 2.pool.ntp.org iburst
|
||||||
|
pool 3.pool.ntp.org iburst
|
||||||
|
|
||||||
|
# Record the rate at which the system clock gains/losses time
|
||||||
|
driftfile /var/lib/chrony/drift
|
||||||
|
|
||||||
|
# Allow the system clock to be stepped in the first three updates
|
||||||
|
makestep 1.0 3
|
||||||
|
|
||||||
|
# Enable kernel synchronization of the real-time clock
|
||||||
|
rtcsync
|
||||||
|
|
||||||
|
# Enable hardware timestamping on all interfaces
|
||||||
|
#hwtimestamp *
|
||||||
|
|
||||||
|
# Increase the minimum number of selectable sources
|
||||||
|
#minsources 2
|
||||||
|
|
||||||
|
# Allow NTP client access from local network
|
||||||
|
#allow 192.168.0.0/16
|
||||||
|
|
||||||
|
# Serve time even if not synchronized to a time source
|
||||||
|
#local stratum 10
|
||||||
|
|
||||||
|
# Specify file containing keys for NTP authentication
|
||||||
|
keyfile /etc/chrony/chrony.keys
|
||||||
|
|
||||||
|
# Save the drift between the system clock and the hardware clock
|
||||||
|
#initstepslew 10 client1.example.com client2.example.com
|
||||||
|
|
||||||
|
# Get TAI-UTC offset and leap seconds from the system tz database
|
||||||
|
leapsectz right/UTC
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl enable chronyd
|
||||||
|
systemctl restart chronyd
|
||||||
|
chronyc sources
|
||||||
|
log "NTP configured"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.setup_swap %}
|
||||||
|
setup_swap() {
|
||||||
|
log "Configuring swap file ({{ params.swap_size_gb }}GB)"
|
||||||
|
|
||||||
|
# Swap Configuration Explanation:
|
||||||
|
# Swap is virtual memory stored on disk. When RAM is full, Linux moves
|
||||||
|
# less-used data to swap to free up RAM. This prevents "out of memory"
|
||||||
|
# crashes but is slower than RAM. Swappiness (0-100) controls how
|
||||||
|
# aggressively Linux uses swap. Lower values (10) prefer RAM, higher (60+)
|
||||||
|
# use swap more aggressively. For servers, 10 is recommended.
|
||||||
|
|
||||||
|
# Check if swap already exists
|
||||||
|
if swapon --show | grep -q "/swapfile"; then
|
||||||
|
log "Swap file already exists, skipping"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create swap file
|
||||||
|
fallocate -l {{ params.swap_size_gb }}G /swapfile || dd if=/dev/zero of=/swapfile bs=1G count={{ params.swap_size_gb }}
|
||||||
|
chmod 600 /swapfile
|
||||||
|
mkswap /swapfile
|
||||||
|
swapon /swapfile
|
||||||
|
|
||||||
|
# Make swap permanent
|
||||||
|
if ! grep -q "/swapfile" /etc/fstab; then
|
||||||
|
echo "/swapfile none swap sw 0 0" >> /etc/fstab
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configure swappiness (10 = less aggressive, 60 = default)
|
||||||
|
# Lower value = prefer RAM, higher = use swap more
|
||||||
|
if ! grep -q "vm.swappiness" /etc/sysctl.conf; then
|
||||||
|
echo "vm.swappiness=10" >> /etc/sysctl.conf
|
||||||
|
sysctl vm.swappiness=10
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Swap file configured ({{ params.swap_size_gb }}GB, swappiness=10)"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.ssh_harden %}
|
||||||
|
harden_ssh() {
|
||||||
|
log "Hardening SSH configuration"
|
||||||
|
ensure_pkg openssh-server
|
||||||
|
|
||||||
|
backup_file /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
# Disable root login
|
||||||
|
sed -i 's|^#*PermitRootLogin.*|PermitRootLogin no|' /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
# Change SSH port if specified
|
||||||
|
{% if params.ssh_port != 22 %}
|
||||||
|
sed -i "s|^#*Port.*|Port {{ params.ssh_port }}|" /etc/ssh/sshd_config
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
sed -i 's|^#*PasswordAuthentication.*|PasswordAuthentication yes|' /etc/ssh/sshd_config
|
||||||
|
sed -i 's|^#*PubkeyAuthentication.*|PubkeyAuthentication yes|' /etc/ssh/sshd_config
|
||||||
|
sed -i 's|^#*PermitEmptyPasswords.*|PermitEmptyPasswords no|' /etc/ssh/sshd_config
|
||||||
|
sed -i 's|^#*MaxAuthTries.*|MaxAuthTries 3|' /etc/ssh/sshd_config
|
||||||
|
sed -i 's|^#*ClientAliveInterval.*|ClientAliveInterval 300|' /etc/ssh/sshd_config
|
||||||
|
sed -i 's|^#*ClientAliveCountMax.*|ClientAliveCountMax 2|' /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
# Disable X11 forwarding for security
|
||||||
|
sed -i 's|^#*X11Forwarding.*|X11Forwarding no|' /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
# Use only strong ciphers
|
||||||
|
if ! grep -q "^Ciphers" /etc/ssh/sshd_config; then
|
||||||
|
echo "Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" >> /etc/ssh/sshd_config
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl restart ssh
|
||||||
|
log "SSH hardened"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.install_fail2ban %}
|
||||||
|
install_fail2ban() {
|
||||||
|
log "Installing and configuring Fail2ban"
|
||||||
|
ensure_pkg fail2ban
|
||||||
|
|
||||||
|
# Create local jail configuration
|
||||||
|
cat >/etc/fail2ban/jail.local <<EOF
|
||||||
|
[DEFAULT]
|
||||||
|
bantime = 3600
|
||||||
|
findtime = 600
|
||||||
|
maxretry = 5
|
||||||
|
destemail = root@localhost
|
||||||
|
sendername = Fail2Ban
|
||||||
|
action = %(action_)s
|
||||||
|
|
||||||
|
[sshd]
|
||||||
|
enabled = true
|
||||||
|
port = {{ params.ssh_port }}
|
||||||
|
logpath = %(sshd_log)s
|
||||||
|
backend = %(sshd_backend)s
|
||||||
|
maxretry = 3
|
||||||
|
bantime = 7200
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl enable fail2ban
|
||||||
|
systemctl restart fail2ban
|
||||||
|
fail2ban-client status
|
||||||
|
log "Fail2ban configured"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.create_admin_user %}
|
||||||
|
create_admin_user() {
|
||||||
|
log "Creating admin user: {{ params.new_admin_username }}"
|
||||||
|
|
||||||
|
if id "{{ params.new_admin_username }}" >/dev/null 2>&1; then
|
||||||
|
log "User {{ params.new_admin_username }} already exists"
|
||||||
|
else
|
||||||
|
# Create user with sudo access
|
||||||
|
useradd -m -s /bin/bash {{ params.new_admin_username }}
|
||||||
|
usermod -aG sudo {{ params.new_admin_username }}
|
||||||
|
|
||||||
|
# Create .ssh directory
|
||||||
|
mkdir -p /home/{{ params.new_admin_username }}/.ssh
|
||||||
|
chmod 700 /home/{{ params.new_admin_username }}/.ssh
|
||||||
|
|
||||||
|
# Add SSH public keys if provided
|
||||||
|
{% if params.ssh_public_keys %}
|
||||||
|
cat >/home/{{ params.new_admin_username }}/.ssh/authorized_keys <<'EOF'
|
||||||
|
{% for key in params.ssh_public_keys %}
|
||||||
|
{{ key }}
|
||||||
|
{% endfor %}
|
||||||
|
EOF
|
||||||
|
chmod 600 /home/{{ params.new_admin_username }}/.ssh/authorized_keys
|
||||||
|
chown -R {{ params.new_admin_username }}:{{ params.new_admin_username }} /home/{{ params.new_admin_username }}/.ssh
|
||||||
|
log "SSH keys added for {{ params.new_admin_username }}"
|
||||||
|
{% else %}
|
||||||
|
log "No SSH keys provided - user will need to set password or add keys manually"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
log "Admin user {{ params.new_admin_username }} created with sudo access"
|
||||||
|
log "IMPORTANT: Set a password with: passwd {{ params.new_admin_username }}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.install_monitoring_tools %}
|
||||||
|
install_monitoring_tools() {
|
||||||
|
log "Installing monitoring and system utilities"
|
||||||
|
|
||||||
|
# Essential monitoring tools
|
||||||
|
ensure_pkg htop
|
||||||
|
ensure_pkg iotop
|
||||||
|
ensure_pkg net-tools
|
||||||
|
ensure_pkg curl
|
||||||
|
ensure_pkg wget
|
||||||
|
ensure_pkg vim
|
||||||
|
ensure_pkg nano
|
||||||
|
ensure_pkg tree
|
||||||
|
ensure_pkg unzip
|
||||||
|
ensure_pkg zip
|
||||||
|
ensure_pkg rsync
|
||||||
|
ensure_pkg tmux
|
||||||
|
ensure_pkg screen
|
||||||
|
|
||||||
|
log "Monitoring tools installed"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flags.install_build_tools %}
|
||||||
|
install_build_tools() {
|
||||||
|
log "Installing build tools and development utilities"
|
||||||
|
|
||||||
|
ensure_pkg build-essential
|
||||||
|
ensure_pkg git
|
||||||
|
ensure_pkg make
|
||||||
|
ensure_pkg cmake
|
||||||
|
ensure_pkg pkg-config
|
||||||
|
|
||||||
|
log "Build tools installed"
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "Starting setup"
|
||||||
|
|
||||||
|
# System Setup (run first)
|
||||||
|
{% if flags.system_update %} system_update {% endif %}
|
||||||
|
{% if flags.setup_timezone %} setup_timezone {% endif %}
|
||||||
|
{% if flags.setup_hostname %} setup_hostname {% endif %}
|
||||||
|
{% if flags.setup_ntp %} setup_ntp {% endif %}
|
||||||
|
{% if flags.setup_swap %} setup_swap {% endif %}
|
||||||
|
{% if flags.auto_security_updates %} setup_auto_security_updates {% endif %}
|
||||||
|
|
||||||
|
# Security & Hardening
|
||||||
|
{% if flags.ssh_harden %} harden_ssh {% endif %}
|
||||||
|
{% if flags.install_fail2ban %} install_fail2ban {% endif %}
|
||||||
|
{% if flags.prelogin_banner %} setup_prelogin_banner {% endif %}
|
||||||
|
{% if flags.postlogin_banner %} setup_postlogin_banner {% endif %}
|
||||||
|
{% if flags.ssh_2fa %} install_google_authenticator {% endif %}
|
||||||
|
|
||||||
|
# User Management
|
||||||
|
{% if flags.create_admin_user %} create_admin_user {% endif %}
|
||||||
|
|
||||||
|
# Docker & Services
|
||||||
|
{% if flags.install_docker %} install_docker {% endif %}
|
||||||
|
{% if flags.docker_admin_user %} create_docker_admin_user {% endif %}
|
||||||
|
{% if flags.open_ports %} setup_firewall {% endif %}
|
||||||
|
{% if flags.combine_lan %} setup_netplan_bond_bridge {% endif %}
|
||||||
|
|
||||||
|
# Monitoring & Utilities
|
||||||
|
{% if flags.install_monitoring_tools %} install_monitoring_tools {% endif %}
|
||||||
|
{% if flags.install_build_tools %} install_build_tools {% endif %}
|
||||||
|
|
||||||
|
log "Done"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Loading…
x
Reference in New Issue
Block a user