This commit is contained in:
Oliver
2025-09-02 08:54:21 +02:00
parent 7d0f1967b3
commit 0ea18322be
11 changed files with 69 additions and 61 deletions

View File

@@ -14,3 +14,8 @@ Host manchester
User 4server User 4server
IdentityFile /app/host_vars/manchester/manchester IdentityFile /app/host_vars/manchester/manchester
Host boston
Hostname 192.168.9.16
User 4server
IdentityFile /app/host_vars/boston/boston

7
app/hardening Executable file
View File

@@ -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

View File

@@ -1,2 +1,2 @@
API_KEY=4h6lDzAOVksuCqmhEB3 API_KEY=your-secret-api-key
HOSTNAME="dev" HOSTNAME="dev"

1
app/host_vars/hosts Normal file
View File

@@ -0,0 +1 @@
dev

View File

@@ -3,7 +3,7 @@ template templates/hostname /etc/hostname
rex doas apk update 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 ### activate lts kerner
template templates/extlinux.conf /boot/extlinux.conf template templates/extlinux.conf /boot/extlinux.conf
@@ -12,7 +12,6 @@ rex doas chmod 644 /boot/extlinux.conf
# ass swap file ???? # ass swap file ????
# ------ disable root user and login
rex doas mkdir -p /4server rex doas mkdir -p /4server
rex doas chmod 777 /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 usermod -p Ne82Vrx8QfUdNHvLgct 4server
rex doas passwd -u 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 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 sshd restart
rex doas rc-service nebula restart rex doas rc-service nebula restart
rex doas reboot rex doas reboot

View File

