Compare commits

...
14 Commits
Author SHA1 Message Date
oliver 920a7de568 db access 2026-11-08 05:08:17 -03:00
oliver 44eaced13d working 2026-10-21 17:27:33 -03:00
oliver eeabd35f8d working 2026-10-17 18:57:34 -03:00
oliver de583713da c 2026-10-15 18:52:10 -03:00
oliver 10f0422155 odoo 2026-10-15 18:33:33 -03:00
oliver 7263c859a2 fixes 2026-10-15 17:45:09 -03:00
oliver cd187b6d3d k 2026-10-15 05:05:31 -03:00
oliver 8772004ff6 working 2026-10-14 18:35:16 -03:00
oliver 4868517abb update 2026-10-14 10:27:30 -03:00
oliver a915a87ed9 works 2026-10-14 10:18:40 -03:00
oliver 722e499089 s 2026-10-14 10:00:09 -03:00
oliver d25a9a94a3 n8n + hostname 2026-10-14 07:26:16 -03:00
oliver b6f6da01e0 host in domain 2026-10-14 07:24:18 -03:00
oliver d6d0f37c5a works 2026-10-13 20:00:20 -03:00
17 changed files with 262 additions and 44 deletions

No files matched your search

+6 -6
View File
@@ -1,8 +1,3 @@
#Host dev
# Hostname dev
# User oliver
# Port 2222
Host dev Host dev
Hostname dev Hostname dev
User 4server User 4server
@@ -30,7 +25,7 @@ Host meppel
IdentityFile /app/host_vars/meppel/meppel IdentityFile /app/host_vars/meppel/meppel
Host sydney Host sydney
Hostname 192.168.9.22 Hostname 192.168.9.14
User 4server User 4server
IdentityFile /app/host_vars/sydney/sydney IdentityFile /app/host_vars/sydney/sydney
@@ -38,3 +33,8 @@ Host saopaulo
Hostname 192.168.9.11 Hostname 192.168.9.11
User 4server User 4server
IdentityFile /app/host_vars/saopaulo/saopaulo IdentityFile /app/host_vars/saopaulo/saopaulo
Host sydney2
Hostname 192.168.9.22
User 4server
IdentityFile /app/host_vars/sydney2/sydney2
+3
View File
@@ -15,3 +15,6 @@
192.168.9.21 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtexcutExMflSRPJOwGPuZx49c+ZUMoElNPNntXONOuVZ8jM+zzA/4s+HCj+RWdHMnarLcNJjwRZRq7JWruqmr1EN9eNmvu13cLCKZr6Ape1gt8VmMhvQ9nA7eMx3UdvnWgXjLCfJy+BKh2XVAneM5lOSPMcyQlcACrLQVkse9EKhxhV5nEjXKYxSNetIPK9PxglXznpC8IpuWlXnDH2R8vDhdvBmBFTKOjN5Wa+GrMfElWeQOjyCpNcMVYohvV87VN6FxHQTADFTm2CKy/W7pnM8rI55ZbBIMfvLyhpM+vtFh1sJlIZSFnn+ytMUImNEAgBDI3fCpwZ99zeLViGhQJlBkdCUI+JJK9XJjpcOMNydeWh142RGU6juPuSLOZPqKNc3BpZYgPY0vMDgDWgT9AcYup3rAJ/UnUnhsxiT2h/gx7+t/j9xg01BQF5S6mezaueuavHCh9MvAzXiJXR8zanhl/Hh9BKgIAFrZh71CzP/PYG33BKCe9DXA09aJrf8= 192.168.9.21 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtexcutExMflSRPJOwGPuZx49c+ZUMoElNPNntXONOuVZ8jM+zzA/4s+HCj+RWdHMnarLcNJjwRZRq7JWruqmr1EN9eNmvu13cLCKZr6Ape1gt8VmMhvQ9nA7eMx3UdvnWgXjLCfJy+BKh2XVAneM5lOSPMcyQlcACrLQVkse9EKhxhV5nEjXKYxSNetIPK9PxglXznpC8IpuWlXnDH2R8vDhdvBmBFTKOjN5Wa+GrMfElWeQOjyCpNcMVYohvV87VN6FxHQTADFTm2CKy/W7pnM8rI55ZbBIMfvLyhpM+vtFh1sJlIZSFnn+ytMUImNEAgBDI3fCpwZ99zeLViGhQJlBkdCUI+JJK9XJjpcOMNydeWh142RGU6juPuSLOZPqKNc3BpZYgPY0vMDgDWgT9AcYup3rAJ/UnUnhsxiT2h/gx7+t/j9xg01BQF5S6mezaueuavHCh9MvAzXiJXR8zanhl/Hh9BKgIAFrZh71CzP/PYG33BKCe9DXA09aJrf8=
192.168.9.22 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMO5VhveQe76kEOdVqrdoTwW1fNzmgDzZNBznFEC4ohtXes0VMtJR02lhc+55XTIUX+EDxBtAh8U+kJpFeLetMU= 192.168.9.22 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMO5VhveQe76kEOdVqrdoTwW1fNzmgDzZNBznFEC4ohtXes0VMtJR02lhc+55XTIUX+EDxBtAh8U+kJpFeLetMU=
192.168.9.21 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGlzLZWNjSaDuDd4e66LoBizY5j+QCsifIIxkvX4CrzP/AqAgWDEEgT+pAXBFkNJlBR6TDFJ0bIdwZTbcq8/72E= 192.168.9.21 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGlzLZWNjSaDuDd4e66LoBizY5j+QCsifIIxkvX4CrzP/AqAgWDEEgT+pAXBFkNJlBR6TDFJ0bIdwZTbcq8/72E=
192.168.9.14 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBoHCfS8EQBWo2v5m/D2S3Sy97yZcVbbLJyW2sf+Rd7c
192.168.9.14 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC3LOvVI7S+/YYRpe/SxElLVn/bRg85PtH6MeYS7DMot6Uj3SnAmOxyVmU5j9dxS+039EjoLjMr+ppx+sRqVtr78q8GhKXmCKmp2EDcieEbJ9h66Drb8JoOuQJFyL3TiXwa9MNFy5qkBahwnhF+1wuV2a82/irnqYvGFFDuNuzXRpzJlZPPQddMFryj/9J3Qu2E1vlXw30Ap1EQD0+I/Zlifj5BQpJMA6ZpOnmXPY4k3qnY/tnyufqfZJQE7DOdxY2NOPOOOYt8JsG0gaydk7+Kt8/01TLBGVKgfmlbKTkKQYQUgfc87Rx1of4AaZMkBnBjTto/x+DIaFh3BvivEUSHbF/9NYL9/cj+Alb9x5qXo4bW6TFFhC/RVPnQ0XWKaH0cDFUZ7sgQ6XXapXYvojZ4TL8SZ3xzRt3cB2t10BQGgkEOKSJ7sDltxEthHhQDzy2pQg6iuFPAL1ss4oz/UiZwTRJ3lp1kto2wVU2JzIci46GfDEV63wOsw8aubQ2LXQM=
192.168.9.14 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMnz/R4sp9QPwJ4VRnsrz9EBjf1E0YuzUEC0TlptUXuyhVcUB4ZSbvd67PsXOuVxQiIIppqnZWzNL1w5dCLJoyU=
+1 -1
View File
@@ -7,7 +7,7 @@ fi
host_vars_dir="/app/host_vars" host_vars_dir="/app/host_vars"
keys=(NEBULA_CA API_KEY HOSTNAME NEBULA_CRT NEBULA_KEY SSH_PRIVATE SSH_PUBLIC) keys=(NEBULA_CA API_KEY HOSTNAME NEBULA_CRT NEBULA_KEY SSH_PRIVATE SSH_PUBLIC PROVIDER COST CONTRACT_END )
NEBULA_CA=$(<"$host_vars_dir/ca.crt") NEBULA_CA=$(<"$host_vars_dir/ca.crt")
+3 -4
View File
@@ -6,12 +6,11 @@ rex doas apk update
rex doas apk add bash doas rsync 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
rex doas chown root:root /boot/extlinux.conf #rex doas chown root:root /boot/extlinux.conf
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
+14
View File
@@ -65,6 +65,20 @@ doas docker exec "$UUID" rm -rf /root/.local/share/Odoo/filestore
if ! doas docker exec "$UUID" odoo db --db_host beedb -w "$ODOO_DB_PASSWORD" -r "$UUID" load "$UUID" $FILENAME -f; then if ! doas docker exec "$UUID" odoo db --db_host beedb -w "$ODOO_DB_PASSWORD" -r "$UUID" load "$UUID" $FILENAME -f; then
echo "ERROR: odoo db load failed - putting the old filestore back" echo "ERROR: odoo db load failed - putting the old filestore back"
# Restrict database access to its UUID owner
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" \
-p "$POSTGRES_PORT" -d postgres \
-v ON_ERROR_STOP=1 \
-c "REVOKE CONNECT, TEMPORARY ON DATABASE \"$UUID\" FROM PUBLIC;
GRANT CONNECT ON DATABASE \"$UUID\" TO \"$UUID\";
ALTER DATABASE \"$UUID\" OWNER TO \"$UUID\";
ALTER ROLE \"$UUID\" NOCREATEDB;"
doas docker exec "$UUID" sh -c "[ -d $OLD_FS ] && rm -rf /home/odoo/.local/share/Odoo/filestore && mv $OLD_FS /home/odoo/.local/share/Odoo/filestore; true" doas docker exec "$UUID" sh -c "[ -d $OLD_FS ] && rm -rf /home/odoo/.local/share/Odoo/filestore && mv $OLD_FS /home/odoo/.local/share/Odoo/filestore; true"
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \ PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \ -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
+59 -23
View File
@@ -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()
cursor.execute(query, params) try:
conn.commit() cursor.execute(query, params)
data = cursor.fetchall() if fetch else None conn.commit()
conn.close() rowcount = cursor.rowcount
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()
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])}
+25
View File
@@ -36,9 +36,34 @@ def read_file(path):
return None return None
def read_env_file(path, keys):
"""Read KEY=VALUE lines from path and return the requested keys."""
result = {key: None for key in keys}
try:
with open(path) as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
if line.startswith("export "):
line = line[len("export "):].lstrip()
key, value = line.split("=", 1)
key = key.strip()
if key in result:
result[key] = value.strip().strip("\"'")
except (FileNotFoundError, PermissionError):
pass
return result
def main(): def main():
audit = {} audit = {}
# Contract info from /etc/4server
audit.update(
read_env_file("/etc/4server", ["PROVIDER", "COST", "CONTRACT_END"])
)
# Container identity # Container identity
audit["container_id"] = os.environ.get("HOSTNAME") audit["container_id"] = os.environ.get("HOSTNAME")
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# checkFilestoreAll — check the filestore of every active container.
# Runs checkFilestore for each UUID returned by activeContainers.
# Output: "<UUID> OK" or "<UUID> NOK" per container.
SBIN_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
active_uuids() {
"$SBIN_DIR/activeContainers" | awk 'NF && $1 != "uuid" && $1 !~ /^-/ {print $1}'
}
failed=0
while read -r UUID; do
[[ -n "$UUID" ]] || continue
if "$SBIN_DIR/checkFilestore" "$UUID" >/dev/null 2>&1; then
echo "$UUID OK"
else
echo "$UUID NOK"
failed=1
fi
done < <(active_uuids)
exit "$failed"
+2 -2
View File
@@ -28,14 +28,14 @@ ETC_DIR="$BASEURL/etc/"
SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1) SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1)
LABEL_DOMAINS=("$UUID.odoo4projects.com") LABEL_DOMAINS=("$UUID.odoo4projects.com" "$UUID.$HOSTNAME.odoo4projects.com")
if [ -f "$BASEURL/etc/domain" ]; then if [ -f "$BASEURL/etc/domain" ]; then
while IFS= read -r domain || [[ -n $domain ]]; do while IFS= read -r domain || [[ -n $domain ]]; do
[ -z "$domain" ] && continue [ -z "$domain" ] && continue
LABEL_DOMAINS+=("$domain") LABEL_DOMAINS+=("$domain")
done < "$BASEURL/etc/domain" done < "$BASEURL/etc/domain"
else else
echo "$(date '+%Y-%m-%d %H:%M:%S') No custom domain file at $BASEURL/etc/domain, using default domain $UUID.odoo4projects.com only" echo "$(date '+%Y-%m-%d %H:%M:%S') No custom domain file at $BASEURL/etc/domain, using default domains $UUID.odoo4projects.com and $UUID.$HOSTNAME.odoo4projects.com only"
fi fi
RULE="" RULE=""
+2 -2
View File
@@ -29,14 +29,14 @@ ETC_DIR="$BASEURL/etc/"
SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1) SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1)
LABEL_DOMAINS=("$UUID.odoo4projects.com") LABEL_DOMAINS=("$UUID.odoo4projects.com" "$UUID.$HOSTNAME.odoo4projects.com")
if [ -f "$BASEURL/etc/domain" ]; then if [ -f "$BASEURL/etc/domain" ]; then
while IFS= read -r domain || [[ -n $domain ]]; do while IFS= read -r domain || [[ -n $domain ]]; do
[ -z "$domain" ] && continue [ -z "$domain" ] && continue
LABEL_DOMAINS+=("$domain") LABEL_DOMAINS+=("$domain")
done < "$BASEURL/etc/domain" done < "$BASEURL/etc/domain"
else else
echo "$(date '+%Y-%m-%d %H:%M:%S') No custom domain file at $BASEURL/etc/domain, using default domain $UUID.odoo4projects.com only" echo "$(date '+%Y-%m-%d %H:%M:%S') No custom domain file at $BASEURL/etc/domain, using default domains $UUID.odoo4projects.com and $UUID.$HOSTNAME.odoo4projects.com only"
fi fi
RULE="" RULE=""
+3 -3
View File
@@ -29,14 +29,14 @@ ETC_DIR="$BASEURL/etc/"
SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1) SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1)
LABEL_DOMAINS=("$UUID.odoo4projects.com") LABEL_DOMAINS=("$UUID.odoo4projects.com" "$UUID.$HOSTNAME.odoo4projects.com")
if [ -f "$BASEURL/etc/domain" ]; then if [ -f "$BASEURL/etc/domain" ]; then
while IFS= read -r domain || [[ -n $domain ]]; do while IFS= read -r domain || [[ -n $domain ]]; do
[ -z "$domain" ] && continue [ -z "$domain" ] && continue
LABEL_DOMAINS+=("$domain") LABEL_DOMAINS+=("$domain")
done < "$BASEURL/etc/domain" done < "$BASEURL/etc/domain"
else else
echo "$(date '+%Y-%m-%d %H:%M:%S') No custom domain file at $BASEURL/etc/domain, using default domain $UUID.odoo4projects.com only" echo "$(date '+%Y-%m-%d %H:%M:%S') No custom domain file at $BASEURL/etc/domain, using default domains $UUID.odoo4projects.com and $UUID.$HOSTNAME.odoo4projects.com only"
fi fi
RULE="" RULE=""
@@ -73,7 +73,7 @@ chmod 777 ${GIT_DIR}
touch ${GIT_DIR}keys/id_rsa.pub touch ${GIT_DIR}keys/id_rsa.pub
echo "git clone \"ssh://git@${UUID}.odoo4projects.com:${PORT}/git-server/repos/odoo.git\"" > "${ETC_DIR}/gitpath" echo "git clone \"ssh://git@${UUID}.$HOSTNAME.odoo4projects.com:${PORT}/git-server/repos/odoo.git\"" > "${ETC_DIR}/gitpath"
echo "$(date '+%Y-%m-%d %H:%M:%S') Wrote git clone path for $UUID to ${ETC_DIR}gitpath (SSH port $PORT)" echo "$(date '+%Y-%m-%d %H:%M:%S') Wrote git clone path for $UUID to ${ETC_DIR}gitpath (SSH port $PORT)"
+2 -2
View File
@@ -4,7 +4,7 @@
# Get the hostname of the machine # Get the hostname of the machine
HOSTNAME=$(hostname) HOSTNAME=$(hostname)
echo "$(date '+%Y-%m-%d %H:%M:%S') START N8N UUID=${UUID} HOST=${HOSTNAME} DOMAIN=${UUID}.odoo4projects.com" echo "$(date '+%Y-%m-%d %H:%M:%S') START N8N UUID=${UUID} HOST=${HOSTNAME} DOMAIN=${UUID}.odoo4projects.com DOMAIN2=${UUID}.${HOSTNAME}.odoo4projects.com"
mkdir -p /4server/data/${UUID}/n8n mkdir -p /4server/data/${UUID}/n8n
mkdir -p /4server/data/${UUID}/data mkdir -p /4server/data/${UUID}/data
@@ -53,7 +53,7 @@ docker run -d \
-v "/4server/data/${UUID}/data:/data" \ -v "/4server/data/${UUID}/data:/data" \
-v "/4server/data/${UUID}/backup:/backup" \ -v "/4server/data/${UUID}/backup:/backup" \
--label "traefik.enable=true" \ --label "traefik.enable=true" \
--label "traefik.http.routers.${UUID}.rule=Host(\`${UUID}.odoo4projects.com\`)" \ --label "traefik.http.routers.${UUID}.rule=Host(\`${UUID}.odoo4projects.com\`) || Host(\`${UUID}.${HOSTNAME}.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" \
+17 -1
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Collect server runtime stats (uptime, top, cgroup, mounts) and return as JSON.""" """Collect server runtime stats (uptime, top, cgroup, mounts, hdd) and return as JSON."""
import json import json
import os
import subprocess import subprocess
@@ -32,6 +33,20 @@ def read_file(path):
return None return None
def disk_usage(path):
"""Total/used/available bytes for the filesystem at `path` (same figures as df)."""
try:
st = os.statvfs(path)
return {
"path": path,
"total_bytes": st.f_blocks * st.f_frsize,
"used_bytes": (st.f_blocks - st.f_bfree) * st.f_frsize,
"available_bytes": st.f_bavail * st.f_frsize,
}
except Exception as e:
return f"ERROR: {e}"
def main(): def main():
stats = {} stats = {}
@@ -39,6 +54,7 @@ def main():
stats["top"] = cmd(["sh", "-c", "top -b -n 1 | head -n 3"], splitlines=True) stats["top"] = cmd(["sh", "-c", "top -b -n 1 | head -n 3"], splitlines=True)
stats["cgroup"] = read_file("/proc/self/cgroup") stats["cgroup"] = read_file("/proc/self/cgroup")
stats["mounts"] = cmd(["mount"], splitlines=True) stats["mounts"] = cmd(["mount"], splitlines=True)
stats["hdd"] = disk_usage("/")
print(json.dumps(stats, indent=2)) print(json.dumps(stats, indent=2))
+98
View File
@@ -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
+3
View File
@@ -1 +1,4 @@
API_KEY={{API_KEY}} API_KEY={{API_KEY}}
PROVIDER={{PROVIDER}}
COST={{COST}}
CONTRACT_END={{CONTRACT_END}}
+1
View File
@@ -1 +1,2 @@
permit nopass 4server as root permit nopass 4server as root
permit nopass root as root
Binary file not shown.