diff --git a/alpine/config b/alpine/config index 8917097..eea1c5a 100644 --- a/alpine/config +++ b/alpine/config @@ -14,3 +14,8 @@ Host manchester User 4server IdentityFile /app/host_vars/manchester/manchester +Host boston + Hostname 192.168.9.16 + User 4server + IdentityFile /app/host_vars/boston/boston + diff --git a/app/hardening b/app/hardening new file mode 100755 index 0000000..fcc3180 --- /dev/null +++ b/app/hardening @@ -0,0 +1,7 @@ +#!/bin/bash +# DISABLE ROOT and PASSWORD LOGIN +rex "doas sh -c 'grep -q \"permit nopass 4server as root\" /etc/doas.d/4server.conf 2>/dev/null || echo \"permit nopass 4server as root\" | tee -a /etc/doas.d/4server.conf > /dev/null'" +rex "doas sh -c 'sed -i \"s/^#\?PasswordAuthentication.*/PasswordAuthentication no/\" /etc/ssh/sshd_config'" + +rex "doas sh -c 'sed -i \"s/^#\?PasswordAuthentication.*/PasswordAuthentication no/\" /etc/ssh/sshd_config.d/50-cloud-init.conf'" +rex doas rc-service sshd restart diff --git a/app/host_vars/dev/dev.env b/app/host_vars/dev/dev.env index b5bac41..1c58284 100644 --- a/app/host_vars/dev/dev.env +++ b/app/host_vars/dev/dev.env @@ -1,2 +1,2 @@ -API_KEY=4h6lDzAOVksuCqmhEB3 +API_KEY=your-secret-api-key HOSTNAME="dev" diff --git a/app/host_vars/hosts b/app/host_vars/hosts new file mode 100644 index 0000000..38f8e88 --- /dev/null +++ b/app/host_vars/hosts @@ -0,0 +1 @@ +dev diff --git a/app/onboarding b/app/onboarding index abab032..1be4fd2 100755 --- a/app/onboarding +++ b/app/onboarding @@ -3,7 +3,7 @@ template templates/hostname /etc/hostname rex doas apk update -rex doas apk add bash doas openssh linux-lts +rex doas apk add bash doas rsync openssh linux-lts ### activate lts kerner template templates/extlinux.conf /boot/extlinux.conf @@ -12,7 +12,6 @@ rex doas chmod 644 /boot/extlinux.conf # ass swap file ???? -# ------ disable root user and login rex doas mkdir -p /4server rex doas chmod 777 /4server @@ -63,16 +62,13 @@ rex doas chown 4server:4server /home/4server/.ssh/authorized_keys rex doas usermod -p Ne82Vrx8QfUdNHvLgct 4server rex doas passwd -u 4server - +template templates/.profile /home/4server/.profile +template templates/etc/doas.d/4server.conf /etc/doas.d/4server.conf rex doas mkdir -p /etc/doas.d -rex "doas sh -c 'grep -q \"permit nopass 4server as root\" /etc/doas.d/4server.conf 2>/dev/null || echo \"permit nopass 4server as root\" | tee -a /etc/doas.d/4server.conf > /dev/null'" -rex "doas sh -c 'sed -i \"s/^#\?PasswordAuthentication.*/PasswordAuthentication no/\" /etc/ssh/sshd_config'" - -rex "doas sh -c 'sed -i \"s/^#\?PasswordAuthentication.*/PasswordAuthentication no/\" /etc/ssh/sshd_config.d/50-cloud-init.conf'" rex doas rc-service sshd restart rex doas rc-service nebula restart rex doas reboot diff --git a/app/sbin/api b/app/sbin/api index 95d9313..f03a775 100755 --- a/app/sbin/api +++ b/app/sbin/api @@ -8,16 +8,19 @@ import sqlite3 import subprocess import os import uvicorn -from typing import Optional +from typing import Dict, Any, Optional from datetime import datetime +import json -# Constants + + +# ---------------------- Constants ---------------------- DB_PATH = "/4server/data/contracts.db" BIN_PATH = "/4server/sbin" API_KEY = os.getenv("API_KEY", "your-secret-api-key") VERSION = "API: 0.0.7" -# FastAPI app +# ---------------------- FastAPI App ---------------------- app = FastAPI() api_key_header = APIKeyHeader(name="X-API-Key") @@ -44,7 +47,7 @@ def init_db(): cursor.execute(''' CREATE TABLE IF NOT EXISTS containers ( ID INTEGER PRIMARY KEY AUTOINCREMENT, - UUID CHAR(50), + UUID CHAR(50) UNIQUE, email CHAR(100), expires DATE, tags TEXT, @@ -65,27 +68,24 @@ def init_db(): def execute_db(query: str, params: tuple = (), fetch: bool = False): conn = sqlite3.connect(DB_PATH) - conn.row_factory = sqlite3.Row # <-- Add this line + conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute(query, params) conn.commit() data = cursor.fetchall() if fetch else None conn.close() if data and fetch: - return [dict(row) for row in data] # Convert each row to dict - return data - - - + return [dict(row) for row in data] + return None # ---------------------- Models ---------------------- class ContainerModel(BaseModel): - UUID: Optional[str] = None + UUID: str email: Optional[str] = None expires: Optional[str] = None tags: Optional[str] = None - env: Optional[str] = None + env: Optional[Dict[str, Any]] = None affiliate: Optional[str] = None image: Optional[str] = None history: Optional[str] = None @@ -96,16 +96,8 @@ class ContainerModel(BaseModel): bump: Optional[str] = None -class ContainerIDRequest(BaseModel): - container_id: Optional[str] = None - - -class UpdateContainerRequest(ContainerModel): - pass - - -class InfoContainerRequest(BaseModel): - container_id: Optional[str] = None +class UUIDRequest(BaseModel): + UUID: str # ---------------------- Routes ---------------------- @@ -115,7 +107,9 @@ def redirect_to_odoo(): @app.post("/container/update", dependencies=[Depends(verify_api_key)]) -def update_container(request: UpdateContainerRequest): +def update_container(request: ContainerModel): + env_str = json.dumps(request.env) if isinstance(request.env, dict) else request.env + existing = execute_db("SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True) if existing: execute_db(""" @@ -123,7 +117,7 @@ def update_container(request: UpdateContainerRequest): history=?, comment=?, domains=?, status=?, created=?, bump=? WHERE UUID=? """, ( - request.email, request.expires, request.tags, request.env, request.affiliate, + request.email, request.expires, request.tags, env_str, request.affiliate, request.image, request.history, request.comment, request.domains, request.status, request.created, request.bump, request.UUID )) @@ -133,7 +127,7 @@ def update_container(request: UpdateContainerRequest): comment, domains, status, created, bump) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( - request.UUID, request.email, request.expires, request.tags, request.env, + request.UUID, request.email, request.expires, request.tags, env_str, request.affiliate, request.image, request.history, request.comment, request.domains, request.status, request.created, request.bump )) @@ -141,44 +135,44 @@ def update_container(request: UpdateContainerRequest): @app.post("/container/start", dependencies=[Depends(verify_api_key)]) -def start_container(request: ContainerIDRequest): - return {"message": run_command([f"{BIN_PATH}/startContainer", request.container_id])} +def start_container(request: UUIDRequest): + return {"message": run_command([f"{BIN_PATH}/startContainer", request.UUID])} @app.post("/container/stop", dependencies=[Depends(verify_api_key)]) -def stop_container(request: ContainerIDRequest): - return {"message": run_command([f"{BIN_PATH}/stopContainer", request.container_id])} +def stop_container(request: UUIDRequest): + return {"message": run_command([f"{BIN_PATH}/stopContainer", request.UUID])} @app.post("/container/nuke", dependencies=[Depends(verify_api_key)]) -def nuke_container(request: ContainerIDRequest): - status = execute_db("SELECT status FROM containers WHERE UUID=?", (request.container_id,), fetch=True) - if not status or status[0][0] != "nuke": +def nuke_container(request: UUIDRequest): + status = execute_db("SELECT status FROM containers WHERE UUID=?", (request.UUID,), fetch=True) + if not status or status[0]["status"] != "nuke": raise HTTPException(400, "Container status is not 'nuke'") - return {"message": run_command([f"{BIN_PATH}/nukeContainer", request.container_id])} + return {"message": run_command([f"{BIN_PATH}/nukeContainer", request.UUID])} @app.post("/container/info", dependencies=[Depends(verify_api_key)]) -def info_container(request: InfoContainerRequest): - if request.container_id: - rows = execute_db("SELECT * FROM containers WHERE UUID=?", (request.container_id,), fetch=True) +def info_container(request: Optional[UUIDRequest] = None): + if request: + rows = execute_db("SELECT * FROM containers WHERE UUID=?", (request.UUID,), fetch=True) else: rows = execute_db("SELECT * FROM containers", fetch=True) return {"containers": rows} @app.post("/container/bump", dependencies=[Depends(verify_api_key)]) -def bump_container(request: ContainerIDRequest): +def bump_container(request: UUIDRequest): today = datetime.utcnow().strftime("%Y-%m-%d") - execute_db("UPDATE containers SET bump=? WHERE UUID=?", (today, request.container_id)) - msg = run_command([f"{BIN_PATH}/bumpContainer", request.container_id]) + execute_db("UPDATE containers SET bump=? WHERE UUID=?", (today, request.UUID)) + msg = run_command([f"{BIN_PATH}/bumpContainer", request.UUID]) return {"message": msg, "bump_date": today} @app.post("/container/quota", dependencies=[Depends(verify_api_key)]) -def container_quota(request: ContainerIDRequest): +def container_quota(request: UUIDRequest): output = run_command([ - "docker", "stats", request.container_id, "--no-stream", + "docker", "stats", request.UUID, "--no-stream", "--format", "{{.MemUsage}},{{.BlockIO}}" ]) mem_usage, disk_usage = output.split(",") @@ -202,7 +196,7 @@ def get_system_info(): try: alpine_version = None last_update = None - bump_dates = execute_db("SELECT MAX(bump) FROM containers", fetch=True)[0][0] + bump_dates = execute_db("SELECT MAX(bump) AS latest_bump FROM containers", fetch=True)[0]["latest_bump"] if os.path.exists("/4server/data/update"): with open("/4server/data/update") as f: last_update = f.read().strip() @@ -238,3 +232,4 @@ if __name__ == "__main__": init_db() uvicorn.run(app, host="10.5.0.1", port=8888) + diff --git a/app/sbin/start/n8n b/app/sbin/start/n8n index ee06383..91c8eb6 100755 --- a/app/sbin/start/n8n +++ b/app/sbin/start/n8n @@ -28,16 +28,16 @@ docker run -d \ --cap-add=SYS_ADMIN \ --security-opt seccomp=unconfined \ --restart=always \ - -e N8N_HOST="${UUID}.od8n.com" \ + -e N8N_HOST="${UUID}.odoo4projects.com" \ -e N8N_PORT=5678 \ -e N8N_PROTOCOL=https \ -e NODE_ENV=production \ - -e WEBHOOK_URL="https://${UUID}.od8n.com/" \ + -e WEBHOOK_URL="https://${UUID}.odoo4projects.com/" \ -e GENERIC_TIMEZONE="UTC-3" \ -v "/4server/data/${UUID}/n8n:/home/node/.n8n" \ -v "/4server/data/${UUID}/data:/data" \ --label "traefik.enable=true" \ - --label "traefik.http.routers.${UUID}.rule=Host(\`${UUID}.od8n.com\`)" \ + --label "traefik.http.routers.${UUID}.rule=Host(\`${UUID}.odoo4projects.com\`)" \ --label "traefik.http.routers.${UUID}.entrypoints=web,websecure" \ --label "traefik.http.routers.${UUID}.tls=true" \ --label "traefik.http.routers.${UUID}.tls.certresolver=production" \ diff --git a/app/sbin/startContainer b/app/sbin/startContainer index 5289281..c804bcd 100755 --- a/app/sbin/startContainer +++ b/app/sbin/startContainer @@ -16,7 +16,9 @@ if [[ -z "$UUID" ]]; then fi while IFS="=" read -r key value; do - export "$key=$value" + if [ -n "$key" ]; then + export "$key=$value" + fi done < <(sqlite3 "$DB_PATH" " SELECT 'UUID=' || UUID FROM containers WHERE UUID='$UUID' UNION ALL SELECT 'EMAIL=' || email FROM containers WHERE UUID='$UUID' @@ -37,8 +39,7 @@ done < <(sqlite3 "$DB_PATH" " # Debug: print loaded environment variables env | grep -E 'UUID|EMAIL|EXPIRES|TAGS|ENV|AFFILIATE|IMAGE|HISTORY|COMMENT|DOMAINS|STATUS|CREATED|BUMP' -echo "UUID ${UUID}" - +eval $(echo "$ENV" | jq -r 'to_entries | .[] | "export \(.key)=\(.value)"') # Extract the second part of UUID (split by "-") SECOND_PART=$(echo "$UUID" | cut -d'-' -f2) @@ -59,5 +60,3 @@ case "$SECOND_PART" in exit 2 ;; esac - - diff --git a/app/templates/etc/doas.d/4server.conf b/app/templates/etc/doas.d/4server.conf new file mode 100644 index 0000000..b0a80eb --- /dev/null +++ b/app/templates/etc/doas.d/4server.conf @@ -0,0 +1 @@ +permit nopass 4server as root diff --git a/app/test/test b/app/test/test index 894fc0a..8939cd5 100755 --- a/app/test/test +++ b/app/test/test @@ -5,8 +5,8 @@ # ========= CONFIG ========= API_KEY="your-secret-api-key" -BASE_URL="https://dev.local" -CONTAINER_ID="001-001-123e4567-e89b-12d3-a456-426614174000" # sample UUID +CONTAINER_ID="001-002-123e4567-e89b-12d3-a456-426614174000" # sample UUID +BASE_URL="https://dev.odoo4projects.com" # ========================== # --- Functions for each endpoint --- @@ -28,7 +28,11 @@ update_container() { "tags": "N8N, SQLITE", "affiliate": "OLIVER", "domains": "N8N.local", - "status":"hot" + "status":"hot", + "env": { + "p": "123", + "BRANCH": "release" + } }' } diff --git a/app/vault/host_vars.img b/app/vault/host_vars.img index ae9e2d3..d4c0a98 100644 Binary files a/app/vault/host_vars.img and b/app/vault/host_vars.img differ