working
This commit is contained in:
1 parent
eeabd35f8d
commit
44eaced13d
3 files changed
+153
-20
No files matched your search
@@ -11,7 +11,6 @@ rex doas apk add bash doas rsync openssh linux-lts
|
|||||||
#rex doas chmod 644 /boot/extlinux.conf
|
#rex doas chmod 644 /boot/extlinux.conf
|
||||||
|
|
||||||
|
|
||||||
# ass swap file ????
|
|
||||||
|
|
||||||
rex doas mkdir -p /4server
|
rex doas mkdir -p /4server
|
||||||
rex doas chmod 777 /4server
|
rex doas chmod 777 /4server
|
||||||
|
|||||||
+55
-19
@@ -17,6 +17,14 @@ from fastapi.responses import PlainTextResponse, RedirectResponse
|
|||||||
from fastapi.security.api_key import APIKeyHeader
|
from fastapi.security.api_key import APIKeyHeader
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
|
||||||
|
)
|
||||||
|
log = logging.getLogger("4server.api")
|
||||||
|
|
||||||
# ---------------------- Constants ----------------------
|
# ---------------------- Constants ----------------------
|
||||||
DB_PATH = "/4server/data/contracts.db"
|
DB_PATH = "/4server/data/contracts.db"
|
||||||
BIN_PATH = "/4server/sbin"
|
BIN_PATH = "/4server/sbin"
|
||||||
@@ -89,13 +97,29 @@ def init_db():
|
|||||||
|
|
||||||
|
|
||||||
def execute_db(query: str, params: tuple = (), fetch: bool = False):
|
def execute_db(query: str, params: tuple = (), fetch: bool = False):
|
||||||
|
log.debug("DB open: path=%s fetch=%s", DB_PATH, fetch)
|
||||||
conn = sqlite3.connect(DB_PATH)
|
conn = sqlite3.connect(DB_PATH)
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
cursor.execute(query, params)
|
cursor.execute(query, params)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
rowcount = cursor.rowcount
|
||||||
data = cursor.fetchall() if fetch else None
|
data = cursor.fetchall() if fetch else None
|
||||||
|
except Exception as exc:
|
||||||
|
log.error(
|
||||||
|
"DB error: %s | query=%s | params=%s", exc, query, params
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
log.debug(
|
||||||
|
"DB done: rowcount=%s rows=%s | query=%s | params=%s",
|
||||||
|
rowcount,
|
||||||
|
len(data) if data else 0,
|
||||||
|
query,
|
||||||
|
params,
|
||||||
|
)
|
||||||
if data and fetch:
|
if data and fetch:
|
||||||
return [dict(row) for row in data]
|
return [dict(row) for row in data]
|
||||||
return None
|
return None
|
||||||
@@ -160,22 +184,6 @@ class ImageRequest(BaseModel):
|
|||||||
image: str
|
image: str
|
||||||
|
|
||||||
|
|
||||||
# ---------------------- Routes ----------------------
|
|
||||||
@app.get("/", include_in_schema=False)
|
|
||||||
def redirect_to_odoo():
|
|
||||||
return RedirectResponse(url="https://ODOO4PROJECTS.com")
|
|
||||||
|
|
||||||
|
|
||||||
import json
|
|
||||||
from typing import Any, Dict, Optional
|
|
||||||
|
|
||||||
from fastapi import Depends, FastAPI
|
|
||||||
from fastapi.responses import RedirectResponse
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------- Routes ----------------------
|
# ---------------------- Routes ----------------------
|
||||||
@app.get("/", include_in_schema=False)
|
@app.get("/", include_in_schema=False)
|
||||||
def redirect_to_odoo():
|
def redirect_to_odoo():
|
||||||
@@ -184,11 +192,24 @@ 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: ContainerModel):
|
def update_container(request: ContainerModel):
|
||||||
|
payload = (
|
||||||
|
request.model_dump()
|
||||||
|
if hasattr(request, "model_dump")
|
||||||
|
else request.dict()
|
||||||
|
)
|
||||||
|
log.info("update_container called: UUID=%r payload=%s", request.UUID, payload)
|
||||||
|
|
||||||
# Fetch existing record
|
# Fetch existing record
|
||||||
existing = execute_db(
|
existing = execute_db(
|
||||||
"SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True
|
"SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True
|
||||||
)
|
)
|
||||||
|
log.info(
|
||||||
|
"update_container lookup: UUID=%r existing=%s",
|
||||||
|
request.UUID,
|
||||||
|
existing,
|
||||||
|
)
|
||||||
if not existing:
|
if not existing:
|
||||||
|
log.info("update_container: no record for UUID=%r -> INSERT", request.UUID)
|
||||||
# If record does not exist, insert a new one with all given fields
|
# If record does not exist, insert a new one with all given fields
|
||||||
execute_db(
|
execute_db(
|
||||||
"""
|
"""
|
||||||
@@ -221,15 +242,21 @@ def update_container(request: ContainerModel):
|
|||||||
request.env_list,
|
request.env_list,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
inserted = execute_db(
|
||||||
|
"SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True
|
||||||
|
)
|
||||||
|
log.info("update_container INSERT result: %s", inserted)
|
||||||
return {"UUID": request.UUID, "status": "created"}
|
return {"UUID": request.UUID, "status": "created"}
|
||||||
|
|
||||||
# Existing record found, do partial update
|
# Existing record found, do partial update
|
||||||
existing = existing[0] # Assuming fetch returns list of dicts
|
existing = existing[0] # Assuming fetch returns list of dicts
|
||||||
|
log.info("update_container: existing row before update=%s", existing)
|
||||||
updates = {}
|
updates = {}
|
||||||
params = []
|
params = []
|
||||||
|
|
||||||
# Only add fields that are not None in the request
|
# Only add fields that are not None in the request
|
||||||
for field in ContainerModel.__fields__:
|
model_fields = getattr(ContainerModel, "model_fields", None) or ContainerModel.__fields__
|
||||||
|
for field in model_fields:
|
||||||
if field == "UUID":
|
if field == "UUID":
|
||||||
continue
|
continue
|
||||||
value = getattr(request, field)
|
value = getattr(request, field)
|
||||||
@@ -237,17 +264,26 @@ def update_container(request: ContainerModel):
|
|||||||
updates[field] = value
|
updates[field] = value
|
||||||
params.append(value)
|
params.append(value)
|
||||||
|
|
||||||
|
log.info("update_container: computed updates=%s params=%s", updates, params)
|
||||||
|
|
||||||
if updates:
|
if updates:
|
||||||
# Build SQL dynamically
|
# Build SQL dynamically
|
||||||
set_clause = ", ".join(f"{k}=?" for k in updates.keys())
|
set_clause = ", ".join(f"{k}=?" for k in updates.keys())
|
||||||
params.append(request.UUID) # UUID for WHERE clause
|
params.append(request.UUID) # UUID for WHERE clause
|
||||||
execute_db(f"UPDATE containers SET {set_clause} WHERE UUID=?", tuple(params))
|
sql = f"UPDATE containers SET {set_clause} WHERE UUID=?"
|
||||||
|
log.info("update_container: executing SQL=%s params=%s", sql, tuple(params))
|
||||||
|
execute_db(sql, tuple(params))
|
||||||
|
after = execute_db(
|
||||||
|
"SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True
|
||||||
|
)
|
||||||
|
log.info("update_container: row after update=%s", after)
|
||||||
return {
|
return {
|
||||||
"UUID": request.UUID,
|
"UUID": request.UUID,
|
||||||
"status": "updated",
|
"status": "updated",
|
||||||
"fields_updated": list(updates.keys()),
|
"fields_updated": list(updates.keys()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("update_container: no non-None fields supplied -> no_change")
|
||||||
return {"UUID": request.UUID, "status": "no_change"}
|
return {"UUID": request.UUID, "status": "no_change"}
|
||||||
|
|
||||||
|
|
||||||
@@ -257,7 +293,7 @@ def start_container(request: UUIDRequest):
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/container/restartAllContainers", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/restartAllContainers", dependencies=[Depends(verify_api_key)])
|
||||||
def start_container(request: ImageRequest):
|
def restart_all_containers_by_image(request: ImageRequest):
|
||||||
return {"message": run_command([f"{BIN_PATH}/restartContainers", request.image])}
|
return {"message": run_command([f"{BIN_PATH}/restartContainers", request.image])}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Executable
+98
@@ -0,0 +1,98 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# swapon — create, enable and persist an 8GB swap file.
|
||||||
|
#
|
||||||
|
# Runs on each managed server (Alpine/OpenRC). Deploy with ./update_sbin, then:
|
||||||
|
#
|
||||||
|
# rex doas /4server/sbin/swapon
|
||||||
|
#
|
||||||
|
# The script aborts if any swap is already active or if /swapfile already
|
||||||
|
# exists, so it is safe to run against the whole fleet.
|
||||||
|
#
|
||||||
|
# Optional first argument: swap size in MiB (default 8192 = 8GB).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SWAPFILE="/swapfile"
|
||||||
|
SWAP_SIZE_MB="${1:-8192}"
|
||||||
|
FSTAB="/etc/fstab"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[$(hostname)] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "[$(hostname)] ERROR: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$SWAP_SIZE_MB" in
|
||||||
|
''|*[!0-9]*) die "swap size must be a positive integer in MiB" ;;
|
||||||
|
esac
|
||||||
|
[ "$SWAP_SIZE_MB" -gt 0 ] || die "swap size must be greater than zero"
|
||||||
|
|
||||||
|
# Remove a partially created swap file if anything fails before it is enabled.
|
||||||
|
CREATED=0
|
||||||
|
cleanup() {
|
||||||
|
[ "$CREATED" -eq 1 ] || return 0
|
||||||
|
[ -e "$SWAPFILE" ] || return 0
|
||||||
|
if grep -q "^${SWAPFILE}[[:space:]]" /proc/swaps; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log "removing incomplete ${SWAPFILE}"
|
||||||
|
rm -f "$SWAPFILE"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# --- must run as root ---------------------------------------------------
|
||||||
|
[ "$(id -u)" -eq 0 ] || die "must be run as root (use: rex doas /4server/sbin/swapon)"
|
||||||
|
|
||||||
|
# --- abort if swap is already active ------------------------------------
|
||||||
|
# /proc/swaps always has a header line; anything more means swap is in use.
|
||||||
|
if [ "$(wc -l < /proc/swaps)" -gt 1 ]; then
|
||||||
|
echo "[$(hostname)] ERROR: swap is already active:" >&2
|
||||||
|
cat /proc/swaps >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- abort if the swap file already exists ------------------------------
|
||||||
|
if [ -e "$SWAPFILE" ]; then
|
||||||
|
die "$SWAPFILE already exists; remove it manually before recreating swap"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- persistence requires OpenRC's swap service --------------------------
|
||||||
|
[ -x /etc/init.d/swap ] || \
|
||||||
|
die "/etc/init.d/swap not found; cannot persist swap across reboots"
|
||||||
|
|
||||||
|
# --- make sure there is enough room on / ---------------------------------
|
||||||
|
AVAIL_KB=$(df -Pk / | awk 'NR==2 {print $4}')
|
||||||
|
# Leave a 100MiB margin for filesystem metadata.
|
||||||
|
REQUIRED_KB=$((SWAP_SIZE_MB * 1024 + 102400))
|
||||||
|
[ "$AVAIL_KB" -ge "$REQUIRED_KB" ] || \
|
||||||
|
die "not enough free space on / (need ${SWAP_SIZE_MB}MiB, have $((AVAIL_KB / 1024))MiB)"
|
||||||
|
|
||||||
|
# --- create the swap file -----------------------------------------------
|
||||||
|
log "creating ${SWAP_SIZE_MB}MiB swap file at ${SWAPFILE}"
|
||||||
|
CREATED=1
|
||||||
|
dd if=/dev/zero of="$SWAPFILE" bs=1M count="$SWAP_SIZE_MB"
|
||||||
|
chmod 600 "$SWAPFILE"
|
||||||
|
/sbin/mkswap "$SWAPFILE"
|
||||||
|
|
||||||
|
# --- persist across reboots ---------------------------------------------
|
||||||
|
# OpenRC's swap service runs `swapon -a`, which reads /etc/fstab.
|
||||||
|
if ! grep -qE "^[[:space:]]*${SWAPFILE}[[:space:]]" "$FSTAB"; then
|
||||||
|
# Terminate the last line first in case fstab has no trailing newline.
|
||||||
|
[ -z "$(tail -c 1 "$FSTAB")" ] || echo >> "$FSTAB"
|
||||||
|
echo "${SWAPFILE} none swap sw 0 0" >> "$FSTAB"
|
||||||
|
log "added ${SWAPFILE} to ${FSTAB}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rc-update add swap boot >/dev/null 2>&1 || \
|
||||||
|
die "could not add the swap service to the boot runlevel"
|
||||||
|
|
||||||
|
# --- enable it now -------------------------------------------------------
|
||||||
|
# Absolute path avoids recursing into this script if /4server/sbin is on PATH.
|
||||||
|
/sbin/swapon "$SWAPFILE"
|
||||||
|
|
||||||
|
log "swap enabled:"
|
||||||
|
cat /proc/swaps
|
||||||
Reference in new issue
Block a user