Compare commits

...
26 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
oliver 9b02a488bf works 2026-10-13 17:42:02 -03:00
oliver 9abed9f2e9 filter hosts 2026-10-13 13:24:58 -03:00
oliver ac65ba2169 working 2026-10-13 13:19:55 -03:00
oliver 796047a39f d 2026-10-13 09:47:24 -03:00
oliver 250280a2c7 update 2026-10-13 09:23:36 -03:00
oliver ed4fa767b0 u 2026-10-13 09:04:22 -03:00
oliver df86400eb5 o 2026-10-13 09:04:10 -03:00
oliver e03bb926e8 g 2026-10-13 08:58:02 -03:00
oliver ef641e9fe2 hmm 2026-10-13 07:28:31 -03:00
oliver 01f4ae8eea Fix in Backup 005 and host routing 2026-10-06 10:14:59 -03:00
oliver e8e6fccfc4 Added ODOO 20 and restart all+fix docker compose 2026-10-06 07:42:14 -03:00
oliver f64ecd6453 added new ip range, url (api), symlink for open 2026-10-06 06:46:59 -03:00
38 changed files with 733 additions and 122 deletions

No files matched your search

+2 -3
View File
@@ -23,6 +23,7 @@ RUN apk add --no-cache \
WORKDIR /root WORKDIR /root
RUN ln -sf /usr/bin/nvim /usr/bin/vim RUN ln -sf /usr/bin/nvim /usr/bin/vim
RUN ln -sf /4server/vault/open /usr/bin/open
COPY known_hosts /root/.ssh/ COPY known_hosts /root/.ssh/
COPY config /root/.ssh/ COPY config /root/.ssh/
@@ -39,7 +40,5 @@ COPY mount_volume /usr/bin/
COPY .bashrc /root/.bashrc COPY .bashrc /root/.bashrc
WORKDIR /app WORKDIR /app
RUN ln -s /app/vault/open /sbin/open
CMD ["bash"] CMD ["bash"]
+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=
+7 -2
View File
@@ -2,7 +2,12 @@
cmd="$*" cmd="$*"
echo "Running on hosts: $cmd" # Build a temporary hosts file without comments (# ...) and blank lines
tmp_hosts=$(mktemp)
trap 'rm -f "$tmp_hosts"' EXIT
sed -e 's/#.*$//' -e 's/[[:space:]]*$//' -e '/^[[:space:]]*$/d' /app/host_vars/hosts > "$tmp_hosts"
pssh -h /app/host_vars/hosts -t 0 "$cmd" echo "Running on hosts: $(tr '\n' ' ' < "$tmp_hosts")"
echo "Command: $cmd"
pssh -h "$tmp_hosts" -t 0 "$cmd"
+12 -5
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")
@@ -15,8 +15,18 @@ localfile="$1"
remotefile="$2" remotefile="$2"
remotetmp_base="/var/tmp/4server" remotetmp_base="/var/tmp/4server"
# If the destination is a directory (trailing slash), keep the local file name
case "$remotefile" in
*/) remotefile="${remotefile}$(basename "$localfile")" ;;
esac
# Read hosts from file descriptor 3 to prevent ssh from consuming stdin # Read hosts from file descriptor 3 to prevent ssh from consuming stdin
while read -r host <&3; do while read -r host <&3 || [ -n "$host" ]; do
# Strip comments (# ...) and surrounding whitespace; skip empty lines
host="${host%%#*}"
host="${host//[[:space:]]/}"
[ -z "$host" ] && continue
host_env_file="$host_vars_dir/$host/$host.env" host_env_file="$host_vars_dir/$host/$host.env"
if [ ! -f "$host_env_file" ]; then if [ ! -f "$host_env_file" ]; then
@@ -34,7 +44,6 @@ while read -r host <&3; do
SSH_PRIVATE=$(<"$host_vars_dir/$host/$host") SSH_PRIVATE=$(<"$host_vars_dir/$host/$host")
SSH_PUBLIC=$(<"$host_vars_dir/$host/$host.pub") SSH_PUBLIC=$(<"$host_vars_dir/$host/$host.pub")
content=$(<"$localfile") content=$(<"$localfile")
for key in "${keys[@]}"; do for key in "${keys[@]}"; do
@@ -42,7 +51,6 @@ while read -r host <&3; do
# Replace placeholder {{KEY}} with value using Bash's parameter expansion # Replace placeholder {{KEY}} with value using Bash's parameter expansion
content="${content//\{\{$key\}\}/$value}" content="${content//\{\{$key\}\}/$value}"
done done
# Copy content to remote temporary file # Copy content to remote temporary file
remotetmp="${remotetmp_base}_${host}" remotetmp="${remotetmp_base}_${host}"
echo "Copying to $host:$remotefile" echo "Copying to $host:$remotefile"
@@ -52,4 +60,3 @@ while read -r host <&3; do
ssh "$host" "doas mv '$remotetmp' '$remotefile'" ssh "$host" "doas mv '$remotetmp' '$remotefile'"
done 3< /app/host_vars/hosts done 3< /app/host_vars/hosts
+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
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
rex doas docker system prune -a -f
+59 -7
View File
@@ -2,16 +2,15 @@
dump_config (){ dump_config (){
echo "========== Odoo Container Configuration ==========" echo "$(date '+%Y-%m-%d %H:%M:%S') ========== Odoo Container Configuration =========="
echo "UUID: $UUID" echo "UUID: $UUID"
echo "BRANCH: $BRANCH" echo "BRANCH: $BRANCH"
echo "STAGING: $STAGING" echo "STAGING: $STAGING"
echo echo
echo "PostgreSQL Host: $POSTGRES_HOST" echo "PostgreSQL Host: $POSTGRES_HOST"
echo "PostgreSQL Port: $POSTGRES_PORT" echo "PostgreSQL Port: $POSTGRES_PORT"
echo "PostgreSQL Admin: $POSTGRES_ADMIN_USER / $POSTGRES_ADMIN_PASSWORD" echo "PostgreSQL Admin: $POSTGRES_ADMIN_USER"
echo "ODOO DB User: $ODOO_DB_USER" echo "ODOO DB User: $ODOO_DB_USER"
echo "ODOO DB Password: $ODOO_DB_PASSWORD"
echo echo
echo "BASEURL: $BASEURL" echo "BASEURL: $BASEURL"
echo "DATA_DIR: $DATA_DIR" echo "DATA_DIR: $DATA_DIR"
@@ -38,26 +37,79 @@ echo "=================================================="
# Function: Create PostgreSQL user # Function: Create PostgreSQL user
# ----------------------------- # -----------------------------
# Run a query as postgres admin. Returns psql's exit status so callers can tell
# "query returned nothing" apart from "could not connect".
pg_admin() {
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -tAc "$1"
}
# Wait until Postgres accepts connections (default: up to 60s).
wait_for_postgres() {
local tries="${1:-30}"
local i
for ((i = 1; i <= tries; i++)); do
if pg_admin "SELECT 1;" >/dev/null 2>&1; then
return 0
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') Postgres at $POSTGRES_HOST:$POSTGRES_PORT not reachable yet ($i/$tries), retrying in 2s"
sleep 2
done
return 1
}
check_and_create_db() { check_and_create_db() {
echo "check and create" echo "$(date '+%Y-%m-%d %H:%M:%S') Checking user and database for $UUID (DB user $ODOO_DB_USER) as $POSTGRES_ADMIN_USER on $POSTGRES_HOST:$POSTGRES_PORT"
echo "Connecting as $POSTGRES_ADMIN_USER to $POSTGRES_HOST:$POSTGRES_PORT"
# -----------------------------
# Never guess: if Postgres cannot be queried we must NOT assume the DB is
# missing, because a restore wipes the filestore.
# -----------------------------
if ! wait_for_postgres; then
echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: cannot connect to Postgres at $POSTGRES_HOST:$POSTGRES_PORT - skipping DB check, NO restore performed"
return 1
fi
# ----------------------------- # -----------------------------
# Check if user exists # Check if user exists
# ----------------------------- # -----------------------------
USER_EXISTS=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='$ODOO_DB_USER';" | grep -q 1 && echo "yes" || echo "no") USER_EXISTS=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='$ODOO_DB_USER';" | grep -q 1 && echo "yes" || echo "no")
if [ "$USER_EXISTS" = "no" ]; then if [ "$USER_EXISTS" = "no" ]; then
echo "Creating PostgreSQL user $ODOO_DB_USER..." echo "$(date '+%Y-%m-%d %H:%M:%S') PostgreSQL user $ODOO_DB_USER does not exist, creating it"
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -c "CREATE USER \"$ODOO_DB_USER\" WITH PASSWORD '$ODOO_DB_PASSWORD';" PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -c "CREATE USER \"$ODOO_DB_USER\" WITH PASSWORD '$ODOO_DB_PASSWORD';"
echo "$(date '+%Y-%m-%d %H:%M:%S') CREATE USER $ODOO_DB_USER finished with exit code $?"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') PostgreSQL user $ODOO_DB_USER already exists"
fi fi
# ----------------------------- # -----------------------------
# Check if database exists # Check if database exists
# ----------------------------- # -----------------------------
DB_EXISTS=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$UUID';" | grep -q 1 && echo "yes" || echo "no") DB_RESULT=$(pg_admin "SELECT 1 FROM pg_database WHERE datname='$UUID';")
DB_RC=$?
if [ $DB_RC -ne 0 ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: existence query for database $UUID failed (rc=$DB_RC) - NO restore performed"
return 1
fi
if [ "$DB_RESULT" = "1" ]; then
DB_EXISTS="yes"
else
DB_EXISTS="no"
fi
if [ "$DB_EXISTS" = "no" ]; then if [ "$DB_EXISTS" = "no" ]; then
FILESTORE_HOST="/4server/data/$UUID/odoo/odoo-web-data/filestore"
if [ -d "$FILESTORE_HOST" ] && [ -n "$(ls -A "$FILESTORE_HOST" 2>/dev/null)" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: database $UUID not found but a filestore exists at $FILESTORE_HOST - refusing automatic restore of default.zip (would delete the filestore). Restore manually if intended."
return 1
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') Database $UUID does not exist and no filestore found, restoring default.zip"
/4server/sbin/ODOO_19/restore $UUID default.zip /4server/sbin/ODOO_19/restore $UUID default.zip
local restore_rc=$?
echo "$(date '+%Y-%m-%d %H:%M:%S') Restore of default.zip for $UUID finished with exit code $restore_rc"
return $restore_rc
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Database $UUID already exists, nothing to do"
fi fi
} }
+35 -2
View File
@@ -35,6 +35,14 @@ else
fi fi
source /4server/sbin/ODOO_19/ODOO_19.lib
# Abort BEFORE touching anything if Postgres is not reachable
if ! wait_for_postgres; then
echo "ERROR: cannot connect to Postgres at $POSTGRES_HOST:$POSTGRES_PORT - restore aborted, nothing was changed"
exit 1
fi
### DELETE AND CREATE DATABASE ### DELETE AND CREATE DATABASE
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -d postgres -c " PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -d postgres -c "
SELECT pg_terminate_backend(pid) FROM pg_stat_activity SELECT pg_terminate_backend(pid) FROM pg_stat_activity
@@ -49,10 +57,35 @@ PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-c "ALTER ROLE \"$UUID\" CREATEDB;" -c "ALTER ROLE \"$UUID\" CREATEDB;"
doas docker exec "$UUID" rm -rf /home/odoo/.local/share/Odoo/filestore # Move the old filestore aside instead of deleting it; it is only removed after a successful load
OLD_FS="/home/odoo/.local/share/Odoo/.filestore.pre-restore"
doas docker exec "$UUID" rm -rf "$OLD_FS"
doas docker exec "$UUID" sh -c "[ -d /home/odoo/.local/share/Odoo/filestore ] && mv /home/odoo/.local/share/Odoo/filestore $OLD_FS; true"
doas docker exec "$UUID" rm -rf /root/.local/share/Odoo/filestore doas docker exec "$UUID" rm -rf /root/.local/share/Odoo/filestore
doas docker exec "$UUID" odoo db --db_host beedb -w "$ODOO_DB_PASSWORD" -r "$UUID" load "$UUID" $FILENAME -f 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"
# 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"
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
-c "ALTER ROLE \"$UUID\" NOCREATEDB;"
exit 1
fi
doas docker exec "$UUID" rm -rf "$OLD_FS"
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 \
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# List containers whose contract has not expired yet, next to expire first.
# Output: UUID, email, expires (header + aligned columns).
DB_PATH="/4server/data/contracts.db"
if [ ! -f "$DB_PATH" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: database not found at $DB_PATH" >&2
exit 1
fi
sqlite3 -header -column "$DB_PATH" "
SELECT UUID AS uuid, email, expires
FROM containers
WHERE expires > date('now')
ORDER BY expires ASC;
"
+74 -20
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()
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])}
@@ -551,13 +587,31 @@ def backup_move(request: MoveRequest):
class BackupCheckRequest(BaseModel): class BackupCheckRequest(BaseModel):
uuid: str uuid: Optional[str] = None
@app.post("/backup/check", dependencies=[Depends(verify_api_key)]) @app.post("/backup/check", dependencies=[Depends(verify_api_key)])
def backup_check(request: BackupCheckRequest): def backup_check(request: BackupCheckRequest):
if request.uuid:
return {"message": run_command([f"{BIN_PATH}/checkBackup", request.uuid])} return {"message": run_command([f"{BIN_PATH}/checkBackup", request.uuid])}
# No uuid: check all active containers. checkBackup -a exits 1 when any
# backup is NOK, but its table is still the valid result, so don't raise.
result = subprocess.run(
[f"{BIN_PATH}/checkBackup", "-a"], capture_output=True, text=True
)
if result.returncode != 0 and not result.stdout.strip():
raise HTTPException(
status_code=500,
detail=f"checkBackup -a failed: {result.stderr.strip() or 'no output'}",
)
return {"message": result.stdout}
@app.get("/containers/active", dependencies=[Depends(verify_api_key)])
def containers_active():
return {"message": run_command([f"{BIN_PATH}/activeContainers"])}
@app.get("/backup/all", dependencies=[Depends(verify_api_key)]) @app.get("/backup/all", dependencies=[Depends(verify_api_key)])
def backup_all(): def backup_all():
+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")
+3
View File
@@ -34,6 +34,9 @@ case "$SECOND_PART" in
004) 004)
"$BIN_PATH/backup/ODOO" "$BIN_PATH/backup/ODOO"
;; ;;
005)
"$BIN_PATH/backup/ODOO"
;;
*) *)
echo "Unknown UUID type: $SECOND_PART" echo "Unknown UUID type: $SECOND_PART"
+80 -16
View File
@@ -1,44 +1,108 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Check the latest backup date.
Usage:
checkBackup <uuid> print latest backup date for one container
checkBackup -a check all containers returned by activeContainers;
status is OK if the latest backup is at most 2 days
old, NOK if older or missing
"""
import subprocess
import sys import sys
from datetime import date, datetime
from pathlib import Path from pathlib import Path
BACKUP_ROOT = Path("/BACKUP") BACKUP_ROOT = Path("/BACKUP")
MAX_AGE_DAYS = 2
ACTIVE_CONTAINERS = Path(__file__).resolve().parent / "activeContainers"
if len(sys.argv) < 2:
print("Usage: checkBackup <uuid>")
sys.exit(1)
uuid = sys.argv[1] def check_backup(uuid):
"""Return (ok, message) for the given UUID."""
directory = BACKUP_ROOT / uuid directory = BACKUP_ROOT / uuid
if not directory.is_dir(): if not directory.is_dir():
print(f"ERROR: directory not found for UUID {uuid}") return False, f"ERROR: directory not found for UUID {uuid}"
sys.exit(1)
latest_date = None latest_date = None
latest_file = None latest_file = None
for zip_file in directory.rglob("*.zip"): for zip_file in directory.rglob("*.zip"):
name = zip_file.name date_part = zip_file.name.split("_")[0]
try:
date_part = name.split("_")[0]
if len(date_part) != 8 or not date_part.isdigit(): if len(date_part) != 8 or not date_part.isdigit():
continue continue
except Exception:
continue
if latest_date is None or date_part > latest_date: if latest_date is None or date_part > latest_date:
latest_date = date_part latest_date = date_part
latest_file = zip_file latest_file = zip_file
if latest_date is None: if latest_date is None:
print("NO BACKUPS") return False, "NO BACKUPS"
sys.exit(1)
if latest_file.stat().st_size < 1000: if latest_file.stat().st_size < 1000:
print("01-01-2000") return True, "01-01-2000"
sys.exit(0)
# Format as yyyy-mm-dd # Format as yyyy-mm-dd
print(f"{latest_date[:4]}-{latest_date[4:6]}-{latest_date[6:8]}") return True, f"{latest_date[:4]}-{latest_date[4:6]}-{latest_date[6:8]}"
def is_recent(ok, message):
"""True if the backup date in message is at most MAX_AGE_DAYS old."""
if not ok:
return False
try:
backup_date = datetime.strptime(message, "%Y-%m-%d").date()
except ValueError:
# e.g. the "01-01-2000" placeholder for too-small backup files
return False
return (date.today() - backup_date).days <= MAX_AGE_DAYS
def active_uuids():
"""UUIDs printed by activeContainers (skips header and separator lines)."""
result = subprocess.run(
[str(ACTIVE_CONTAINERS)], capture_output=True, text=True
)
if result.returncode != 0:
print(
f"ERROR: activeContainers failed: {result.stderr.strip()}",
file=sys.stderr,
)
sys.exit(1)
lines = result.stdout.splitlines()
uuids = []
for line in lines[2:]: # line 0 = header, line 1 = "----" separator
fields = line.split()
if fields:
uuids.append(fields[0])
return uuids
def main():
if len(sys.argv) < 2:
print("Usage: checkBackup <uuid> | -a")
sys.exit(1)
if sys.argv[1] == "-a":
rows = [("uuid", "latest", "status")]
failed = False
for uuid in active_uuids():
ok, message = check_backup(uuid)
recent = is_recent(ok, message)
rows.append((uuid, message, "OK" if recent else "NOK"))
failed = failed or not recent
widths = [max(len(row[i]) for row in rows) for i in range(2)]
for uuid, message, status in rows:
print(f"{uuid:<{widths[0]}} {message:<{widths[1]}} {status}")
sys.exit(1 if failed else 0)
ok, message = check_backup(sys.argv[1])
print(message)
sys.exit(0 if ok else 1)
if __name__ == "__main__":
main()
+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"
+10 -1
View File
@@ -10,7 +10,7 @@ DB_PATH="/4server/data/contracts.db"
get_contract_info() { get_contract_info() {
echo "get_contract_info $UUID" echo "$(date '+%Y-%m-%d %H:%M:%S') Loading contract info for $UUID from $DB_PATH"
# Single CTE: the table is scanned once and all projections come from it # Single CTE: the table is scanned once and all projections come from it
while IFS="=" read -r key value; do while IFS="=" read -r key value; do
@@ -48,4 +48,13 @@ get_contract_info() {
done < <(printf '%s' "$SECRET_LIST" | tr -d '\r\n' | tr '\\' '\n') done < <(printf '%s' "$SECRET_LIST" | tr -d '\r\n' | tr '\\' '\n')
fi fi
export ODOO_DB_PASSWORD=$psql export ODOO_DB_PASSWORD=$psql
if [ -z "$CONTAINERDBID" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') WARNING: no contract found for $UUID in $DB_PATH"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Loaded contract for $UUID: DBID=$CONTAINERDBID IMAGE=$IMAGE STATUS=$STATUS CREATED=$CREATED EXPIRES=$EXPIRES"
fi
if [ -z "$ODOO_DB_PASSWORD" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') WARNING: no psql secret found for $UUID, ODOO_DB_PASSWORD is empty"
fi
} }
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -e
DB_PATH="/4server/data/contracts.db"
COMPOSE_FILE="/4server/docker-compose.yml"
START_CONTAINER="/4server/sbin/startContainer"
echo "$(date '+%Y-%m-%d %H:%M:%S') Stopping and removing all Docker containers..."
# Remove all containers, including running containers
containers=$(doas docker ps -a -q)
if [ -n "$containers" ]; then
doas docker rm -f $containers
else
echo "No containers to remove."
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') Starting infrastructure..."
# Start infrastructure
doas docker compose -f "$COMPOSE_FILE" up -d
echo "$(date '+%Y-%m-%d %H:%M:%S') Infrastructure started."
# Get all non-expired container UUIDs
echo "$(date '+%Y-%m-%d %H:%M:%S') Loading active containers from database..."
uuids=$(sqlite3 "$DB_PATH" "
SELECT UUID
FROM containers
WHERE expires > date('now');
")
# Start each active container
for uuid in $uuids; do
echo "$(date '+%Y-%m-%d %H:%M:%S') Starting $uuid"
"$START_CONTAINER" "$uuid"
done
echo "$(date '+%Y-%m-%d %H:%M:%S') Restart complete."
+1 -1
View File
@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
cd /4server
# Check that a search string was provided # Check that a search string was provided
if [ -z "$1" ]; then if [ -z "$1" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Usage: $0 <search_string>" echo "$(date '+%Y-%m-%d %H:%M:%S') Usage: $0 <search_string>"
+14 -11
View File
@@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
echo "START ODOO 17"
# Load functions # Load functions
source /4server/sbin/ODOO_19/ODOO_19.lib source /4server/sbin/ODOO_19/ODOO_19.lib
source /4server/sbin/helpers source /4server/sbin/helpers
@@ -12,9 +11,7 @@ BRANCH="${BRANCH:-release}"
ODOO_DB_USER="${UUID}" ODOO_DB_USER="${UUID}"
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') #export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
echo "$(date '+%Y-%m-%d %H:%M:%S') START ODOO 17 UUID=$UUID BRANCH=$BRANCH DBID=$CONTAINERDBID DB_USER=$ODOO_DB_USER HDD=$HDD DOMAIN_SLOTS=$DOMAIN_SLOTS BACKUP_SLOTS=$BACKUP_SLOTS WORKER=$WORKER"
echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
echo "DBID: $CONTAINERDBID"
BASEURL="${BASEURL:-/4server/data/$UUID}" BASEURL="${BASEURL:-/4server/data/$UUID}"
DATA_DIR="$BASEURL/odoo/" DATA_DIR="$BASEURL/odoo/"
@@ -31,32 +28,33 @@ 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 "[DEBUG] No additional domain file found at $BASEURL/etc/domain" 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=""
for d in "${LABEL_DOMAINS[@]}"; do for d in "${LABEL_DOMAINS[@]}"; do
RESOLVED_IP=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true) RESOLVED_IP=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true)
if [ -z "$RESOLVED_IP" ]; then if [ -z "$RESOLVED_IP" ]; then
echo "[DEBUG] Could not resolve $d, skipping" echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d does not resolve, skipping"
continue continue
fi fi
if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then
RULE+=" || Host(\`$d\`)" RULE+=" || Host(\`$d\`)"
echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d resolves to $RESOLVED_IP (server IP), added to Traefik rule"
else else
echo "[DEBUG] Skipping $d (does not match SERVER_IP $SERVER_IP)" echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d resolves to $RESOLVED_IP but server IP is $SERVER_IP, skipping"
fi fi
done done
RULE="${RULE# || }" RULE="${RULE# || }"
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE" DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE"
echo "[DEBUG] Final Traefik label: $DOMAIN_LABEL" echo "$(date '+%Y-%m-%d %H:%M:%S') Traefik label for $UUID: $DOMAIN_LABEL"
docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo/ docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo/
docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore
@@ -64,9 +62,7 @@ docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.
find "$BASEURL" -type d -exec chmod 777 {} \; find "$BASEURL" -type d -exec chmod 777 {} \;
PORT=$((CONTAINERDBID + 2200)) PORT=$((CONTAINERDBID + 2200))
echo "PORT $PORT"
mkdir -p ${ETC_DIR} mkdir -p ${ETC_DIR}
echo "GITPATH ${ETC_DIR}gitpath"
touch ${ETC_DIR}gitpath touch ${ETC_DIR}gitpath
mkdir -p ${GIT_DIR}keys mkdir -p ${GIT_DIR}keys
@@ -74,13 +70,16 @@ 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}.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') Stopping and removing existing container $UUID (if any)"
docker stop "$UUID" 2>/dev/null docker stop "$UUID" 2>/dev/null
docker rm "$UUID" 2>/dev/null docker rm "$UUID" 2>/dev/null
EXTRA_DOCKER_PARAMETER="" EXTRA_DOCKER_PARAMETER=""
echo "$(date '+%Y-%m-%d %H:%M:%S') Starting container $UUID from docker.odoo4projects.com/4projects/odoo_17:$BRANCH (SSH port $PORT, network 4server_4projects)"
docker run -d --name "$UUID" \ docker run -d --name "$UUID" \
--network 4server_4projects \ --network 4server_4projects \
--restart=always \ --restart=always \
@@ -112,7 +111,9 @@ docker run -d --name "$UUID" \
--label "traefik.http.routers.$UUID.tls=true" \ --label "traefik.http.routers.$UUID.tls=true" \
--label "traefik.http.routers.$UUID.service=$UUID" \ --label "traefik.http.routers.$UUID.service=$UUID" \
docker.odoo4projects.com/4projects/odoo_17:$BRANCH docker.odoo4projects.com/4projects/odoo_17:$BRANCH
echo "$(date '+%Y-%m-%d %H:%M:%S') docker run for $UUID finished with exit code $?"
echo "$(date '+%Y-%m-%d %H:%M:%S') Preparing filestore and fixing ownership in container $UUID"
docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo
docker exec "$UUID" rm -rf /var/lib/odoo/.local/share/Odoo/filestore docker exec "$UUID" rm -rf /var/lib/odoo/.local/share/Odoo/filestore
docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore
@@ -124,4 +125,6 @@ docker exec $UUID chown -R odoo:odoo /mnt/*
docker exec $UUID chown odoo:odoo /git-server/keys/id_rsa.pub docker exec $UUID chown odoo:odoo /git-server/keys/id_rsa.pub
echo "$(date '+%Y-%m-%d %H:%M:%S') Checking/creating database for $UUID (user $ODOO_DB_USER)"
check_and_create_db check_and_create_db
echo "$(date '+%Y-%m-%d %H:%M:%S') START ODOO 17 finished for $UUID"
+14 -10
View File
@@ -12,9 +12,7 @@ BRANCH="${BRANCH:-release}"
ODOO_DB_USER="${UUID}" ODOO_DB_USER="${UUID}"
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') #export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
echo "$(date '+%Y-%m-%d %H:%M:%S') START ODOO 18 UUID=$UUID BRANCH=$BRANCH DBID=$CONTAINERDBID DB_USER=$ODOO_DB_USER HDD=$HDD DOMAIN_SLOTS=$DOMAIN_SLOTS BACKUP_SLOTS=$BACKUP_SLOTS WORKER=$WORKER"
echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
echo "DBID: $CONTAINERDBID"
BASEURL="${BASEURL:-/4server/data/$UUID}" BASEURL="${BASEURL:-/4server/data/$UUID}"
DATA_DIR="$BASEURL/odoo/" DATA_DIR="$BASEURL/odoo/"
@@ -31,32 +29,33 @@ 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 "[DEBUG] No additional domain file found at $BASEURL/etc/domain" 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=""
for d in "${LABEL_DOMAINS[@]}"; do for d in "${LABEL_DOMAINS[@]}"; do
RESOLVED_IP=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true) RESOLVED_IP=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true)
if [ -z "$RESOLVED_IP" ]; then if [ -z "$RESOLVED_IP" ]; then
echo "[DEBUG] Could not resolve $d, skipping" echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d does not resolve, skipping"
continue continue
fi fi
if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then
RULE+=" || Host(\`$d\`)" RULE+=" || Host(\`$d\`)"
echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d resolves to $RESOLVED_IP (server IP), added to Traefik rule"
else else
echo "[DEBUG] Skipping $d (does not match SERVER_IP $SERVER_IP)" echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d resolves to $RESOLVED_IP but server IP is $SERVER_IP, skipping"
fi fi
done done
RULE="${RULE# || }" RULE="${RULE# || }"
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE" DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE"
echo "[DEBUG] Final Traefik label: $DOMAIN_LABEL" echo "$(date '+%Y-%m-%d %H:%M:%S') Traefik label for $UUID: $DOMAIN_LABEL"
docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo/ docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo/
docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore
@@ -64,9 +63,7 @@ docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.
find "$BASEURL" -type d -exec chmod 777 {} \; find "$BASEURL" -type d -exec chmod 777 {} \;
PORT=$((CONTAINERDBID + 2200)) PORT=$((CONTAINERDBID + 2200))
echo "PORT $PORT"
mkdir -p ${ETC_DIR} mkdir -p ${ETC_DIR}
echo "GITPATH ${ETC_DIR}gitpath"
touch ${ETC_DIR}gitpath touch ${ETC_DIR}gitpath
mkdir -p ${GIT_DIR}keys mkdir -p ${GIT_DIR}keys
@@ -74,13 +71,16 @@ 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}.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') Stopping and removing existing container $UUID (if any)"
docker stop "$UUID" 2>/dev/null docker stop "$UUID" 2>/dev/null
docker rm "$UUID" 2>/dev/null docker rm "$UUID" 2>/dev/null
EXTRA_DOCKER_PARAMETER="" EXTRA_DOCKER_PARAMETER=""
echo "$(date '+%Y-%m-%d %H:%M:%S') Starting container $UUID from docker.odoo4projects.com/4projects/odoo_18:$BRANCH (SSH port $PORT, network 4server_4projects)"
docker run -d --name "$UUID" \ docker run -d --name "$UUID" \
--network 4server_4projects \ --network 4server_4projects \
--restart=always \ --restart=always \
@@ -112,7 +112,9 @@ docker run -d --name "$UUID" \
--label "traefik.http.routers.$UUID.tls=true" \ --label "traefik.http.routers.$UUID.tls=true" \
--label "traefik.http.routers.$UUID.service=$UUID" \ --label "traefik.http.routers.$UUID.service=$UUID" \
docker.odoo4projects.com/4projects/odoo_18:$BRANCH docker.odoo4projects.com/4projects/odoo_18:$BRANCH
echo "$(date '+%Y-%m-%d %H:%M:%S') docker run for $UUID finished with exit code $?"
echo "$(date '+%Y-%m-%d %H:%M:%S') Preparing filestore and fixing ownership in container $UUID"
docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo
docker exec "$UUID" rm -rf /var/lib/odoo/.local/share/Odoo/filestore docker exec "$UUID" rm -rf /var/lib/odoo/.local/share/Odoo/filestore
docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore
@@ -124,4 +126,6 @@ docker exec $UUID chown -R odoo:odoo /mnt/*
docker exec $UUID chown odoo:odoo /git-server/keys/id_rsa.pub docker exec $UUID chown odoo:odoo /git-server/keys/id_rsa.pub
echo "$(date '+%Y-%m-%d %H:%M:%S') Checking/creating database for $UUID (user $ODOO_DB_USER)"
check_and_create_db check_and_create_db
echo "$(date '+%Y-%m-%d %H:%M:%S') START ODOO 18 finished for $UUID"
+15 -11
View File
@@ -11,10 +11,8 @@ BRANCH="${BRANCH:-release}"
ODOO_DB_USER="${UUID}" ODOO_DB_USER="${UUID}"
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql') #export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
echo "***** $ODOO_DB_PASSWORD"
echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID" echo "$(date '+%Y-%m-%d %H:%M:%S') START ODOO 19 UUID=$UUID BRANCH=$BRANCH DBID=$CONTAINERDBID DB_USER=$ODOO_DB_USER HDD=$HDD DOMAIN_SLOTS=$DOMAIN_SLOTS BACKUP_SLOTS=$BACKUP_SLOTS WORKER=$WORKER"
echo "DBID: $CONTAINERDBID"
BASEURL="${BASEURL:-/4server/data/$UUID}" BASEURL="${BASEURL:-/4server/data/$UUID}"
DATA_DIR="$BASEURL/odoo/" DATA_DIR="$BASEURL/odoo/"
@@ -31,41 +29,40 @@ 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 "[DEBUG] No additional domain file found at $BASEURL/etc/domain" 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=""
for d in "${LABEL_DOMAINS[@]}"; do for d in "${LABEL_DOMAINS[@]}"; do
RESOLVED_IP=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true) RESOLVED_IP=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true)
if [ -z "$RESOLVED_IP" ]; then if [ -z "$RESOLVED_IP" ]; then
echo "[DEBUG] Could not resolve $d, skipping" echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d does not resolve, skipping"
continue continue
fi fi
if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then
RULE+=" || Host(\`$d\`)" RULE+=" || Host(\`$d\`)"
echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d resolves to $RESOLVED_IP (server IP), added to Traefik rule"
else else
echo "[DEBUG] Skipping $d (does not match SERVER_IP $SERVER_IP)" echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d resolves to $RESOLVED_IP but server IP is $SERVER_IP, skipping"
fi fi
done done
RULE="${RULE# || }" RULE="${RULE# || }"
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE" DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE"
echo "[DEBUG] Final Traefik label: $DOMAIN_LABEL" echo "$(date '+%Y-%m-%d %H:%M:%S') Traefik label for $UUID: $DOMAIN_LABEL"
find "$BASEURL" -type d -exec chmod 777 {} \; find "$BASEURL" -type d -exec chmod 777 {} \;
PORT=$((CONTAINERDBID + 2200)) PORT=$((CONTAINERDBID + 2200))
echo "PORT $PORT"
mkdir -p ${ETC_DIR} mkdir -p ${ETC_DIR}
chmod 777 ${ETC_DIR} chmod 777 ${ETC_DIR}
echo "GITPATH ${ETC_DIR}gitpath"
touch ${ETC_DIR}gitpath touch ${ETC_DIR}gitpath
mkdir -p ${BACKUP_DIR} mkdir -p ${BACKUP_DIR}
@@ -76,14 +73,17 @@ 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') Stopping and removing existing container $UUID (if any)"
docker stop "$UUID" 2>/dev/null docker stop "$UUID" 2>/dev/null
docker rm "$UUID" 2>/dev/null docker rm "$UUID" 2>/dev/null
EXTRA_DOCKER_PARAMETER="" EXTRA_DOCKER_PARAMETER=""
echo "$(date '+%Y-%m-%d %H:%M:%S') Starting container $UUID from docker.odoo4projects.com/4projects/odoo_19:$BRANCH (SSH port $PORT, network 4server_4projects)"
docker run -d --name "$UUID" \ docker run -d --name "$UUID" \
--network 4server_4projects \ --network 4server_4projects \
--restart=always \ --restart=always \
@@ -115,11 +115,15 @@ docker run -d --name "$UUID" \
--label "traefik.http.routers.$UUID.tls=true" \ --label "traefik.http.routers.$UUID.tls=true" \
--label "traefik.http.routers.$UUID.service=$UUID" \ --label "traefik.http.routers.$UUID.service=$UUID" \
docker.odoo4projects.com/4projects/odoo_19:$BRANCH docker.odoo4projects.com/4projects/odoo_19:$BRANCH
echo "$(date '+%Y-%m-%d %H:%M:%S') docker run for $UUID finished with exit code $?"
echo "$(date '+%Y-%m-%d %H:%M:%S') Fixing ownership in container $UUID"
docker exec $UUID chown -R odoo:odoo /home/odoo/.local docker exec $UUID chown -R odoo:odoo /home/odoo/.local
docker exec $UUID chown -R odoo:odoo /mnt/* docker exec $UUID chown -R odoo:odoo /mnt/*
docker exec $UUID chown -R git:git /git-server docker exec $UUID chown -R git:git /git-server
chmod 777 /4server/data/$UUID/cc chmod 777 /4server/data/$UUID/cc
echo "$(date '+%Y-%m-%d %H:%M:%S') Checking/creating database for $UUID (user $ODOO_DB_USER)"
check_and_create_db check_and_create_db
echo "$(date '+%Y-%m-%d %H:%M:%S') START ODOO 19 finished for $UUID"
+129
View File
@@ -0,0 +1,129 @@
#!/bin/bash
# Load functions
source /4server/sbin/ODOO_19/ODOO_19.lib
source /4server/sbin/helpers
get_contract_info
# Config variables
UUID="${UUID:-default}"
BRANCH="${BRANCH:-release}"
ODOO_DB_USER="${UUID}"
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
echo "$(date '+%Y-%m-%d %H:%M:%S') START ODOO 20 UUID=$UUID BRANCH=$BRANCH DBID=$CONTAINERDBID DB_USER=$ODOO_DB_USER HDD=$HDD DOMAIN_SLOTS=$DOMAIN_SLOTS BACKUP_SLOTS=$BACKUP_SLOTS WORKER=$WORKER"
BASEURL="${BASEURL:-/4server/data/$UUID}"
DATA_DIR="$BASEURL/odoo/"
CUSTOM_DIR="$BASEURL/git/$UUID/custom/"
ENTERPRISE_DIR="$BASEURL/git/$UUID/enterprise/"
LOGS_DIR="$BASEURL/logs/"
CONFIG_DIR="$BASEURL/config/"
CC_DIR="$BASEURL/cc/"
BACKUP_DIR="/BACKUP/$UUID"
GIT_DIR="$BASEURL/git-server/"
INSTALL_DIR="$BASEURL/install/"
SSH_DIR="$BASEURL/.ssh/"
ETC_DIR="$BASEURL/etc/"
SERVER_IP=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1)
LABEL_DOMAINS=("$UUID.$HOSTNAME.odoo4projects.com")
if [ -f "$BASEURL/etc/domain" ]; then
while IFS= read -r domain || [[ -n $domain ]]; do
[ -z "$domain" ] && continue
LABEL_DOMAINS+=("$domain")
done < "$BASEURL/etc/domain"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') No custom domain file at $BASEURL/etc/domain, using default domain $UUID.$HOSTNAME.odoo4projects.com only"
fi
RULE=""
for d in "${LABEL_DOMAINS[@]}"; do
RESOLVED_IP=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true)
if [ -z "$RESOLVED_IP" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d does not resolve, skipping"
continue
fi
if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then
RULE+=" || Host(\`$d\`)"
echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d resolves to $RESOLVED_IP (server IP), added to Traefik rule"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Domain $d resolves to $RESOLVED_IP but server IP is $SERVER_IP, skipping"
fi
done
RULE="${RULE# || }"
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE"
echo "$(date '+%Y-%m-%d %H:%M:%S') Traefik label for $UUID: $DOMAIN_LABEL"
find "$BASEURL" -type d -exec chmod 777 {} \;
PORT=$((CONTAINERDBID + 2200))
mkdir -p ${ETC_DIR}
chmod 777 ${ETC_DIR}
touch ${ETC_DIR}gitpath
mkdir -p ${BACKUP_DIR}
chmod 777 ${BACKUP_DIR}
mkdir -p ${GIT_DIR}keys
chmod 777 ${GIT_DIR}
touch ${GIT_DIR}keys/id_rsa.pub
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') Stopping and removing existing container $UUID (if any)"
docker stop "$UUID" 2>/dev/null
docker rm "$UUID" 2>/dev/null
EXTRA_DOCKER_PARAMETER=""
echo "$(date '+%Y-%m-%d %H:%M:%S') Starting container $UUID from docker.odoo4projects.com/4projects/odoo_20:$BRANCH (SSH port $PORT, network 4server_4projects)"
docker run -d --name "$UUID" \
--network 4server_4projects \
--restart=always \
$EXTRA_DOCKER_PARAMETER \
-v "$DATA_DIR/odoo-web-data:/home/odoo/.local/share/Odoo" \
-v "$CUSTOM_DIR:/mnt/addons/custom" \
-v "$ENTERPRISE_DIR:/mnt/addons/enterprise" \
-v "$LOGS_DIR:/mnt/logs" \
-v "$CC_DIR:/mnt/cc" \
-v "$BACKUP_DIR:/mnt/backup" \
-v "$GIT_DIR:/git-server" \
-v "$INSTALL_DIR:/mnt/install" \
-v "$SSH_DIR:/etc/sshkey" \
-v "$ETC_DIR:/mnt/etc" \
-p "$PORT:22" \
-e HOST="beedb" \
-e USER="$ODOO_DB_USER" \
-e PASSWORD="$ODOO_DB_PASSWORD" \
-e UUID="$UUID" \
-e HDD="$HDD" \
-e DOMAIN_SLOTS="$DOMAIN_SLOTS" \
-e BACKUP_SLOTS="$BACKUP_SLOTS" \
-e WORKER="$WORKER" \
-e GIT="$GIT" \
--label "$DOMAIN_LABEL" \
--label "traefik.http.services.$UUID.loadbalancer.server.port=8069" \
--label "traefik.http.routers.$UUID.entrypoints=web, websecure" \
--label "traefik.http.routers.$UUID.tls.certresolver=production" \
--label "traefik.http.routers.$UUID.tls=true" \
--label "traefik.http.routers.$UUID.service=$UUID" \
docker.odoo4projects.com/4projects/odoo_20:$BRANCH
echo "$(date '+%Y-%m-%d %H:%M:%S') docker run for $UUID finished with exit code $?"
echo "$(date '+%Y-%m-%d %H:%M:%S') Fixing ownership in container $UUID"
docker exec $UUID chown -R odoo:odoo /home/odoo/.local
docker exec $UUID chown -R odoo:odoo /mnt/*
docker exec $UUID chown -R git:git /git-server
chmod 777 /4server/data/$UUID/cc
echo "$(date '+%Y-%m-%d %H:%M:%S') Checking/creating database for $UUID (user $ODOO_DB_USER)"
check_and_create_db
echo "$(date '+%Y-%m-%d %H:%M:%S') START ODOO 20 finished for $UUID"
+6 -4
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
#--label "traefik.http.routers.${UUID}.middlewares=cors-headers@file" \ #--label "traefik.http.routers.${UUID}.middlewares=cors-headers@file" \
echo "Start N8N container ${UUID}"
# 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 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
mkdir -p /4server/data/${UUID}/backup mkdir -p /4server/data/${UUID}/backup
@@ -15,13 +16,14 @@ chmod 777 /4server/data/${UUID}/backup
# Stop the container if it exists # Stop the container if it exists
if docker ps -a --format '{{.Names}}' | grep -q "^${UUID}$"; then if docker ps -a --format '{{.Names}}' | grep -q "^${UUID}$"; then
echo "$(date '+%Y-%m-%d %H:%M') - stopping existing container $UUID" echo "$(date '+%Y-%m-%d %H:%M:%S') Stopping and removing existing container $UUID"
docker stop "$UUID" docker stop "$UUID"
docker rm "$UUID" docker rm "$UUID"
fi fi
echo "$(date '+%Y-%m-%d %H:%M:%S') Starting container $UUID from docker.odoo4projects.com/4projects/n8n:release (data in /4server/data/${UUID}, network 4server_4projects)"
docker run -d \ docker run -d \
--name "$UUID" \ --name "$UUID" \
-p 5678 \ -p 5678 \
@@ -51,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" \
@@ -59,4 +61,4 @@ docker run -d \
--network 4server_4projects \ --network 4server_4projects \
docker.odoo4projects.com/4projects/n8n:release docker.odoo4projects.com/4projects/n8n:release
echo "Started $1" echo "$(date '+%Y-%m-%d %H:%M:%S') docker run for $UUID finished with exit code $?"
+4 -2
View File
@@ -3,7 +3,7 @@
# Example: ./start_by_uuid.sh abc-001-xxxx-xxxx-xxxx # Example: ./start_by_uuid.sh abc-001-xxxx-xxxx-xxxx
exec > /4server/data/log/startContainer.log 2>&1 exec > /4server/data/log/startContainer.log 2>&1
echo "$(date '+%Y-%m-%d %H:%M:%S') Start container $1" echo "$(date '+%Y-%m-%d %H:%M:%S') Start container $1"
cd/4server
source /4server/sbin/helpers source /4server/sbin/helpers
BIN_PATH="/4server/sbin" BIN_PATH="/4server/sbin"
@@ -56,7 +56,9 @@ case "$SECOND_PART" in
"$BIN_PATH/start/ODOO_17" "$BIN_PATH/start/ODOO_17"
;; ;;
005)
"$BIN_PATH/start/ODOO_20"
;;
*) *)
echo "$(date '+%Y-%m-%d %H:%M:%S') Unknown UUID type: $SECOND_PART" echo "$(date '+%Y-%m-%d %H:%M:%S') Unknown UUID type: $SECOND_PART"
+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}}
+2 -3
View File
@@ -17,7 +17,7 @@ services:
ipv4_address: 10.5.0.200 ipv4_address: 10.5.0.200
traefik: traefik:
image: docker.io/library/traefik:3.6 image: docker.io/library/traefik:3.7
container_name: traefik container_name: traefik
ports: ports:
- 80:80 - 80:80
@@ -38,5 +38,4 @@ networks:
config: config:
- subnet: 10.5.0.0/16 - subnet: 10.5.0.0/16
gateway: 10.5.0.1 gateway: 10.5.0.1
ip_range: 10.5.0.0/26 ip_range: 10.5.16.0/20
+1
View File
@@ -1 +1,2 @@
permit nopass 4server as root permit nopass 4server as root
permit nopass root as root
+1 -1
View File
@@ -1,3 +1,3 @@
# Example: allow your host to connect to all DBs as any user with password # Example: allow your host to connect to all DBs as any user with password
host all all 10.5.0.1/32 md5 host all all 10.5.0.1/16 md5
+1 -1
View File
@@ -1,5 +1,5 @@
listen_addresses = '*' listen_addresses = '*'
max_connections = 150 max_connections = 300
dynamic_shared_memory_type = posix dynamic_shared_memory_type = posix
max_wal_size = 1GB max_wal_size = 1GB
min_wal_size = 80MB min_wal_size = 80MB
+2 -3
View File
@@ -1,3 +1,2 @@
https://dl-cdn.alpinelinux.org/alpine/v3.22/main https://dl-cdn.alpinelinux.org/alpine/v3.24/main
https://dl-cdn.alpinelinux.org/alpine/v3.22/community https://dl-cdn.alpinelinux.org/alpine/v3.24/community
+1 -3
View File
@@ -87,7 +87,7 @@ http:
routers: routers:
api-router: api-router:
rule: "Host(`{{HOSTNAME}}.odoo4projects.com`)" rule: "Host(`{{HOSTNAME}}.odoo4projects.com`) || Host(`api.{{HOSTNAME}}.odoo4projects.com`)"
service: api-service service: api-service
entryPoints: entryPoints:
- websecure - websecure
@@ -99,5 +99,3 @@ http:
loadBalancer: loadBalancer:
servers: servers:
- url: "http://10.5.0.1:8888" - url: "http://10.5.0.1:8888"
+3 -1
View File
@@ -35,7 +35,9 @@ prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/
### POSTGRESS ### POSTGRESS
rex mkdir -p /4server/data/postgres/etc rex mkdir -p /4server/data/postgres/etc
template templates/pq_hba.conf /4server/data/postgres/etc/ template templates/pg_hba.conf /4server/data/postgres/data/pg_hba.conf
template templates/postgresql.conf /4server/data/postgres/data/postgresql.conf
-4
View File
@@ -1,4 +0,0 @@
rex doas apk update
rex doas apk fix
rex doas apk upgrade
rex doas reboot
+7
View File
@@ -12,3 +12,10 @@ rex doas rc-service checkCalls restart
prsync -h "/app/host_vars/hosts" -avz ./host_vars/borg-backup/* /home/4server/.ssh prsync -h "/app/host_vars/hosts" -avz ./host_vars/borg-backup/* /home/4server/.ssh
rex doas chown 4server:4server /home/4server/.ssh/borg* rex doas chown 4server:4server /home/4server/.ssh/borg*
rex doas chmod 600 /home/4server/.ssh/borg* rex doas chmod 600 /home/4server/.ssh/borg*
# link all save scripts to /usr/sbin
rex doas ln -sf /4server/sbin/activeContainers /usr/bin/
rex doas ln -sf /4server/sbin/startContainer /usr/bin/
rex doas ln -sf /4server/sbin/restartContainers /usr/bin/
rex doas ln -sf /4server/sbin/checkBackup /usr/bin/
+9
View File
@@ -0,0 +1,9 @@
template templates/repositories /etc/apk/repositories
template templates/docker-compose.yml /4server/docker-compose.yml
rex doas apk update
rex doas apk fix
rex doas apk upgrade
rex doas docker compose -f /4server/docker-compose.yml pull
rex doas docker compose -f /4server/docker-compose.yml up -d
rex doas reboot
Binary file not shown.