@@ -8,16 +8,19 @@ import sqlite3
import subprocess import subprocess
import os import os
import uvicorn import uvicorn
from typing import Optional from typing import Dict, Any, Optional
from datetime import datetime from datetime import datetime
import json
# Constants
# ---------------------- Constants ----------------------
DB_PATH = "/4server/data/contracts.db" DB_PATH = "/4server/data/contracts.db"
BIN_PATH = "/4server/sbin" BIN_PATH = "/4server/sbin"
API_KEY = os.getenv("API_KEY", "your-secret-api-key") API_KEY = os.getenv("API_KEY", "your-secret-api-key")
VERSION = "API: 0.0.7" VERSION = "API: 0.0.7"
# FastAPI app # ---------------------- FastAPI App ----------------------
app = FastAPI() app = FastAPI()
api_key_header = APIKeyHeader(name="X-API-Key") api_key_header = APIKeyHeader(name="X-API-Key")
@@ -44,7 +47,7 @@ def init_db():
cursor.execute(''' cursor.execute('''
CREATE TABLE IF NOT EXISTS containers ( CREATE TABLE IF NOT EXISTS containers (
ID INTEGER PRIMARY KEY AUTOINCREMENT, ID INTEGER PRIMARY KEY AUTOINCREMENT,
UUID CHAR(50), UUID CHAR(50) UNIQUE,
email CHAR(100), email CHAR(100),
expires DATE, expires DATE,
tags TEXT, tags TEXT,
@@ -65,27 +68,24 @@ def init_db():
def execute_db(query: str, params: tuple = (), fetch: bool = False): def execute_db(query: str, params: tuple = (), fetch: bool = False):
conn = sqlite3.connect(DB_PATH) conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row # <-- Add this line conn.row_factory = sqlite3.Row
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query, params) cursor.execute(query, params)
conn.commit() conn.commit()
data = cursor.fetchall() if fetch else None data = cursor.fetchall() if fetch else None
conn.close() conn.close()
if data and fetch: if data and fetch:
return [dict(row) for row in data] # Convert each row to dict return [dict(row) for row in data]
return data return None
# ---------------------- Models ---------------------- # ---------------------- Models ----------------------
class ContainerModel(BaseModel): class ContainerModel(BaseModel):
UUID: Optional[str] = None UUID: str
email: Optional[str] = None email: Optional[str] = None
expires: Optional[str] = None expires: Optional[str] = None
tags: Optional[str] = None tags: Optional[str] = None
env: Optional[str] = None env: Optional[Dict[str, Any]] = None
affiliate: Optional[str] = None affiliate: Optional[str] = None
image: Optional[str] = None image: Optional[str] = None
history: Optional[str] = None history: Optional[str] = None
@@ -96,16 +96,8 @@ class ContainerModel(BaseModel):
bump: Optional[str] = None bump: Optional[str] = None
class ContainerIDRequest(BaseModel): class UUIDRequest(BaseModel):
container_id: Optional[str] = None UUID: str
class UpdateContainerRequest(ContainerModel):
pass
class InfoContainerRequest(BaseModel):
container_id: Optional[str] = None
# ---------------------- Routes ---------------------- # ---------------------- Routes ----------------------
@@ -115,7 +107,9 @@ def redirect_to_odoo():
@app.post("/container/update", dependencies=[Depends(verify_api_key)]) @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) existing = execute_db("SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True)
if existing: if existing:
execute_db(""" execute_db("""
@@ -123,7 +117,7 @@ def update_container(request: UpdateContainerRequest):
history=?, comment=?, domains=?, status=?, created=?, bump=? history=?, comment=?, domains=?, status=?, created=?, bump=?
WHERE UUID=? 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.image, request.history, request.comment, request.domains, request.status,
request.created, request.bump, request.UUID request.created, request.bump, request.UUID
)) ))
@@ -133,7 +127,7 @@ def update_container(request: UpdateContainerRequest):
comment, domains, status, created, bump) comment, domains, status, created, bump)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 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.affiliate, request.image, request.history, request.comment,
request.domains, request.status, request.created, request.bump 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)]) @app.post("/container/start", dependencies=[Depends(verify_api_key)])
def start_container(request: ContainerIDRequest): def start_container(request: UUIDRequest):
return {"message": run_command([f"{BIN_PATH}/startContainer", request.container_id])} return {"message": run_command([f"{BIN_PATH}/startContainer", request.UUID])}
@app.post("/container/stop", dependencies=[Depends(verify_api_key)]) @app.post("/container/stop", dependencies=[Depends(verify_api_key)])
def stop_container(request: ContainerIDRequest): def stop_container(request: UUIDRequest):
return {"message": run_command([f"{BIN_PATH}/stopContainer", request.container_id])} return {"message": run_command([f"{BIN_PATH}/stopContainer", request.UUID])}
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)]) @app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
def nuke_container(request: ContainerIDRequest): def nuke_container(request: UUIDRequest):
status = execute_db("SELECT status FROM containers WHERE UUID=?", (request.container_id,), fetch=True) status = execute_db("SELECT status FROM containers WHERE UUID=?", (request.UUID,), fetch=True)
if not status or status[0][0] != "nuke": if not status or status[0]["status"] != "nuke":
raise HTTPException(400, "Container status is not '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)]) @app.post("/container/info", dependencies=[Depends(verify_api_key)])
def info_container(request: InfoContainerRequest): def info_container(request: Optional[UUIDRequest] = None):
if request.container_id: if request:
rows = execute_db("SELECT * FROM containers WHERE UUID=?", (request.container_id,), fetch=True) rows = execute_db("SELECT * FROM containers WHERE UUID=?", (request.UUID,), fetch=True)
else: else:
rows = execute_db("SELECT * FROM containers", fetch=True) rows = execute_db("SELECT * FROM containers", fetch=True)
return {"containers": rows} return {"containers": rows}
@app.post("/container/bump", dependencies=[Depends(verify_api_key)]) @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") today = datetime.utcnow().strftime("%Y-%m-%d")
execute_db("UPDATE containers SET bump=? WHERE UUID=?", (today, request.container_id)) execute_db("UPDATE containers SET bump=? WHERE UUID=?", (today, request.UUID))
msg = run_command([f"{BIN_PATH}/bumpContainer", request.container_id]) msg = run_command([f"{BIN_PATH}/bumpContainer", request.UUID])
return {"message": msg, "bump_date": today} return {"message": msg, "bump_date": today}
@app.post("/container/quota", dependencies=[Depends(verify_api_key)]) @app.post("/container/quota", dependencies=[Depends(verify_api_key)])
def container_quota(request: ContainerIDRequest): def container_quota(request: UUIDRequest):
output = run_command([ output = run_command([
"docker", "stats", request.container_id, "--no-stream", "docker", "stats", request.UUID, "--no-stream",
"--format", "{{.MemUsage}},{{.BlockIO}}" "--format", "{{.MemUsage}},{{.BlockIO}}"
]) ])
mem_usage, disk_usage = output.split(",") mem_usage, disk_usage = output.split(",")
@@ -202,7 +196,7 @@ def get_system_info():
try: try:
alpine_version = None alpine_version = None
last_update = 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"): if os.path.exists("/4server/data/update"):
with open("/4server/data/update") as f: with open("/4server/data/update") as f:
last_update = f.read().strip() last_update = f.read().strip()
@@ -238,3 +232,4 @@ if __name__ == "__main__":
init_db() init_db()
uvicorn.run(app, host="10.5.0.1", port=8888) uvicorn.run(app, host="10.5.0.1", port=8888)

View File

@@ -28,16 +28,16 @@ docker run -d \
--cap-add=SYS_ADMIN \ --cap-add=SYS_ADMIN \
--security-opt seccomp=unconfined \ --security-opt seccomp=unconfined \
--restart=always \ --restart=always \
-e N8N_HOST="${UUID}.od8n.com" \ -e N8N_HOST="${UUID}.odoo4projects.com" \
-e N8N_PORT=5678 \ -e N8N_PORT=5678 \
-e N8N_PROTOCOL=https \ -e N8N_PROTOCOL=https \
-e NODE_ENV=production \ -e NODE_ENV=production \
-e WEBHOOK_URL="https://${UUID}.od8n.com/" \ -e WEBHOOK_URL="https://${UUID}.odoo4projects.com/" \
-e GENERIC_TIMEZONE="UTC-3" \ -e GENERIC_TIMEZONE="UTC-3" \
-v "/4server/data/${UUID}/n8n:/home/node/.n8n" \ -v "/4server/data/${UUID}/n8n:/home/node/.n8n" \
-v "/4server/data/${UUID}/data:/data" \ -v "/4server/data/${UUID}/data:/data" \
--label "traefik.enable=true" \ --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}.entrypoints=web,websecure" \
--label "traefik.http.routers.${UUID}.tls=true" \ --label "traefik.http.routers.${UUID}.tls=true" \
--label "traefik.http.routers.${UUID}.tls.certresolver=production" \ --label "traefik.http.routers.${UUID}.tls.certresolver=production" \

View File

@@ -16,7 +16,9 @@ if [[ -z "$UUID" ]]; then
fi fi
while IFS="=" read -r key value; do while IFS="=" read -r key value; do
export "$key=$value" if [ -n "$key" ]; then
export "$key=$value"
fi
done < <(sqlite3 "$DB_PATH" " done < <(sqlite3 "$DB_PATH" "
SELECT 'UUID=' || UUID FROM containers WHERE UUID='$UUID' SELECT 'UUID=' || UUID FROM containers WHERE UUID='$UUID'
UNION ALL SELECT 'EMAIL=' || email 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 # Debug: print loaded environment variables
env | grep -E 'UUID|EMAIL|EXPIRES|TAGS|ENV|AFFILIATE|IMAGE|HISTORY|COMMENT|DOMAINS|STATUS|CREATED|BUMP' 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 "-") # Extract the second part of UUID (split by "-")
SECOND_PART=$(echo "$UUID" | cut -d'-' -f2) SECOND_PART=$(echo "$UUID" | cut -d'-' -f2)
@@ -59,5 +60,3 @@ case "$SECOND_PART" in
exit 2 exit 2
;; ;;
esac esac

View File

@@ -0,0 +1 @@
permit nopass 4server as root

View File

@@ -5,8 +5,8 @@
# ========= CONFIG ========= # ========= CONFIG =========
API_KEY="your-secret-api-key" API_KEY="your-secret-api-key"
BASE_URL="https://dev.local" CONTAINER_ID="001-002-123e4567-e89b-12d3-a456-426614174000" # sample UUID
CONTAINER_ID="001-001-123e4567-e89b-12d3-a456-426614174000" # sample UUID BASE_URL="https://dev.odoo4projects.com"
# ========================== # ==========================
# --- Functions for each endpoint --- # --- Functions for each endpoint ---
@@ -28,7 +28,11 @@ update_container() {
"tags": "N8N, SQLITE", "tags": "N8N, SQLITE",
"affiliate": "OLIVER", "affiliate": "OLIVER",
"domains": "N8N.local", "domains": "N8N.local",
"status":"hot" "status":"hot",
"env": {
"p": "123",
"BRANCH": "release"
}
}' }'
} }

Binary file not shown.