Compare commits
3
Commits
main
..
66ddb386a8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66ddb386a8 | ||
|
|
9a59b2932a | ||
|
|
2e9c775599 |
No files matched your search
+4
-3
@@ -18,12 +18,11 @@ RUN apk add --no-cache \
|
|||||||
sshfs \
|
sshfs \
|
||||||
device-mapper \
|
device-mapper \
|
||||||
borgbackup
|
borgbackup
|
||||||
|
|
||||||
|
|
||||||
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/
|
||||||
@@ -40,5 +39,7 @@ 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
@@ -1,3 +1,8 @@
|
|||||||
|
#Host dev
|
||||||
|
# Hostname dev
|
||||||
|
# User oliver
|
||||||
|
# Port 2222
|
||||||
|
|
||||||
Host dev
|
Host dev
|
||||||
Hostname dev
|
Hostname dev
|
||||||
User 4server
|
User 4server
|
||||||
@@ -25,7 +30,7 @@ Host meppel
|
|||||||
IdentityFile /app/host_vars/meppel/meppel
|
IdentityFile /app/host_vars/meppel/meppel
|
||||||
|
|
||||||
Host sydney
|
Host sydney
|
||||||
Hostname 192.168.9.14
|
Hostname 192.168.9.22
|
||||||
User 4server
|
User 4server
|
||||||
IdentityFile /app/host_vars/sydney/sydney
|
IdentityFile /app/host_vars/sydney/sydney
|
||||||
|
|
||||||
@@ -33,8 +38,3 @@ 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
|
|
||||||
@@ -15,6 +15,3 @@
|
|||||||
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=
|
|
||||||
+2
-7
@@ -2,12 +2,7 @@
|
|||||||
|
|
||||||
cmd="$*"
|
cmd="$*"
|
||||||
|
|
||||||
# Build a temporary hosts file without comments (# ...) and blank lines
|
echo "Running on hosts: $cmd"
|
||||||
tmp_hosts=$(mktemp)
|
|
||||||
trap 'rm -f "$tmp_hosts"' EXIT
|
|
||||||
sed -e 's/#.*$//' -e 's/[[:space:]]*$//' -e '/^[[:space:]]*$/d' /app/host_vars/hosts > "$tmp_hosts"
|
|
||||||
|
|
||||||
echo "Running on hosts: $(tr '\n' ' ' < "$tmp_hosts")"
|
pssh -h /app/host_vars/hosts -t 0 "$cmd"
|
||||||
echo "Command: $cmd"
|
|
||||||
|
|
||||||
pssh -h "$tmp_hosts" -t 0 "$cmd"
|
|
||||||
+5
-12
@@ -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 PROVIDER COST CONTRACT_END )
|
keys=(NEBULA_CA API_KEY HOSTNAME NEBULA_CRT NEBULA_KEY SSH_PRIVATE SSH_PUBLIC)
|
||||||
|
|
||||||
NEBULA_CA=$(<"$host_vars_dir/ca.crt")
|
NEBULA_CA=$(<"$host_vars_dir/ca.crt")
|
||||||
|
|
||||||
@@ -15,18 +15,8 @@ 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 || [ -n "$host" ]; do
|
while read -r host <&3; 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
|
||||||
@@ -44,6 +34,7 @@ while read -r host <&3 || [ -n "$host" ]; 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
|
||||||
@@ -51,6 +42,7 @@ while read -r host <&3 || [ -n "$host" ]; 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"
|
||||||
@@ -60,3 +52,4 @@ while read -r host <&3 || [ -n "$host" ]; 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
|
||||||
|
|
||||||
+6
-5
@@ -6,11 +6,12 @@ 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
|
||||||
@@ -19,13 +20,13 @@ rex doas chmod 777 /4server
|
|||||||
# ----- install nabula
|
# ----- install nabula
|
||||||
echo "prsync nebula bin"
|
echo "prsync nebula bin"
|
||||||
prsync -h "/app/host_vars/hosts" -avz ./sbin/nebula /4server/nebula
|
prsync -h "/app/host_vars/hosts" -avz ./sbin/nebula /4server/nebula
|
||||||
rex doas mv /4server/nebula /usr/bin/
|
rex doas mv /4server/nebula /usr/bin/
|
||||||
|
|
||||||
rex doas mkdir -p /etc/nebula
|
rex doas mkdir -p /etc/nebula
|
||||||
rex doas chmod 700 /etc/nebula
|
rex doas chmod 700 /etc/nebula
|
||||||
|
|
||||||
template templates/nebula/config.yml /etc/nebula/config.yml
|
template templates/nebula/config.yml /etc/nebula/config.yml
|
||||||
template templates/nebula/host.key /etc/nebula/host.key
|
template templates/nebula/host.key /etc/nebula/host.key
|
||||||
template templates/nebula/host.crt /etc/nebula/host.crt
|
template templates/nebula/host.crt /etc/nebula/host.crt
|
||||||
template templates/nebula/ca.crt /etc/nebula/ca.crt
|
template templates/nebula/ca.crt /etc/nebula/ca.crt
|
||||||
|
|
||||||
|
|||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
"$(dirname "$(realpath "${BASH_SOURCE[0]:-$0}")")/listModules" "$1"
|
||||||
File renamed without changes.
@@ -17,7 +17,7 @@ RANDOM_FILE="/4server/tmp/import_$(date '+%Y%m%d_%H%M').zip"
|
|||||||
gdown "$1" -O "$RANDOM_FILE"
|
gdown "$1" -O "$RANDOM_FILE"
|
||||||
|
|
||||||
# Execute dbVersion on the downloaded file and capture output
|
# Execute dbVersion on the downloaded file and capture output
|
||||||
VERSION=$(/4server/sbin/ODOO_19/dbVersion "$RANDOM_FILE")
|
VERSION=$("$(dirname "$(realpath "${BASH_SOURCE[0]:-$0}")")/dbVersion" "$RANDOM_FILE")
|
||||||
|
|
||||||
# Output JSON to original stdout
|
# Output JSON to original stdout
|
||||||
cat <<EOF >&3
|
cat <<EOF >&3
|
||||||
@@ -29,4 +29,3 @@ EOF
|
|||||||
|
|
||||||
# Close saved stdout
|
# Close saved stdout
|
||||||
exec 3>&-
|
exec 3>&-
|
||||||
|
|
||||||
@@ -1,22 +1,19 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
export PATH=/4PROJECTS/bin:$PATH
|
export PATH=/4PROJECTS/bin:$PATH
|
||||||
if [ ! -n "$1" ]; then
|
CONTAINER_ID="$1"
|
||||||
echo "Missing Parameters <UUID>"
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of ODOO/
|
||||||
exit 0
|
source "$SBIN_ROOT/helpers"
|
||||||
fi
|
[ -z "$CONTAINER_ID" ] && die "Usage: $0 <container_id>"
|
||||||
|
load_container_config "$CONTAINER_ID"
|
||||||
UUID=$1
|
|
||||||
|
|
||||||
source /4server/sbin/helpers
|
|
||||||
|
|
||||||
get_contract_info
|
get_contract_info
|
||||||
|
DB_NAME="${DB_NAME:-$CONTAINER_ID}"
|
||||||
|
|
||||||
# Query installed modules from database and save CSV in variable
|
# Query installed modules from database and save CSV in variable
|
||||||
DB_MODULES_CSV=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" PAGER= psql \
|
DB_MODULES_CSV=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" PAGER= psql \
|
||||||
-h "$POSTGRES_HOST" \
|
-h "$POSTGRES_HOST" \
|
||||||
-U "$POSTGRES_ADMIN_USER" \
|
-U "$POSTGRES_ADMIN_USER" \
|
||||||
-p "$POSTGRES_PORT" \
|
-p "$POSTGRES_PORT" \
|
||||||
-d "$UUID" \
|
-d "$DB_NAME" \
|
||||||
--csv \
|
--csv \
|
||||||
-c "
|
-c "
|
||||||
SELECT
|
SELECT
|
||||||
@@ -30,26 +27,26 @@ ORDER BY name;
|
|||||||
")
|
")
|
||||||
|
|
||||||
# Check if container exists and is running
|
# Check if container exists and is running
|
||||||
if ! doas docker ps --format "{{.Names}}" | grep -q "^${UUID}$"; then
|
if ! doas docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_ID}$"; then
|
||||||
echo "Error: Container $UUID is not running" >&2
|
echo "Error: Container $CONTAINER_ID is not running" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get addons_path from odoo.conf in container (try multiple possible locations)
|
# Get addons_path from odoo.conf in container (try multiple possible locations)
|
||||||
ADDONS_PATH_RAW=""
|
ADDONS_PATH_RAW=""
|
||||||
for ODOO_CONF in "/etc/odoo.conf" "/etc/odoo/odoo.conf" "/opt/odoo/etc/odoo.conf"; do
|
for ODOO_CONF in "/etc/odoo.conf" "/etc/odoo/odoo.conf" "/opt/odoo/etc/odoo.conf"; do
|
||||||
ADDONS_PATH_RAW=$(doas docker exec "$UUID" grep "^addons_path" "$ODOO_CONF" 2>/dev/null | sed 's/.*=//' | xargs)
|
ADDONS_PATH_RAW=$(doas docker exec "$CONTAINER_ID" grep "^addons_path" "$ODOO_CONF" 2>/dev/null | sed 's/.*=//' | xargs)
|
||||||
[ -n "$ADDONS_PATH_RAW" ] && break
|
[ -n "$ADDONS_PATH_RAW" ] && break
|
||||||
done
|
done
|
||||||
|
|
||||||
# Also check standard Odoo library locations
|
# Also check standard Odoo library locations
|
||||||
ODOO_LIB_PATHS=""
|
ODOO_LIB_PATHS=""
|
||||||
# Check standard docker installation path
|
# Check standard docker installation path
|
||||||
if doas docker exec "$UUID" test -d "/lib/python3/dist-packages/odoo/addons" 2>/dev/null; then
|
if doas docker exec "$CONTAINER_ID" test -d "/lib/python3/dist-packages/odoo/addons" 2>/dev/null; then
|
||||||
ODOO_LIB_PATHS="/lib/python3/dist-packages/odoo/addons"
|
ODOO_LIB_PATHS="/lib/python3/dist-packages/odoo/addons"
|
||||||
fi
|
fi
|
||||||
# Also check other possible locations
|
# Also check other possible locations
|
||||||
for lib_path in $(doas docker exec "$UUID" find /usr/lib /usr/local/lib -maxdepth 3 -type d -path "*/python3*/site-packages/odoo/addons" 2>/dev/null); do
|
for lib_path in $(doas docker exec "$CONTAINER_ID" find /usr/lib /usr/local/lib -maxdepth 3 -type d -path "*/python3*/site-packages/odoo/addons" 2>/dev/null); do
|
||||||
ODOO_LIB_PATHS="$ODOO_LIB_PATHS $lib_path"
|
ODOO_LIB_PATHS="$ODOO_LIB_PATHS $lib_path"
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -67,7 +64,7 @@ if [ -n "$ADDONS_PATH_RAW" ]; then
|
|||||||
[ -z "$addons_dir" ] && continue
|
[ -z "$addons_dir" ] && continue
|
||||||
|
|
||||||
# Get all directories in this addons path
|
# Get all directories in this addons path
|
||||||
doas docker exec "$UUID" find "$addons_dir" -maxdepth 1 -type d 2>/dev/null | \
|
doas docker exec "$CONTAINER_ID" find "$addons_dir" -maxdepth 1 -type d 2>/dev/null | \
|
||||||
sed "s|^$addons_dir/||" | sed "s|^$addons_dir$||" | grep -v "^$" >> "$FILESYSTEM_MODULES"
|
sed "s|^$addons_dir/||" | sed "s|^$addons_dir$||" | grep -v "^$" >> "$FILESYSTEM_MODULES"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -79,7 +76,7 @@ if [ -n "$ODOO_LIB_PATHS" ]; then
|
|||||||
[ -z "$lib_path" ] && continue
|
[ -z "$lib_path" ] && continue
|
||||||
|
|
||||||
# Get all directories in this library path
|
# Get all directories in this library path
|
||||||
doas docker exec "$UUID" find "$lib_path" -maxdepth 1 -type d 2>/dev/null | \
|
doas docker exec "$CONTAINER_ID" find "$lib_path" -maxdepth 1 -type d 2>/dev/null | \
|
||||||
sed "s|^$lib_path/||" | sed "s|^$lib_path$||" | grep -v "^$" >> "$FILESYSTEM_MODULES"
|
sed "s|^$lib_path/||" | sed "s|^$lib_path$||" | grep -v "^$" >> "$FILESYSTEM_MODULES"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
export PATH=/4PROJECTS/bin:$PATH
|
||||||
|
CONTAINER_ID="$1"
|
||||||
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of ODOO/
|
||||||
|
source "$SBIN_ROOT/helpers"
|
||||||
|
[ -z "$CONTAINER_ID" ] && die "Usage: $0 <container_id>"
|
||||||
|
load_container_config "$CONTAINER_ID"
|
||||||
|
get_contract_info
|
||||||
|
DB_NAME="${DB_NAME:-$CONTAINER_ID}"
|
||||||
|
log "shell: CONTAINER_ID=$CONTAINER_ID DB_NAME=$DB_NAME"
|
||||||
|
doas docker exec -it "$CONTAINER_ID" odoo shell --db_host beedb --db_password="$ODOO_DB_PASSWORD" -d "$DB_NAME" --db_user="$DB_NAME"
|
||||||
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
export PATH=/4PROJECTS/bin:$PATH
|
||||||
|
|
||||||
|
# --------------------------------------------------
|
||||||
|
# Validate parameter
|
||||||
|
# --------------------------------------------------
|
||||||
|
CONTAINER_ID="$1"
|
||||||
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of ODOO/
|
||||||
|
source "$SBIN_ROOT/helpers"
|
||||||
|
[ -z "$CONTAINER_ID" ] && die "Usage: $0 <container_id>"
|
||||||
|
load_container_config "$CONTAINER_ID"
|
||||||
|
get_contract_info
|
||||||
|
DB_NAME="${DB_NAME:-$CONTAINER_ID}"
|
||||||
|
|
||||||
|
doas docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_ID}$" \
|
||||||
|
|| die "Container $CONTAINER_ID does not exist"
|
||||||
|
|
||||||
|
log "Updating all Odoo modules: DB=$DB_NAME host=$POSTGRES_HOST"
|
||||||
|
|
||||||
|
doas docker exec -u odoo "$CONTAINER_ID" odoo \
|
||||||
|
-u all \
|
||||||
|
-d "$DB_NAME" \
|
||||||
|
--stop-after-init \
|
||||||
|
--db_host="$POSTGRES_HOST" \
|
||||||
|
--db_port="$POSTGRES_PORT" \
|
||||||
|
--db_user="$POSTGRES_ADMIN_USER" \
|
||||||
|
--db_password="$POSTGRES_ADMIN_PASSWORD" \
|
||||||
|
|| die "Odoo module update failed for $CONTAINER_ID"
|
||||||
|
|
||||||
|
log "Module update complete — restarting $CONTAINER_ID"
|
||||||
|
"$SBIN_ROOT/startContainer" "$CONTAINER_ID"
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
|
|
||||||
dump_config (){
|
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') ========== Odoo Container Configuration =========="
|
|
||||||
echo "UUID: $UUID"
|
|
||||||
echo "BRANCH: $BRANCH"
|
|
||||||
echo "STAGING: $STAGING"
|
|
||||||
echo
|
|
||||||
echo "PostgreSQL Host: $POSTGRES_HOST"
|
|
||||||
echo "PostgreSQL Port: $POSTGRES_PORT"
|
|
||||||
echo "PostgreSQL Admin: $POSTGRES_ADMIN_USER"
|
|
||||||
echo "ODOO DB User: $ODOO_DB_USER"
|
|
||||||
echo
|
|
||||||
echo "BASEURL: $BASEURL"
|
|
||||||
echo "DATA_DIR: $DATA_DIR"
|
|
||||||
echo "CUSTOM_DIR: $CUSTOM_DIR"
|
|
||||||
echo "ENTERPRISE_DIR: $ENTERPRISE_DIR"
|
|
||||||
echo "LOGS_DIR: $LOGS_DIR"
|
|
||||||
echo "CONFIG_DIR: $CONFIG_DIR"
|
|
||||||
echo "CC_DIR: $CC_DIR"
|
|
||||||
echo "BACKUP_DIR: $BACKUP_DIR"
|
|
||||||
echo "GIT_DIR: $GIT_DIR"
|
|
||||||
echo "ETC_DIR: $ETC_DIR"
|
|
||||||
echo "INSTALL_DIR: $INSTALL_DIR"
|
|
||||||
echo "SSH_DIR: $SSH_DIR"
|
|
||||||
echo "HUGO_DIR: $HUGO_DIR"
|
|
||||||
echo
|
|
||||||
echo "SERVER_IP: $SERVER_IP"
|
|
||||||
echo "=================================================="
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# 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() {
|
|
||||||
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"
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# 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
|
|
||||||
# -----------------------------
|
|
||||||
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
|
|
||||||
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';"
|
|
||||||
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
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# Check if database exists
|
|
||||||
# -----------------------------
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# Function: Check DNS and build Traefik labels
|
|
||||||
# -----------------------------
|
|
||||||
check_domains() {
|
|
||||||
local domains="$1"
|
|
||||||
local server_ip="$2"
|
|
||||||
|
|
||||||
echo "Checking DNS resolution for domains: $domains"
|
|
||||||
local filtered_domains=""
|
|
||||||
for domain in $domains; do
|
|
||||||
ns_ip=$(nslookup "$domain" 2>/dev/null | grep -Eo 'Address: ([0-9]{1,3}\.){3}[0-9]{1,3}' | awk '{print $2}' | tail -n1)
|
|
||||||
if [[ "$ns_ip" == "$server_ip" ]]; then
|
|
||||||
filtered_domains+=" $domain"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
filtered_domains=$(echo "$filtered_domains" | xargs)
|
|
||||||
|
|
||||||
DOMAIN_LABEL=""
|
|
||||||
for domain in $filtered_domains; do
|
|
||||||
if [ -z "$DOMAIN_LABEL" ]; then
|
|
||||||
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=Host(\`$domain\`)"
|
|
||||||
else
|
|
||||||
DOMAIN_LABEL+=" || Host(\`$domain\`)"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "$DOMAIN_LABEL"
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
/4server/sbin/ODOO_19/listModules $1
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
export PATH=/4PROJECTS/bin:$PATH
|
|
||||||
if [ ! -n "$2" ]; then
|
|
||||||
echo "Missing Parameters <UUID> <FILE>"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
UUID=$1
|
|
||||||
echo "UUID: $UUID"
|
|
||||||
|
|
||||||
source /4server/sbin/helpers
|
|
||||||
|
|
||||||
get_contract_info
|
|
||||||
|
|
||||||
|
|
||||||
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
|
||||||
|
|
||||||
echo "PASSWORD $ODOO_DB_PASSWORD"
|
|
||||||
|
|
||||||
echo "Restoring $FILENAME to $UUID"
|
|
||||||
echo "status of container"
|
|
||||||
doas docker ps -a --filter "id=$UUID"
|
|
||||||
echo "POSTGRES HOST: $POSTGRES_HOST"
|
|
||||||
BACKUP="/mnt/backup/$2"
|
|
||||||
TEMPLATE="/mnt/db_images/$2"
|
|
||||||
doas docker exec "$UUID" /bin/bash -c "[ -f $TEMPLATE ]"
|
|
||||||
|
|
||||||
if doas docker exec "$UUID" /bin/bash -c "[ -f $BACKUP ]"; then
|
|
||||||
FILENAME="$BACKUP"
|
|
||||||
elif doas docker exec "$UUID" /bin/bash -c "[ -f $TEMPLATE ]"; then
|
|
||||||
FILENAME="$TEMPLATE"
|
|
||||||
else
|
|
||||||
echo "File not exists"
|
|
||||||
exit 0
|
|
||||||
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
|
|
||||||
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -d postgres -c "
|
|
||||||
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
|
|
||||||
WHERE datname = '$UUID' AND pid <> pg_backend_pid();
|
|
||||||
"
|
|
||||||
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -d postgres -c "
|
|
||||||
DROP DATABASE IF EXISTS \"$UUID\";
|
|
||||||
"
|
|
||||||
|
|
||||||
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
|
|
||||||
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
|
|
||||||
-c "ALTER ROLE \"$UUID\" CREATEDB;"
|
|
||||||
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
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 \
|
|
||||||
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
|
|
||||||
-c "ALTER ROLE \"$UUID\" NOCREATEDB;"
|
|
||||||
|
|
||||||
|
|
||||||
doas docker exec "$UUID" cp -r /root/.local/share/Odoo/filestore /home/odoo/.local/share/Odoo/filestore
|
|
||||||
doas docker exec "$UUID" chown -R odoo:odoo /home/odoo/.local
|
|
||||||
|
|
||||||
doas docker exec "$UUID" mkdir -p /var/lib/odoo/.local/share/Odoo/
|
|
||||||
doas docker exec "$UUID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore
|
|
||||||
|
|
||||||
docker restart "$UUID"
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
export PATH=/4PROJECTS/bin:$PATH
|
|
||||||
if [ ! -n "$1" ]; then
|
|
||||||
echo "Missing Parameters <UUID>"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
UUID=$1
|
|
||||||
echo "UUID: $UUID"
|
|
||||||
|
|
||||||
source /4server/sbin/helpers
|
|
||||||
|
|
||||||
get_contract_info
|
|
||||||
|
|
||||||
|
|
||||||
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
|
||||||
|
|
||||||
echo "PASSWORD $ODOO_DB_PASSWORD"
|
|
||||||
|
|
||||||
echo "POSTGRES HOST: $POSTGRES_HOST"
|
|
||||||
|
|
||||||
doas docker exec -it "$UUID" odoo shell --db_host beedb --db_password="$ODOO_DB_PASSWORD" -d "$UUID" --db_user="$UUID"
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
export PATH=/4PROJECTS/bin:$PATH
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
# Validate parameter
|
|
||||||
# --------------------------------------------------
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
echo "Missing Parameters <UUID>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
UUID=$1
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
# Load environment helpers
|
|
||||||
# --------------------------------------------------
|
|
||||||
source /4server/sbin/helpers
|
|
||||||
|
|
||||||
if ! get_contract_info "$UUID"; then
|
|
||||||
echo "Error: Failed to load contract info for $UUID" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
# Verify container exists
|
|
||||||
# --------------------------------------------------
|
|
||||||
if ! doas docker ps -a --format "{{.Names}}" | grep -q "^${UUID}$"; then
|
|
||||||
echo "Error: Container $UUID does not exist" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Updating all Odoo modules for database: $UUID"
|
|
||||||
echo "Using DB host: $POSTGRES_HOST"
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
# Run Odoo update inside container
|
|
||||||
# --------------------------------------------------
|
|
||||||
if ! doas docker exec -u odoo "$UUID" odoo \
|
|
||||||
-u all \
|
|
||||||
-d "$UUID" \
|
|
||||||
--stop-after-init \
|
|
||||||
--db_host="$POSTGRES_HOST" \
|
|
||||||
--db_port="$POSTGRES_PORT" \
|
|
||||||
--db_user="$POSTGRES_ADMIN_USER" \
|
|
||||||
--db_password="$POSTGRES_ADMIN_PASSWORD"
|
|
||||||
then
|
|
||||||
echo "Error: Odoo module update failed for $UUID" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Module update completed successfully for $UUID"
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
# Restart container
|
|
||||||
# --------------------------------------------------
|
|
||||||
echo "Starting container $UUID"
|
|
||||||
/4server/sbin/startContainer "$UUID"
|
|
||||||
|
|
||||||
echo "Done."
|
|
||||||
exit 0
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#!/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;
|
|
||||||
"
|
|
||||||
+76
-127
@@ -17,17 +17,9 @@ 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 = os.getenv("SBIN_PATH", "/usr/bin")
|
||||||
API_KEY = os.getenv("API_KEY", "your-secret-api-key")
|
API_KEY = os.getenv("API_KEY", "your-secret-api-key")
|
||||||
VERSION = "API: 0.0.9"
|
VERSION = "API: 0.0.9"
|
||||||
|
|
||||||
@@ -66,11 +58,11 @@ def init_db():
|
|||||||
CREATE TABLE IF NOT EXISTS containers (
|
CREATE TABLE IF NOT EXISTS containers (
|
||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
UUID CHAR(50) UNIQUE,
|
UUID CHAR(50) UNIQUE,
|
||||||
|
container_id CHAR(50) UNIQUE,
|
||||||
email CHAR(100),
|
email CHAR(100),
|
||||||
expires DATE,
|
expires DATE,
|
||||||
tags TEXT,
|
tags TEXT,
|
||||||
env TEXT,
|
env TEXT,
|
||||||
affiliate CHAR(30),
|
|
||||||
image CHAR(50),
|
image CHAR(50),
|
||||||
history TEXT,
|
history TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
@@ -97,29 +89,13 @@ 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()
|
data = cursor.fetchall() if fetch else None
|
||||||
rowcount = cursor.rowcount
|
conn.close()
|
||||||
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
|
||||||
@@ -134,7 +110,7 @@ class ContractItem(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class ContainerModel(BaseModel):
|
class ContainerModel(BaseModel):
|
||||||
UUID: str
|
container_id: str
|
||||||
email: Optional[str] = None
|
email: Optional[str] = None
|
||||||
expires: Optional[str] = None
|
expires: Optional[str] = None
|
||||||
tags: Optional[str] = None
|
tags: Optional[str] = None
|
||||||
@@ -157,12 +133,12 @@ class ContainerModel(BaseModel):
|
|||||||
env_list: Optional[str] = None
|
env_list: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class UUIDRequest(BaseModel):
|
class ContainerRequest(BaseModel):
|
||||||
UUID: str
|
container_id: str
|
||||||
|
|
||||||
|
|
||||||
class CommandRequest(BaseModel):
|
class CommandRequest(BaseModel):
|
||||||
uuid: str
|
container_id: str
|
||||||
method: int
|
method: int
|
||||||
|
|
||||||
|
|
||||||
@@ -177,13 +153,29 @@ class MoveRequest(BaseModel):
|
|||||||
|
|
||||||
class AddKeyRequest(BaseModel):
|
class AddKeyRequest(BaseModel):
|
||||||
key: str
|
key: str
|
||||||
uuid: str
|
container_id: str
|
||||||
|
|
||||||
|
|
||||||
class ImageRequest(BaseModel):
|
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():
|
||||||
@@ -192,34 +184,22 @@ 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.container_id,), 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(
|
||||||
"""
|
"""
|
||||||
INSERT INTO containers (UUID, email, expires, tags, image, history,
|
INSERT INTO containers (UUID, container_id, email, expires, tags, image, history,
|
||||||
comment, domains, status, created, bump, git, backup_slots, hdd, workers,
|
comment, domains, status, created, bump, git, backup_slots, hdd, workers,
|
||||||
utm_source, utm_medium, utm_campaign, domains_slots, secret_list, env_list)
|
utm_source, utm_medium, utm_campaign, domains_slots, secret_list, env_list)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
request.UUID,
|
request.container_id,
|
||||||
|
request.container_id,
|
||||||
request.email,
|
request.email,
|
||||||
request.expires,
|
request.expires,
|
||||||
request.tags,
|
request.tags,
|
||||||
@@ -242,86 +222,73 @@ def update_container(request: ContainerModel):
|
|||||||
request.env_list,
|
request.env_list,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
inserted = execute_db(
|
return {"container_id": request.container_id, "status": "created"}
|
||||||
"SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True
|
|
||||||
)
|
|
||||||
log.info("update_container INSERT result: %s", inserted)
|
|
||||||
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
|
||||||
model_fields = getattr(ContainerModel, "model_fields", None) or ContainerModel.__fields__
|
for field in ContainerModel.__fields__:
|
||||||
for field in model_fields:
|
if field == "container_id":
|
||||||
if field == "UUID":
|
|
||||||
continue
|
continue
|
||||||
value = getattr(request, field)
|
value = getattr(request, field)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
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; always keep UUID in sync with container_id
|
||||||
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
|
set_clause += ", UUID=?"
|
||||||
sql = f"UPDATE containers SET {set_clause} WHERE UUID=?"
|
params.append(request.container_id) # value for UUID=?
|
||||||
log.info("update_container: executing SQL=%s params=%s", sql, tuple(params))
|
params.append(request.container_id) # value for WHERE UUID=?
|
||||||
execute_db(sql, tuple(params))
|
execute_db(f"UPDATE containers SET {set_clause} WHERE UUID=?", 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,
|
"container_id": request.container_id,
|
||||||
"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 {"container_id": request.container_id, "status": "no_change"}
|
||||||
return {"UUID": request.UUID, "status": "no_change"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/start", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/start", dependencies=[Depends(verify_api_key)])
|
||||||
def start_container(request: UUIDRequest):
|
def start_container(request: ContainerRequest):
|
||||||
return {"message": run_command([f"{BIN_PATH}/startContainer", request.UUID])}
|
return {"message": run_command([f"{BIN_PATH}/startContainer", request.container_id])}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/restartAllContainers", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/restartAllContainers", dependencies=[Depends(verify_api_key)])
|
||||||
def restart_all_containers_by_image(request: ImageRequest):
|
def start_container(request: ImageRequest):
|
||||||
return {"message": run_command([f"{BIN_PATH}/restartContainers", request.image])}
|
return {"message": run_command([f"{BIN_PATH}/restartContainers", request.image])}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/stop", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/stop", dependencies=[Depends(verify_api_key)])
|
||||||
def stop_container(request: UUIDRequest):
|
def stop_container(request: ContainerRequest):
|
||||||
try:
|
try:
|
||||||
return {"message": run_command([f"{BIN_PATH}/stopContainer", request.UUID])}
|
return {"message": run_command([f"{BIN_PATH}/stopContainer", request.container_id])}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
# Command failed, but continue without error to the API
|
# Command failed, but continue without error to the API
|
||||||
return {"message": "Container stop command executed (may have failed)"}
|
return {"message": "Container stop command executed (may have failed)"}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
|
||||||
def nuke_container(request: UUIDRequest):
|
def nuke_container(request: ContainerRequest):
|
||||||
status = execute_db(
|
status = execute_db(
|
||||||
"SELECT status FROM containers WHERE UUID=?", (request.UUID,), fetch=True
|
"SELECT status FROM containers WHERE UUID=?", (request.container_id,), fetch=True
|
||||||
)
|
)
|
||||||
if not status or status[0]["status"] != "nuke":
|
if not status or status[0]["status"] != "nuke":
|
||||||
raise HTTPException(400, "Container status is not 'nuke'")
|
raise HTTPException(400, "Container status is not 'nuke'")
|
||||||
return {"message": run_command([f"{BIN_PATH}/nukeContainer", request.UUID])}
|
return {"message": run_command([f"{BIN_PATH}/nukeContainer", request.container_id])}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/info", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/info", dependencies=[Depends(verify_api_key)])
|
||||||
def info_container(request: Optional[UUIDRequest] = None):
|
def info_container(request: Optional[ContainerRequest] = None):
|
||||||
# Fields to select
|
# Fields to select
|
||||||
fields = [
|
fields = [
|
||||||
"ID",
|
"ID",
|
||||||
"UUID",
|
"container_id",
|
||||||
"email",
|
"email",
|
||||||
"expires",
|
"expires",
|
||||||
"tags",
|
"tags",
|
||||||
@@ -347,7 +314,7 @@ def info_container(request: Optional[UUIDRequest] = None):
|
|||||||
if request:
|
if request:
|
||||||
rows = execute_db(
|
rows = execute_db(
|
||||||
f"SELECT {field_str} FROM containers WHERE UUID=?",
|
f"SELECT {field_str} FROM containers WHERE UUID=?",
|
||||||
(str(request.UUID),),
|
(str(request.container_id),),
|
||||||
fetch=True,
|
fetch=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -370,20 +337,20 @@ def info_container(request: Optional[UUIDRequest] = None):
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/container/bump", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/bump", dependencies=[Depends(verify_api_key)])
|
||||||
def bump_container(request: UUIDRequest):
|
def bump_container(request: ContainerRequest):
|
||||||
today = datetime.utcnow().strftime("%Y-%m-%d")
|
today = datetime.utcnow().strftime("%Y-%m-%d")
|
||||||
execute_db("UPDATE containers SET bump=? WHERE UUID=?", (today, request.UUID))
|
execute_db("UPDATE containers SET bump=? WHERE UUID=?", (today, request.container_id))
|
||||||
msg = run_command([f"{BIN_PATH}/bumpContainer", request.UUID])
|
msg = run_command([f"{BIN_PATH}/bumpContainer", request.container_id])
|
||||||
return {"message": msg, "bump_date": today}
|
return {"message": msg, "bump_date": today}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/quota", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/quota", dependencies=[Depends(verify_api_key)])
|
||||||
def container_quota(request: UUIDRequest):
|
def container_quota(request: ContainerRequest):
|
||||||
output = run_command(
|
output = run_command(
|
||||||
[
|
[
|
||||||
"docker",
|
"docker",
|
||||||
"stats",
|
"stats",
|
||||||
request.UUID,
|
request.container_id,
|
||||||
"--no-stream",
|
"--no-stream",
|
||||||
"--format",
|
"--format",
|
||||||
"{{.MemUsage}},{{.BlockIO}}",
|
"{{.MemUsage}},{{.BlockIO}}",
|
||||||
@@ -456,9 +423,9 @@ def restart_all_containers(request: ImageRequest):
|
|||||||
@app.post("/client/git", dependencies=[Depends(verify_api_key)])
|
@app.post("/client/git", dependencies=[Depends(verify_api_key)])
|
||||||
def git_tool(request: CommandRequest):
|
def git_tool(request: CommandRequest):
|
||||||
if request.method == 1:
|
if request.method == 1:
|
||||||
command = [f"{BIN_PATH}/gitPull", request.uuid]
|
command = [f"{BIN_PATH}/gitPull", request.container_id]
|
||||||
elif request.method == 2:
|
elif request.method == 2:
|
||||||
command = [f"{BIN_PATH}/gitRevert", request.uuid]
|
command = [f"{BIN_PATH}/gitRevert", request.container_id]
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=400, detail="Invalid method")
|
raise HTTPException(status_code=400, detail="Invalid method")
|
||||||
|
|
||||||
@@ -468,10 +435,10 @@ def git_tool(request: CommandRequest):
|
|||||||
|
|
||||||
@app.post("/client/addGitKey", dependencies=[Depends(verify_api_key)])
|
@app.post("/client/addGitKey", dependencies=[Depends(verify_api_key)])
|
||||||
def add_git_key(request: AddKeyRequest):
|
def add_git_key(request: AddKeyRequest):
|
||||||
if not request.key or not request.uuid:
|
if not request.key or not request.container_id:
|
||||||
raise HTTPException(status_code=400, detail="Key and uuid are required")
|
raise HTTPException(status_code=400, detail="Key and container_id are required")
|
||||||
|
|
||||||
file_path = f"/4server/data/{request.uuid}/git-server/keys/id_rsa.pub"
|
file_path = f"/4server/data/{request.container_id}/git-server/keys/id_rsa.pub"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Ensure the directory exists
|
# Ensure the directory exists
|
||||||
@@ -482,26 +449,26 @@ def add_git_key(request: AddKeyRequest):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"Failed to save key: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Failed to save key: {str(e)}")
|
||||||
|
|
||||||
return {"message": f"Key saved for container {request.uuid}", "file": file_path}
|
return {"message": f"Key saved for container {request.container_id}", "file": file_path}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/client/audit/{uuid}", dependencies=[Depends(verify_api_key)])
|
@app.get("/client/audit/{container_id}", dependencies=[Depends(verify_api_key)])
|
||||||
def audit_odoo(uuid: str):
|
def audit_odoo(container_id: str):
|
||||||
return {"message": run_command([f"{BIN_PATH}/ODOO_19/listModules", uuid])}
|
return {"message": run_command([f"{BIN_PATH}/ODOO/listModules", container_id])}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/client/logs/{uuid}", dependencies=[Depends(verify_api_key)])
|
@app.get("/client/logs/{container_id}", dependencies=[Depends(verify_api_key)])
|
||||||
async def get_odoo_log_summary(uuid: str):
|
async def get_odoo_log_summary(container_id: str):
|
||||||
if not re.fullmatch(r"[0-9a-fA-F\-]+", uuid):
|
if not re.fullmatch(r"[a-zA-Z0-9_-]+", container_id):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail="Invalid UUID format. Only numbers, letters a-f, and '-' are allowed.",
|
detail="Invalid container_id format. Only letters, numbers, hyphens, and underscores are allowed.",
|
||||||
)
|
)
|
||||||
|
|
||||||
BASE_LOG_DIR = "/4server/data"
|
BASE_LOG_DIR = "/4server/data"
|
||||||
|
|
||||||
# Build file paths as strings
|
# Build file paths as strings
|
||||||
project_dir = os.path.join(BASE_LOG_DIR, uuid)
|
project_dir = os.path.join(BASE_LOG_DIR, container_id)
|
||||||
odoo_log_file = os.path.join(project_dir, "logs", "odoo.log")
|
odoo_log_file = os.path.join(project_dir, "logs", "odoo.log")
|
||||||
git_log_file = os.path.join(project_dir, "logs", "git.log")
|
git_log_file = os.path.join(project_dir, "logs", "git.log")
|
||||||
|
|
||||||
@@ -546,7 +513,7 @@ async def get_odoo_log_summary(uuid: str):
|
|||||||
last_50_git_lines = read_last_lines(git_log_file, 50)
|
last_50_git_lines = read_last_lines(git_log_file, 50)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"uuid": str(uuid),
|
"container_id": str(container_id),
|
||||||
"important_odoo_log_lines": important_odoo_lines,
|
"important_odoo_log_lines": important_odoo_lines,
|
||||||
"last_git_log_lines": last_50_git_lines,
|
"last_git_log_lines": last_50_git_lines,
|
||||||
}
|
}
|
||||||
@@ -560,7 +527,7 @@ def backup_import(request: ImportRequest):
|
|||||||
if not request.filename:
|
if not request.filename:
|
||||||
raise HTTPException(status_code=400, detail="Filename is required")
|
raise HTTPException(status_code=400, detail="Filename is required")
|
||||||
|
|
||||||
command = [f"{BIN_PATH}/ODOO_19/import", request.filename]
|
command = [f"{BIN_PATH}/ODOO/import", request.filename]
|
||||||
output = run_command(command)
|
output = run_command(command)
|
||||||
return {"message": output}
|
return {"message": output}
|
||||||
|
|
||||||
@@ -587,30 +554,12 @@ def backup_move(request: MoveRequest):
|
|||||||
|
|
||||||
|
|
||||||
class BackupCheckRequest(BaseModel):
|
class BackupCheckRequest(BaseModel):
|
||||||
uuid: Optional[str] = None
|
container_id: str
|
||||||
|
|
||||||
|
|
||||||
@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.container_id])}
|
||||||
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)])
|
||||||
|
|||||||
@@ -1,591 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sqlite3
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
from collections import deque
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Dict, Optional
|
|
||||||
|
|
||||||
import psutil
|
|
||||||
import uvicorn
|
|
||||||
from fastapi import Depends, FastAPI, HTTPException, Response
|
|
||||||
from fastapi.responses import PlainTextResponse, RedirectResponse
|
|
||||||
from fastapi.security.api_key import APIKeyHeader
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
# ---------------------- Constants ----------------------
|
|
||||||
DB_PATH = "/4server/data/contracts.db"
|
|
||||||
BIN_PATH = "/4server/sbin"
|
|
||||||
API_KEY = os.getenv("API_KEY", "your-secret-api-key")
|
|
||||||
VERSION = "API: 0.0.9"
|
|
||||||
|
|
||||||
# ---------------------- FastAPI App ----------------------
|
|
||||||
app = FastAPI()
|
|
||||||
api_key_header = APIKeyHeader(name="X-API-Key")
|
|
||||||
|
|
||||||
|
|
||||||
def verify_api_key(key: str = Depends(api_key_header)):
|
|
||||||
if key != API_KEY:
|
|
||||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------- Helpers ----------------------
|
|
||||||
def run_command(cmd: list[str]) -> str:
|
|
||||||
"""Run a shell command and return stdout or raise HTTPException on error."""
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
if result.returncode != 0:
|
|
||||||
error_msg = (
|
|
||||||
f"Command failed\n"
|
|
||||||
f"Command: {' '.join(cmd)}\n"
|
|
||||||
f"Return code: {result.returncode}\n"
|
|
||||||
f"Stdout: {result.stdout.strip()}\n"
|
|
||||||
f"Stderr: {result.stderr.strip() or 'None'}"
|
|
||||||
)
|
|
||||||
raise HTTPException(status_code=500, detail=error_msg)
|
|
||||||
return result.stdout.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def init_db():
|
|
||||||
"""Initialize the database with containers table."""
|
|
||||||
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
|
||||||
conn = sqlite3.connect(DB_PATH)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS containers (
|
|
||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
UUID CHAR(50) UNIQUE,
|
|
||||||
email CHAR(100),
|
|
||||||
expires DATE,
|
|
||||||
tags TEXT,
|
|
||||||
env TEXT,
|
|
||||||
affiliate CHAR(30),
|
|
||||||
image CHAR(50),
|
|
||||||
history TEXT,
|
|
||||||
comment TEXT,
|
|
||||||
domains TEXT,
|
|
||||||
status CHAR(20),
|
|
||||||
created DATE,
|
|
||||||
bump DATE,
|
|
||||||
secret TEXT,
|
|
||||||
contract TEXT,
|
|
||||||
git integer,
|
|
||||||
backup_slots integer,
|
|
||||||
hdd integer,
|
|
||||||
workers integer,
|
|
||||||
utm_source text,
|
|
||||||
utm_medium text,
|
|
||||||
utm_campaign text,
|
|
||||||
domains_slots integer,
|
|
||||||
secret_list TEXT,
|
|
||||||
env_list TEXT
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def execute_db(query: str, params: tuple = (), fetch: bool = False):
|
|
||||||
conn = sqlite3.connect(DB_PATH)
|
|
||||||
conn.row_factory = sqlite3.Row
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute(query, params)
|
|
||||||
conn.commit()
|
|
||||||
data = cursor.fetchall() if fetch else None
|
|
||||||
conn.close()
|
|
||||||
if data and fetch:
|
|
||||||
return [dict(row) for row in data]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------- Models ----------------------
|
|
||||||
class ContractItem(BaseModel):
|
|
||||||
quantity: int
|
|
||||||
name: str
|
|
||||||
product_id: int
|
|
||||||
features: Dict[str, Any]
|
|
||||||
|
|
||||||
|
|
||||||
class ContainerModel(BaseModel):
|
|
||||||
UUID: str
|
|
||||||
email: Optional[str] = None
|
|
||||||
expires: Optional[str] = None
|
|
||||||
tags: Optional[str] = None
|
|
||||||
image: Optional[str] = None
|
|
||||||
history: Optional[str] = None
|
|
||||||
comment: Optional[str] = None
|
|
||||||
domains: Optional[str] = None
|
|
||||||
status: Optional[str] = None
|
|
||||||
created: Optional[str] = None
|
|
||||||
bump: Optional[str] = None
|
|
||||||
git: Optional[int] = None
|
|
||||||
backup_slots: Optional[int] = None
|
|
||||||
hdd: Optional[int] = None
|
|
||||||
workers: Optional[int] = None
|
|
||||||
utm_source: Optional[str] = None
|
|
||||||
utm_medium: Optional[str] = None
|
|
||||||
utm_campaign: Optional[str] = None
|
|
||||||
domains_slots: Optional[int] = None
|
|
||||||
<<<<<<< HEAD
|
|
||||||
secret_list: Optional[str] = None
|
|
||||||
=======
|
|
||||||
>>>>>>> c583649 (hmm)
|
|
||||||
env_list: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class UUIDRequest(BaseModel):
|
|
||||||
UUID: str
|
|
||||||
|
|
||||||
|
|
||||||
class CommandRequest(BaseModel):
|
|
||||||
uuid: str
|
|
||||||
method: int
|
|
||||||
|
|
||||||
|
|
||||||
class ImportRequest(BaseModel):
|
|
||||||
filename: str
|
|
||||||
|
|
||||||
|
|
||||||
class MoveRequest(BaseModel):
|
|
||||||
source: str
|
|
||||||
destination: str
|
|
||||||
|
|
||||||
|
|
||||||
class AddKeyRequest(BaseModel):
|
|
||||||
key: str
|
|
||||||
uuid: str
|
|
||||||
|
|
||||||
|
|
||||||
class ImageRequest(BaseModel):
|
|
||||||
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 ----------------------
|
|
||||||
@app.get("/", include_in_schema=False)
|
|
||||||
def redirect_to_odoo():
|
|
||||||
return RedirectResponse(url="https://ODOO4PROJECTS.com")
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/update", dependencies=[Depends(verify_api_key)])
|
|
||||||
def update_container(request: ContainerModel):
|
|
||||||
# Fetch existing record
|
|
||||||
existing = execute_db(
|
|
||||||
"SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True
|
|
||||||
)
|
|
||||||
if not existing:
|
|
||||||
# If record does not exist, insert a new one with all given fields
|
|
||||||
execute_db(
|
|
||||||
"""
|
|
||||||
INSERT INTO containers (UUID, email, expires, tags, image, history,
|
|
||||||
comment, domains, status, created, bump, git, backup_slots, hdd, workers,
|
|
||||||
<<<<<<< HEAD
|
|
||||||
utm_source, utm_medium, utm_campaign, domains_slots, secret_list, env_list)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
=======
|
|
||||||
utm_source, utm_medium, utm_campaign, domains_slots, env_list)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
>>>>>>> c583649 (hmm)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
request.UUID,
|
|
||||||
request.email,
|
|
||||||
request.expires,
|
|
||||||
request.tags,
|
|
||||||
request.image,
|
|
||||||
request.history,
|
|
||||||
request.comment,
|
|
||||||
request.domains,
|
|
||||||
request.status,
|
|
||||||
request.created,
|
|
||||||
request.bump,
|
|
||||||
request.git,
|
|
||||||
request.backup_slots,
|
|
||||||
request.hdd,
|
|
||||||
request.workers,
|
|
||||||
request.utm_source,
|
|
||||||
request.utm_medium,
|
|
||||||
request.utm_campaign,
|
|
||||||
request.domains_slots,
|
|
||||||
<<<<<<< HEAD
|
|
||||||
request.secret_list,
|
|
||||||
=======
|
|
||||||
>>>>>>> c583649 (hmm)
|
|
||||||
request.env_list,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return {"UUID": request.UUID, "status": "created"}
|
|
||||||
|
|
||||||
# Existing record found, do partial update
|
|
||||||
existing = existing[0] # Assuming fetch returns list of dicts
|
|
||||||
updates = {}
|
|
||||||
params = []
|
|
||||||
|
|
||||||
# Only add fields that are not None in the request
|
|
||||||
for field in ContainerModel.__fields__:
|
|
||||||
if field == "UUID":
|
|
||||||
continue
|
|
||||||
value = getattr(request, field)
|
|
||||||
if value is not None:
|
|
||||||
updates[field] = value
|
|
||||||
params.append(value)
|
|
||||||
|
|
||||||
if updates:
|
|
||||||
# Build SQL dynamically
|
|
||||||
set_clause = ", ".join(f"{k}=?" for k in updates.keys())
|
|
||||||
params.append(request.UUID) # UUID for WHERE clause
|
|
||||||
execute_db(f"UPDATE containers SET {set_clause} WHERE UUID=?", tuple(params))
|
|
||||||
return {
|
|
||||||
"UUID": request.UUID,
|
|
||||||
"status": "updated",
|
|
||||||
"fields_updated": list(updates.keys()),
|
|
||||||
}
|
|
||||||
|
|
||||||
return {"UUID": request.UUID, "status": "no_change"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/start", dependencies=[Depends(verify_api_key)])
|
|
||||||
def start_container(request: UUIDRequest):
|
|
||||||
return {"message": run_command([f"{BIN_PATH}/startContainer", request.UUID])}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/restartAllContainers", dependencies=[Depends(verify_api_key)])
|
|
||||||
def start_container(request: ImageRequest):
|
|
||||||
return {"message": run_command([f"{BIN_PATH}/restartContainers", request.image])}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/stop", dependencies=[Depends(verify_api_key)])
|
|
||||||
def stop_container(request: UUIDRequest):
|
|
||||||
try:
|
|
||||||
return {"message": run_command([f"{BIN_PATH}/stopContainer", request.UUID])}
|
|
||||||
except HTTPException:
|
|
||||||
# Command failed, but continue without error to the API
|
|
||||||
return {"message": "Container stop command executed (may have failed)"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
|
|
||||||
def nuke_container(request: UUIDRequest):
|
|
||||||
status = execute_db(
|
|
||||||
"SELECT status FROM containers WHERE UUID=?", (request.UUID,), fetch=True
|
|
||||||
)
|
|
||||||
if not status or status[0]["status"] != "nuke":
|
|
||||||
raise HTTPException(400, "Container status is not 'nuke'")
|
|
||||||
return {"message": run_command([f"{BIN_PATH}/nukeContainer", request.UUID])}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/info", dependencies=[Depends(verify_api_key)])
|
|
||||||
def info_container(request: Optional[UUIDRequest] = None):
|
|
||||||
# Fields to select
|
|
||||||
fields = [
|
|
||||||
"ID",
|
|
||||||
"UUID",
|
|
||||||
"email",
|
|
||||||
"expires",
|
|
||||||
"tags",
|
|
||||||
"image",
|
|
||||||
"history",
|
|
||||||
"comment",
|
|
||||||
"domains",
|
|
||||||
"status",
|
|
||||||
"created",
|
|
||||||
"git",
|
|
||||||
"backup_slots",
|
|
||||||
"hdd",
|
|
||||||
"workers",
|
|
||||||
"utm_source",
|
|
||||||
"utm_medium",
|
|
||||||
"utm_campaign",
|
|
||||||
"domains_slots",
|
|
||||||
"env_list",
|
|
||||||
]
|
|
||||||
field_str = ", ".join(fields)
|
|
||||||
|
|
||||||
# Execute query
|
|
||||||
if request:
|
|
||||||
rows = execute_db(
|
|
||||||
f"SELECT {field_str} FROM containers WHERE UUID=?",
|
|
||||||
(str(request.UUID),),
|
|
||||||
fetch=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
rows = execute_db(f"SELECT {field_str} FROM containers", fetch=True)
|
|
||||||
|
|
||||||
# Map rows to dicts safely
|
|
||||||
containers = []
|
|
||||||
for row in rows:
|
|
||||||
if isinstance(row, dict):
|
|
||||||
# Already a dict (e.g., some DB wrappers)
|
|
||||||
containers.append(row)
|
|
||||||
else:
|
|
||||||
# Tuple/list -> map with fields
|
|
||||||
containers.append(dict(zip(fields, row)))
|
|
||||||
|
|
||||||
# Wrap in n8n JSON format
|
|
||||||
n8n_items = [{"json": container} for container in containers]
|
|
||||||
|
|
||||||
return n8n_items
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/bump", dependencies=[Depends(verify_api_key)])
|
|
||||||
def bump_container(request: UUIDRequest):
|
|
||||||
today = datetime.utcnow().strftime("%Y-%m-%d")
|
|
||||||
execute_db("UPDATE containers SET bump=? WHERE UUID=?", (today, request.UUID))
|
|
||||||
msg = run_command([f"{BIN_PATH}/bumpContainer", request.UUID])
|
|
||||||
return {"message": msg, "bump_date": today}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/quota", dependencies=[Depends(verify_api_key)])
|
|
||||||
def container_quota(request: UUIDRequest):
|
|
||||||
output = run_command(
|
|
||||||
[
|
|
||||||
"docker",
|
|
||||||
"stats",
|
|
||||||
request.UUID,
|
|
||||||
"--no-stream",
|
|
||||||
"--format",
|
|
||||||
"{{.MemUsage}},{{.BlockIO}}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
mem_usage, disk_usage = output.split(",")
|
|
||||||
return {"memory_usage": mem_usage, "disk_io": disk_usage}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------- SYSTEM ----------------------
|
|
||||||
@app.get("/system/containers", dependencies=[Depends(verify_api_key)])
|
|
||||||
def get_containers():
|
|
||||||
return Response(
|
|
||||||
content=run_command([f"{BIN_PATH}/getContainers"]),
|
|
||||||
media_type="application/json",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/system/images", dependencies=[Depends(verify_api_key)])
|
|
||||||
def list_images():
|
|
||||||
images = run_command(["docker", "images", "--format", "{{.Repository}}:{{.Tag}}"])
|
|
||||||
return {"images": images.split("\n")}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/system/cpu", dependencies=[Depends(verify_api_key)])
|
|
||||||
def get_cpu_log():
|
|
||||||
CPU_LOG_PATH = Path("/4server/data/log/cpu.log")
|
|
||||||
if not CPU_LOG_PATH.exists():
|
|
||||||
raise HTTPException(status_code=404, detail="CPU log file not found")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with CPU_LOG_PATH.open("r") as f:
|
|
||||||
content = f.read()
|
|
||||||
return PlainTextResponse(content)
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=f"Error reading CPU log: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/system/info", dependencies=[Depends(verify_api_key)])
|
|
||||||
def get_system_info():
|
|
||||||
try:
|
|
||||||
alpine_version = None
|
|
||||||
last_update = None
|
|
||||||
bump_dates = execute_db(
|
|
||||||
"SELECT MAX(bump) AS latest_bump FROM containers", fetch=True
|
|
||||||
)[0]["latest_bump"]
|
|
||||||
if os.path.exists("/4server/data/update"):
|
|
||||||
with open("/4server/data/update") as f:
|
|
||||||
last_update = f.read().strip()
|
|
||||||
if os.path.exists("/etc/alpine-release"):
|
|
||||||
with open("/etc/alpine-release") as f:
|
|
||||||
alpine_version = f.read().strip()
|
|
||||||
mem = psutil.virtual_memory()
|
|
||||||
disk = psutil.disk_usage("/")
|
|
||||||
cpu_count = psutil.cpu_count(logical=True)
|
|
||||||
return {
|
|
||||||
"alpine_version": alpine_version,
|
|
||||||
"last_update": last_update,
|
|
||||||
"latest_bump": bump_dates,
|
|
||||||
"version": VERSION,
|
|
||||||
"resources": {
|
|
||||||
"memory": {
|
|
||||||
"total": mem.total,
|
|
||||||
"available": mem.available,
|
|
||||||
"used": mem.used,
|
|
||||||
},
|
|
||||||
"disk": {"total": disk.total, "used": disk.used, "free": disk.free},
|
|
||||||
"cpu_count": cpu_count,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/system/pull", dependencies=[Depends(verify_api_key)])
|
|
||||||
def pull_all_images():
|
|
||||||
return {"message": run_command([f"{BIN_PATH}/pullAllContainers"])}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/system/restartAllContainers", dependencies=[Depends(verify_api_key)])
|
|
||||||
def restart_all_containers(request: ImageRequest):
|
|
||||||
if not request.image:
|
|
||||||
raise HTTPException(status_code=400, detail="Image name is required")
|
|
||||||
return {"message": run_command([f"{BIN_PATH}/restartContainers", request.image])}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/client/git", dependencies=[Depends(verify_api_key)])
|
|
||||||
def git_tool(request: CommandRequest):
|
|
||||||
if request.method == 1:
|
|
||||||
command = [f"{BIN_PATH}/gitPull", request.uuid]
|
|
||||||
elif request.method == 2:
|
|
||||||
command = [f"{BIN_PATH}/gitRevert", request.uuid]
|
|
||||||
else:
|
|
||||||
raise HTTPException(status_code=400, detail="Invalid method")
|
|
||||||
|
|
||||||
output = run_command(command)
|
|
||||||
return {"message": output}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/client/addGitKey", dependencies=[Depends(verify_api_key)])
|
|
||||||
def add_git_key(request: AddKeyRequest):
|
|
||||||
if not request.key or not request.uuid:
|
|
||||||
raise HTTPException(status_code=400, detail="Key and uuid are required")
|
|
||||||
|
|
||||||
file_path = f"/4server/data/{request.uuid}/git-server/keys/id_rsa.pub"
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Ensure the directory exists
|
|
||||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
||||||
# Write the key to the file
|
|
||||||
with open(file_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write(request.key)
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=f"Failed to save key: {str(e)}")
|
|
||||||
|
|
||||||
return {"message": f"Key saved for container {request.uuid}", "file": file_path}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/client/audit/{uuid}", dependencies=[Depends(verify_api_key)])
|
|
||||||
def audit_odoo(uuid: str):
|
|
||||||
return {"message": run_command([f"{BIN_PATH}/ODOO_19/listModules", uuid])}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/client/logs/{uuid}", dependencies=[Depends(verify_api_key)])
|
|
||||||
async def get_odoo_log_summary(uuid: str):
|
|
||||||
if not re.fullmatch(r"[0-9a-fA-F\-]+", uuid):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail="Invalid UUID format. Only numbers, letters a-f, and '-' are allowed.",
|
|
||||||
)
|
|
||||||
|
|
||||||
BASE_LOG_DIR = "/4server/data"
|
|
||||||
|
|
||||||
# Build file paths as strings
|
|
||||||
project_dir = os.path.join(BASE_LOG_DIR, uuid)
|
|
||||||
odoo_log_file = os.path.join(project_dir, "logs", "odoo.log")
|
|
||||||
git_log_file = os.path.join(project_dir, "logs", "git.log")
|
|
||||||
|
|
||||||
if not os.path.isfile(odoo_log_file):
|
|
||||||
raise HTTPException(status_code=404, detail="Odoo log file not found")
|
|
||||||
|
|
||||||
# --- Helper variables and functions ---
|
|
||||||
IMPORTANT_PATTERNS = [
|
|
||||||
re.compile(r"odoo\.addons\."), # Any Odoo addon log
|
|
||||||
re.compile(r"Job '.*' starting"), # Cron job start
|
|
||||||
re.compile(r"Job '.*' fully done"), # Cron job end
|
|
||||||
re.compile(r"ERROR"), # Errors
|
|
||||||
re.compile(r"WARNING"), # Warnings
|
|
||||||
re.compile(r"Traceback"), # Tracebacks
|
|
||||||
re.compile(r"Error"),
|
|
||||||
]
|
|
||||||
|
|
||||||
def is_important_line(line: str) -> bool:
|
|
||||||
return any(p.search(line) for p in IMPORTANT_PATTERNS)
|
|
||||||
|
|
||||||
def read_last_lines(file_path: str, max_lines: int) -> list[str]:
|
|
||||||
"""
|
|
||||||
Read the last `max_lines` from the file efficiently.
|
|
||||||
"""
|
|
||||||
if not os.path.isfile(file_path):
|
|
||||||
return []
|
|
||||||
last_lines = deque(maxlen=max_lines)
|
|
||||||
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
||||||
for line in f:
|
|
||||||
last_lines.append(line.strip())
|
|
||||||
return list(last_lines)
|
|
||||||
|
|
||||||
# --- Main logic ---
|
|
||||||
try:
|
|
||||||
# Last 500 lines from Odoo log
|
|
||||||
last_500_lines = read_last_lines(odoo_log_file, 500)
|
|
||||||
important_odoo_lines = [
|
|
||||||
line for line in last_500_lines if is_important_line(line)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Last 50 lines from git.log
|
|
||||||
last_50_git_lines = read_last_lines(git_log_file, 50)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"uuid": str(uuid),
|
|
||||||
"important_odoo_log_lines": important_odoo_lines,
|
|
||||||
"last_git_log_lines": last_50_git_lines,
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=f"Error reading log files: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------ BACKUP HANDLING -------------------------------------
|
|
||||||
@app.post("/backup/import", dependencies=[Depends(verify_api_key)])
|
|
||||||
def backup_import(request: ImportRequest):
|
|
||||||
if not request.filename:
|
|
||||||
raise HTTPException(status_code=400, detail="Filename is required")
|
|
||||||
|
|
||||||
command = [f"{BIN_PATH}/ODOO_19/import", request.filename]
|
|
||||||
output = run_command(command)
|
|
||||||
return {"message": output}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/backup/move", dependencies=[Depends(verify_api_key)])
|
|
||||||
def backup_move(request: MoveRequest):
|
|
||||||
if not request.source or not request.destination:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400, detail="Source and destination are required"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not os.path.exists(request.source):
|
|
||||||
raise HTTPException(status_code=404, detail="Source file does not exist")
|
|
||||||
|
|
||||||
# Use shell command to move the file
|
|
||||||
command = ["mv", request.source, request.destination] # Linux/macOS
|
|
||||||
# For Windows, use: command = ["move", request.source, request.destination]
|
|
||||||
|
|
||||||
output = run_command(command)
|
|
||||||
return {
|
|
||||||
"message": f"Moved {request.source} to {request.destination}",
|
|
||||||
"output": output,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/backup/borgpush", dependencies=[Depends(verify_api_key)])
|
|
||||||
def backup_borgpush():
|
|
||||||
return {"message": run_command(["doas", "-u", "4server", f"{BIN_PATH}/borgpush"])}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------- Entry Point ----------------------
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print(VERSION)
|
|
||||||
init_db()
|
|
||||||
time.sleep(25)
|
|
||||||
uvicorn.run(app, host="10.5.0.1", port=8888)
|
|
||||||
@@ -36,34 +36,9 @@ 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")
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -4,10 +4,10 @@ set -euo pipefail # Fail on error, undefined variables, and pipe errors
|
|||||||
|
|
||||||
# Create backup filename
|
# Create backup filename
|
||||||
FILENAME="$(date +"%Y%m%d_%H%M").zip"
|
FILENAME="$(date +"%Y%m%d_%H%M").zip"
|
||||||
BACKUP_DIR="/BACKUP/$UUID"
|
BACKUP_DIR="/BACKUP/$CONTAINER_ID"
|
||||||
mkdir -p "$BACKUP_DIR"
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
tar -czvf "$BACKUP_DIR/$FILENAME" -C "/4server/data" "$UUID"
|
tar -czvf "$BACKUP_DIR/$FILENAME" -C "/4server/data" "$CONTAINER_ID"
|
||||||
|
|
||||||
# Remove old backups beyond the configured slots
|
# Remove old backups beyond the configured slots
|
||||||
ls -t "$BACKUP_DIR"/[0-9]*.zip 2>/dev/null | tail -n +$((BACKUP_SLOTS + 1)) | while read -r file; do
|
ls -t "$BACKUP_DIR"/[0-9]*.zip 2>/dev/null | tail -n +$((BACKUP_SLOTS + 1)) | while read -r file; do
|
||||||
|
|||||||
+18
-12
@@ -4,21 +4,27 @@
|
|||||||
|
|
||||||
set -euo pipefail # Fail on error, undefined variables, and pipe errors
|
set -euo pipefail # Fail on error, undefined variables, and pipe errors
|
||||||
|
|
||||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
# Load helper functions (provides log/warn/die)
|
||||||
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of backup/
|
||||||
|
source "$SBIN_ROOT/helpers"
|
||||||
|
|
||||||
# Load helper functions
|
# Load SOFTWARE and DB_NAME from container.env
|
||||||
source /4server/sbin/helpers
|
load_container_config "$CONTAINER_ID"
|
||||||
|
|
||||||
# Get contract info (also exports ODOO_DB_PASSWORD via helpers)
|
# Get contract info (also exports ODOO_DB_PASSWORD via helpers)
|
||||||
get_contract_info
|
get_contract_info
|
||||||
|
|
||||||
log "UUID: $UUID"
|
# DB_NAME is the postgres database name (may differ from CONTAINER_ID for migrated containers)
|
||||||
|
DB_NAME="${DB_NAME:-$CONTAINER_ID}"
|
||||||
|
|
||||||
|
log "CONTAINER_ID: $CONTAINER_ID"
|
||||||
|
log "DB_NAME: $DB_NAME"
|
||||||
log "Retention: daily 14d, weekly 12w"
|
log "Retention: daily 14d, weekly 12w"
|
||||||
log "DB host: $POSTGRES_HOST"
|
log "DB host: $POSTGRES_HOST"
|
||||||
|
|
||||||
# Build paths
|
# Build paths
|
||||||
FILENAME="$(date +"%Y%m%d_%H%M").zip"
|
FILENAME="$(date +"%Y%m%d_%H%M").zip"
|
||||||
BACKUP_DIR="/BACKUP/$UUID"
|
BACKUP_DIR="/BACKUP/$CONTAINER_ID"
|
||||||
BB_DIR="$BACKUP_DIR/bb"
|
BB_DIR="$BACKUP_DIR/bb"
|
||||||
|
|
||||||
# Ensure backup directories exist (requires elevated permissions like other ops)
|
# Ensure backup directories exist (requires elevated permissions like other ops)
|
||||||
@@ -27,12 +33,12 @@ doas mkdir -p "$BB_DIR"
|
|||||||
|
|
||||||
# Perform database dump inside the container
|
# Perform database dump inside the container
|
||||||
log "Starting dump -> $BACKUP_DIR/$FILENAME"
|
log "Starting dump -> $BACKUP_DIR/$FILENAME"
|
||||||
doas docker exec "$UUID" odoo db \
|
doas docker exec "$CONTAINER_ID" odoo db \
|
||||||
--db_host "$POSTGRES_HOST" \
|
--db_host "$POSTGRES_HOST" \
|
||||||
-r "$UUID" \
|
-r "$DB_NAME" \
|
||||||
-w "$ODOO_DB_PASSWORD" \
|
-w "$ODOO_DB_PASSWORD" \
|
||||||
--data-dir /home/odoo/.local/share/Odoo/ \
|
--data-dir /home/odoo/.local/share/Odoo/ \
|
||||||
dump "$UUID" "/mnt/backup/$FILENAME"
|
dump "$DB_NAME" "/mnt/backup/$FILENAME"
|
||||||
|
|
||||||
log "Dump complete: $(doas du -sh "$BACKUP_DIR/$FILENAME" | cut -f1)"
|
log "Dump complete: $(doas du -sh "$BACKUP_DIR/$FILENAME" | cut -f1)"
|
||||||
|
|
||||||
@@ -40,7 +46,7 @@ log "Dump complete: $(doas du -sh "$BACKUP_DIR/$FILENAME" | cut -f1)"
|
|||||||
doas chmod 666 "$BACKUP_DIR"/[0-9]*.zip
|
doas chmod 666 "$BACKUP_DIR"/[0-9]*.zip
|
||||||
|
|
||||||
# Hand ownership back to odoo inside the container
|
# Hand ownership back to odoo inside the container
|
||||||
doas docker exec "$UUID" chown odoo:odoo -R /mnt/backup
|
doas docker exec "$CONTAINER_ID" chown odoo:odoo -R /mnt/backup
|
||||||
|
|
||||||
# Update the current.zip symlink inside bb/ pointing at the backup just created.
|
# Update the current.zip symlink inside bb/ pointing at the backup just created.
|
||||||
# Target is relative (../$FILENAME) because the symlink lives one level deeper
|
# Target is relative (../$FILENAME) because the symlink lives one level deeper
|
||||||
@@ -97,10 +103,10 @@ done
|
|||||||
log "Backup finished successfully ."
|
log "Backup finished successfully ."
|
||||||
|
|
||||||
# --- Custom modules snapshot ---
|
# --- Custom modules snapshot ---
|
||||||
# Archive /4server/data/$UUID/git-server/repos/local/custom/ into .current.tar.gz.
|
# Archive /4server/data/$CONTAINER_ID/git-server/repos/local/custom/ into .current.tar.gz.
|
||||||
# Rebuilt only when at least one file inside is newer than the existing archive,
|
# Rebuilt only when at least one file inside is newer than the existing archive,
|
||||||
# so borg backup dedup sees an identical blob on unchanged runs.
|
# so borg backup dedup sees an identical blob on unchanged runs.
|
||||||
CUSTOM_SRC="/4server/data/$UUID/git-server/repos/local/custom"
|
CUSTOM_SRC="/4server/data/$CONTAINER_ID/git-server/repos/local/custom"
|
||||||
CUSTOM_ARCHIVE="$BB_DIR/current_modules.tar.gz"
|
CUSTOM_ARCHIVE="$BB_DIR/current_modules.tar.gz"
|
||||||
|
|
||||||
if [ ! -d "$CUSTOM_SRC" ]; then
|
if [ ! -d "$CUSTOM_SRC" ]; then
|
||||||
@@ -110,7 +116,7 @@ elif [ ! -f "$CUSTOM_ARCHIVE" ] || \
|
|||||||
log "Changes detected in custom modules — rebuilding current.tar.gz"
|
log "Changes detected in custom modules — rebuilding current.tar.gz"
|
||||||
# Write to a temp file first; mv is atomic, so borg never sees a partial archive
|
# Write to a temp file first; mv is atomic, so borg never sees a partial archive
|
||||||
doas tar -czf "${CUSTOM_ARCHIVE}.tmp" \
|
doas tar -czf "${CUSTOM_ARCHIVE}.tmp" \
|
||||||
-C "/4server/data/$UUID/git-server/repos/local" custom
|
-C "/4server/data/$CONTAINER_ID/git-server/repos/local" custom
|
||||||
doas mv "${CUSTOM_ARCHIVE}.tmp" "$CUSTOM_ARCHIVE"
|
doas mv "${CUSTOM_ARCHIVE}.tmp" "$CUSTOM_ARCHIVE"
|
||||||
log ".current.tar.gz updated: $(doas du -sh "$CUSTOM_ARCHIVE" | cut -f1)"
|
log ".current.tar.gz updated: $(doas du -sh "$CUSTOM_ARCHIVE" | cut -f1)"
|
||||||
else
|
else
|
||||||
|
|||||||
+9
-7
@@ -1,18 +1,20 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
exec >> /4server/data/log/backupAll.log 2>&1
|
exec >> /4server/data/log/backupAll.log 2>&1
|
||||||
echo "$(date '+%Y-%m-%d %H:%M') Starting backupAll"
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")"
|
||||||
|
source "$SBIN/helpers"
|
||||||
|
log "Starting backupAll"
|
||||||
|
|
||||||
BIN_PATH="/4server/sbin"
|
BIN_PATH="$SBIN"
|
||||||
|
|
||||||
containers=$(docker ps --format '{{.Names}}' | grep -E '^[0-9]{3}-[0-9]{3}')
|
containers=$(docker ps --format '{{.Names}}' | grep -v '^traefik$' | grep -v '^beedb$')
|
||||||
|
|
||||||
if [[ -z "$containers" ]]; then
|
if [ -z "$containers" ]; then
|
||||||
echo "No containers matching pattern NNN-NNN found."
|
log "No managed container_ids found."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Found containers:"
|
log "Triggering backup for:"
|
||||||
while IFS= read -r container; do
|
while IFS= read -r container; do
|
||||||
echo " $container"
|
log " $container"
|
||||||
touch "/4server/data/$container/cc/backup"
|
touch "/4server/data/$container/cc/backup"
|
||||||
done <<< "$containers"
|
done <<< "$containers"
|
||||||
+13
-29
@@ -1,45 +1,29 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
exec >> /4server/data/log/backupContainer.log 2>&1
|
exec >> /4server/data/log/backupContainer.log 2>&1
|
||||||
echo "$(date '+%Y-%m-%d %H:%M') Backup container $1"
|
|
||||||
|
|
||||||
source /4server/sbin/helpers
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")"
|
||||||
|
source "$SBIN/helpers"
|
||||||
|
|
||||||
BIN_PATH="/4server/sbin"
|
BIN_PATH="$SBIN"
|
||||||
|
|
||||||
UUID="$1"
|
CONTAINER_ID="$1"
|
||||||
|
|
||||||
if [[ -z "$UUID" ]]; then
|
[ -z "$CONTAINER_ID" ] && die "Usage: $0 <uuid>"
|
||||||
echo "Usage: $0 <uuid>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
load_container_config "$CONTAINER_ID"
|
||||||
get_contract_info
|
get_contract_info
|
||||||
|
|
||||||
# Extract the second part of UUID (split by "-")
|
log "Backup container $CONTAINER_ID (SOFTWARE=$SOFTWARE)"
|
||||||
SECOND_PART=$(echo "$UUID" | cut -d'-' -f2)
|
|
||||||
|
|
||||||
echo "Conatiner Type $UUID"
|
# Decide which script to run based on SOFTWARE from container.env
|
||||||
|
case "$SOFTWARE" in
|
||||||
# Decide which script to run
|
n8n)
|
||||||
case "$SECOND_PART" in
|
|
||||||
001)
|
|
||||||
"$BIN_PATH/backup/N8N"
|
"$BIN_PATH/backup/N8N"
|
||||||
;;
|
;;
|
||||||
002)
|
ODOO_17|ODOO_18|ODOO_19)
|
||||||
"$BIN_PATH/backup/ODOO"
|
"$BIN_PATH/backup/ODOO"
|
||||||
;;
|
;;
|
||||||
003)
|
*)
|
||||||
"$BIN_PATH/backup/ODOO"
|
die "Unknown SOFTWARE type '${SOFTWARE}' for container_id $CONTAINER_ID — run: initContainer $CONTAINER_ID <n8n|ODOO_17|ODOO_18|ODOO_19>"
|
||||||
;;
|
|
||||||
004)
|
|
||||||
"$BIN_PATH/backup/ODOO"
|
|
||||||
;;
|
|
||||||
005)
|
|
||||||
"$BIN_PATH/backup/ODOO"
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
echo "Unknown UUID type: $SECOND_PART"
|
|
||||||
exit 2
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
+27
-91
@@ -1,108 +1,44 @@
|
|||||||
#!/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 <container_id>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def check_backup(uuid):
|
container_id = sys.argv[1]
|
||||||
"""Return (ok, message) for the given UUID."""
|
directory = BACKUP_ROOT / container_id
|
||||||
directory = BACKUP_ROOT / uuid
|
|
||||||
|
|
||||||
if not directory.is_dir():
|
if not directory.is_dir():
|
||||||
return False, f"ERROR: directory not found for UUID {uuid}"
|
print(f"ERROR: directory not found for container_id {container_id}")
|
||||||
|
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"):
|
||||||
date_part = zip_file.name.split("_")[0]
|
name = zip_file.name
|
||||||
|
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:
|
||||||
return False, "NO BACKUPS"
|
print("NO BACKUPS")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if latest_file.stat().st_size < 1000:
|
if latest_file.stat().st_size < 1000:
|
||||||
return True, "01-01-2000"
|
print("01-01-2000")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# Format as yyyy-mm-dd
|
# Format as yyyy-mm-dd
|
||||||
return True, f"{latest_date[:4]}-{latest_date[4:6]}-{latest_date[6:8]}"
|
print(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()
|
|
||||||
+16
-9
@@ -1,36 +1,43 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")"
|
||||||
|
source "$SBIN/helpers"
|
||||||
|
|
||||||
cd /4server/data/
|
cd /4server/data/
|
||||||
|
|
||||||
while :
|
while :
|
||||||
do
|
do
|
||||||
|
|
||||||
for dir in ???-???-*; do
|
# nullglob: if no subdirectories exist the loop body is skipped entirely
|
||||||
|
shopt -s nullglob
|
||||||
|
for dir in */; do
|
||||||
|
dir="${dir%/}"
|
||||||
|
[ -z "$dir" ] && continue
|
||||||
if [ -d "${dir}/cc" ]; then
|
if [ -d "${dir}/cc" ]; then
|
||||||
if [ -f "${dir}/cc/backup" ]; then
|
if [ -f "${dir}/cc/backup" ]; then
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') BACKUP for: ${dir%/}"
|
log "BACKUP for: $dir"
|
||||||
/4server/sbin/backupContainer ${dir%/} 2
|
"$SBIN/backupContainer" "$dir"
|
||||||
rm "${dir}/cc/backup"
|
rm "${dir}/cc/backup"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "${dir}/cc/restart" ]; then
|
if [ -f "${dir}/cc/restart" ]; then
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') Restart for: ${dir%/}"
|
log "Restart for: $dir"
|
||||||
/4server/sbin/startContainer ${dir%/}
|
"$SBIN/startContainer" "$dir"
|
||||||
rm "${dir}/cc/restart"
|
rm "${dir}/cc/restart"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "${dir}/cc/restore" ]; then
|
if [ -f "${dir}/cc/restore" ]; then
|
||||||
FILENAME=$(head -n 1 "${dir}/cc/restore")
|
FILENAME=$(head -n 1 "${dir}/cc/restore")
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') Restore for: ${dir%/} - $FILENAME"
|
log "Restore for: $dir - $FILENAME"
|
||||||
/4server/sbin/ODOO_19/restore ${dir%/} $FILENAME
|
"$SBIN/odoo/restore" "$dir" "$FILENAME"
|
||||||
rm "${dir}/cc/restore"
|
rm "${dir}/cc/restore"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -d "${dir}/n8n" ]; then
|
if [ -d "${dir}/n8n" ]; then
|
||||||
if [ -f "${dir}/n8n/database.sqlite-journal" ]; then
|
if [ -f "${dir}/n8n/database.sqlite-journal" ]; then
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') Restart N8N (journal) for: ${dir%/}"
|
log "Restart N8N (journal) for: $dir"
|
||||||
/4server/sbin/startContainer ${dir%/}
|
"$SBIN/startContainer" "$dir"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+16
-16
@@ -1,23 +1,23 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# checkFilestore <UUID> — compare container filestore vs host disk via md5
|
# checkFilestore <CONTAINER_ID> — compare container filestore vs host disk via md5
|
||||||
|
|
||||||
check_one() {
|
check_one() {
|
||||||
local UUID="$1"
|
local CONTAINER_ID="$1"
|
||||||
|
|
||||||
FILESTORE_HOST="/4server/data/$UUID/odoo/odoo-web-data/filestore/$UUID"
|
FILESTORE_HOST="/4server/data/$CONTAINER_ID/odoo/odoo-web-data/filestore/$CONTAINER_ID"
|
||||||
|
|
||||||
[[ -d "$FILESTORE_HOST" ]] || { echo "ERROR: host filestore not found: $FILESTORE_HOST"; exit 2; }
|
[[ -d "$FILESTORE_HOST" ]] || { echo "ERROR: host filestore not found: $FILESTORE_HOST"; exit 2; }
|
||||||
doas docker ps --format "{{.Names}}" | grep -q "^${UUID}$" \
|
doas docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_ID}$" \
|
||||||
|| { echo "ERROR: container $UUID is not running"; exit 2; }
|
|| { echo "ERROR: container $CONTAINER_ID is not running"; exit 2; }
|
||||||
|
|
||||||
# Read data_dir from odoo.conf inside the container, fall back to default
|
# Read data_dir from odoo.conf inside the container, fall back to default
|
||||||
DATA_DIR=$(doas docker exec "$UUID" sh -c \
|
DATA_DIR=$(doas docker exec "$CONTAINER_ID" sh -c \
|
||||||
'for f in /etc/odoo.conf /etc/odoo/odoo.conf /opt/odoo/etc/odoo.conf; do
|
'for f in /etc/odoo.conf /etc/odoo/odoo.conf /opt/odoo/etc/odoo.conf; do
|
||||||
grep -E "^data_dir" "$f" 2>/dev/null | cut -d= -f2- | xargs && break
|
grep -E "^data_dir" "$f" 2>/dev/null | cut -d= -f2- | xargs && break
|
||||||
done')
|
done')
|
||||||
DATA_DIR="${DATA_DIR:-/home/odoo/.local/share/Odoo}"
|
DATA_DIR="${DATA_DIR:-/home/odoo/.local/share/Odoo}"
|
||||||
FILESTORE_CONTAINER="$DATA_DIR/filestore/$UUID"
|
FILESTORE_CONTAINER="$DATA_DIR/filestore/$CONTAINER_ID"
|
||||||
MOUNT_HOST=$(doas docker inspect "$UUID" --format '{{json .Mounts}}' \
|
MOUNT_HOST=$(doas docker inspect "$CONTAINER_ID" --format '{{json .Mounts}}' \
|
||||||
| jq -r --arg dest "$DATA_DIR" '.[] | select(.Type=="bind") | select(.Destination==$dest) | .Source')
|
| jq -r --arg dest "$DATA_DIR" '.[] | select(.Type=="bind") | select(.Destination==$dest) | .Source')
|
||||||
echo "ODOO USES : $FILESTORE_CONTAINER"
|
echo "ODOO USES : $FILESTORE_CONTAINER"
|
||||||
echo "Docker maps to : $DATA_DIR"
|
echo "Docker maps to : $DATA_DIR"
|
||||||
@@ -25,7 +25,7 @@ echo ""
|
|||||||
echo "Docker mapped to : ${MOUNT_HOST:-<no matching mount found>}"
|
echo "Docker mapped to : ${MOUNT_HOST:-<no matching mount found>}"
|
||||||
|
|
||||||
echo "Hashing container filestore..."
|
echo "Hashing container filestore..."
|
||||||
MD5_CONTAINER=$(doas docker exec "$UUID" sh -c \
|
MD5_CONTAINER=$(doas docker exec "$CONTAINER_ID" sh -c \
|
||||||
"find $FILESTORE_CONTAINER -type f | sort | xargs md5sum 2>/dev/null | awk '{print \$1}'" | md5sum | cut -d' ' -f1)
|
"find $FILESTORE_CONTAINER -type f | sort | xargs md5sum 2>/dev/null | awk '{print \$1}'" | md5sum | cut -d' ' -f1)
|
||||||
|
|
||||||
echo "Hashing host filestore..."
|
echo "Hashing host filestore..."
|
||||||
@@ -41,18 +41,18 @@ if [[ -n "${1-}" ]]; then
|
|||||||
check_one "$1"
|
check_one "$1"
|
||||||
else
|
else
|
||||||
for dir in /4server/data/00*/; do
|
for dir in /4server/data/00*/; do
|
||||||
UUID=$(basename "$dir")
|
CONTAINER_ID=$(basename "$dir")
|
||||||
result=$(check_one "$UUID" 2>&1)
|
result=$(check_one "$CONTAINER_ID" 2>&1)
|
||||||
if echo "$result" | grep -q "OK: filestores match"; then
|
if echo "$result" | grep -q "OK: filestores match"; then
|
||||||
echo "$UUID OK"
|
echo "$CONTAINER_ID OK"
|
||||||
elif echo "$result" | grep -q "not running"; then
|
elif echo "$result" | grep -q "not running"; then
|
||||||
echo "$UUID NOT RUNNING"
|
echo "$CONTAINER_ID NOT RUNNING"
|
||||||
elif echo "$result" | grep -q "host filestore not found"; then
|
elif echo "$result" | grep -q "host filestore not found"; then
|
||||||
echo "$UUID NO FILESTORE"
|
echo "$CONTAINER_ID NO FILESTORE"
|
||||||
elif echo "$result" | grep -q "cannot query DB"; then
|
elif echo "$result" | grep -q "cannot query DB"; then
|
||||||
echo "$UUID DB ERROR"
|
echo "$CONTAINER_ID DB ERROR"
|
||||||
else
|
else
|
||||||
echo "$UUID MISMATCH"
|
echo "$CONTAINER_ID MISMATCH"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
export PATH=/4PROJECTS/bin:$PATH
|
export PATH=/4PROJECTS/bin:$PATH
|
||||||
if [ ! -n "$1" ]; then
|
if [ ! -n "$1" ]; then
|
||||||
echo "Missing Parameters <UUID>"
|
echo "Missing Parameters <CONTAINER_ID>"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
UUID=$1
|
CONTAINER_ID=$1
|
||||||
echo "UUID: $UUID"
|
echo "CONTAINER_ID: $CONTAINER_ID"
|
||||||
|
|
||||||
source /4server/sbin/helpers
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")"
|
||||||
|
source "$SBIN/helpers"
|
||||||
|
|
||||||
get_contract_info
|
get_contract_info
|
||||||
|
|
||||||
|
|||||||
+55
-26
@@ -1,5 +1,48 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Logging — use these everywhere instead of bare echo
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] $*"; }
|
||||||
|
warn() { echo "$(date '+%Y-%m-%d %H:%M:%S') [WARN] $*" >&2; }
|
||||||
|
die() { echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Resolve the absolute directory of a script.
|
||||||
|
# Always pass ${BASH_SOURCE[0]} — it holds the real path even when the
|
||||||
|
# script is invoked via PATH (unlike $0 which may be just a bare name).
|
||||||
|
# Usage in top-level scripts: SBIN="$(sbin_dir "${BASH_SOURCE[0]}")"
|
||||||
|
# Sub-scripts one level deep: SBIN_ROOT="$(dirname "$(sbin_dir "${BASH_SOURCE[0]}")")"
|
||||||
|
sbin_dir() { dirname "$(realpath "$1")"; }
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# load_container_config <container_id>
|
||||||
|
# Sources /4server/data/<container_id>/config/container.env and exports:
|
||||||
|
# SOFTWARE — n8n | ODOO_17 | ODOO_18 | ODOO_19
|
||||||
|
# DB_NAME — postgres database/user (may differ from CONTAINER_ID for migrations)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
load_container_config() {
|
||||||
|
local container_id="${1:-$CONTAINER_ID}"
|
||||||
|
local cfg="/4server/data/$container_id/config/container.env"
|
||||||
|
if [ -f "$cfg" ]; then
|
||||||
|
while IFS= read -r line; do
|
||||||
|
case "$line" in
|
||||||
|
''|'#'*) continue ;;
|
||||||
|
esac
|
||||||
|
local _k="${line%%=*}"
|
||||||
|
local _v="${line#*=}"
|
||||||
|
case "$_k" in
|
||||||
|
*[!A-Z_]*) continue ;;
|
||||||
|
esac
|
||||||
|
export "$_k=$_v"
|
||||||
|
done < "$cfg"
|
||||||
|
else
|
||||||
|
warn "no container.env found at $cfg"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Postgres defaults (overridable via environment)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
POSTGRES_HOST="${POSTGRES_HOST:-beedb}"
|
POSTGRES_HOST="${POSTGRES_HOST:-beedb}"
|
||||||
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
||||||
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
|
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
|
||||||
@@ -7,19 +50,19 @@ POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
|
|||||||
|
|
||||||
DB_PATH="/4server/data/contracts.db"
|
DB_PATH="/4server/data/contracts.db"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# get_contract_info
|
||||||
|
# Reads the contracts.db row for $CONTAINER_ID and exports all columns as env vars.
|
||||||
|
# Also parses SECRET_LIST and exports ODOO_DB_PASSWORD from the psql key.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
get_contract_info() {
|
get_contract_info() {
|
||||||
|
log "get_contract_info $CONTAINER_ID"
|
||||||
|
|
||||||
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
|
|
||||||
while IFS="=" read -r key value; do
|
while IFS="=" read -r key value; do
|
||||||
if [ -n "$key" ]; then
|
[ -n "$key" ] && export "$key=$value"
|
||||||
export "$key=$value"
|
|
||||||
fi
|
|
||||||
done < <(sqlite3 "$DB_PATH" "
|
done < <(sqlite3 "$DB_PATH" "
|
||||||
WITH c AS (SELECT * FROM containers WHERE UUID='$UUID' LIMIT 1)
|
WITH c AS (SELECT * FROM containers WHERE UUID='$CONTAINER_ID' LIMIT 1)
|
||||||
SELECT 'UUID=' || COALESCE(UUID, '') FROM c UNION ALL
|
SELECT 'CONTAINER_ID=' || COALESCE(UUID, '') FROM c UNION ALL
|
||||||
SELECT 'EXPIRES=' || COALESCE(expires, '') FROM c UNION ALL
|
SELECT 'EXPIRES=' || COALESCE(expires, '') FROM c UNION ALL
|
||||||
SELECT 'IMAGE=' || COALESCE(image, '') FROM c UNION ALL
|
SELECT 'IMAGE=' || COALESCE(image, '') FROM c UNION ALL
|
||||||
SELECT 'STATUS=' || COALESCE(status, '') FROM c UNION ALL
|
SELECT 'STATUS=' || COALESCE(status, '') FROM c UNION ALL
|
||||||
@@ -33,28 +76,14 @@ get_contract_info() {
|
|||||||
SELECT 'DOMAINS_SLOTS=' || COALESCE(domains_slots, '') FROM c;
|
SELECT 'DOMAINS_SLOTS=' || COALESCE(domains_slots, '') FROM c;
|
||||||
")
|
")
|
||||||
|
|
||||||
# Parse SECRET_LIST: backslash-separated key=value pairs (e.g. worex1=1\pswl=22)
|
# Parse SECRET_LIST: backslash-separated key=value pairs
|
||||||
# - printf avoids echo interpreting escape sequences
|
# %%=* / #*= split on the FIRST = only, so base64 values like tok=ab== are safe
|
||||||
# - tr -d '\r\n' strips any embedded newlines the DB value may contain
|
|
||||||
# - tr '\\' '\n' turns each backslash separator into a real newline
|
|
||||||
# - %%=* / #*= split on the FIRST = only, so base64 values like tok=ab== are safe
|
|
||||||
if [ -n "$SECRET_LIST" ]; then
|
if [ -n "$SECRET_LIST" ]; then
|
||||||
while IFS= read -r pair || [ -n "$pair" ]; do
|
while IFS= read -r pair || [ -n "$pair" ]; do
|
||||||
_key="${pair%%=*}"
|
_key="${pair%%=*}"
|
||||||
_value="${pair#*=}"
|
_value="${pair#*=}"
|
||||||
if [ -n "$_key" ]; then
|
[ -n "$_key" ] && export "$_key=$_value"
|
||||||
export "$_key=$_value"
|
|
||||||
fi
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
POSTGRES_HOST="${POSTGRES_HOST:-beedb}"
|
|
||||||
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
|
||||||
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
|
|
||||||
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
|
|
||||||
|
|
||||||
DB_PATH="/4server/data/contracts.db"
|
|
||||||
|
|
||||||
|
|
||||||
get_contract_info() {
|
|
||||||
|
|
||||||
echo "get_contract_info $UUID"
|
|
||||||
|
|
||||||
# Single CTE: the table is scanned once and all projections come from it
|
|
||||||
while IFS="=" read -r key value; do
|
|
||||||
if [ -n "$key" ]; then
|
|
||||||
export "$key=$value"
|
|
||||||
fi
|
|
||||||
done < <(sqlite3 "$DB_PATH" "
|
|
||||||
WITH c AS (SELECT * FROM containers WHERE UUID='$UUID' LIMIT 1)
|
|
||||||
SELECT 'UUID=' || COALESCE(UUID, '') FROM c UNION ALL
|
|
||||||
SELECT 'EXPIRES=' || COALESCE(expires, '') FROM c UNION ALL
|
|
||||||
SELECT 'IMAGE=' || COALESCE(image, '') FROM c UNION ALL
|
|
||||||
SELECT 'STATUS=' || COALESCE(status, '') FROM c UNION ALL
|
|
||||||
SELECT 'CREATED=' || COALESCE(created, '') FROM c UNION ALL
|
|
||||||
SELECT 'SECRET_LIST=' || COALESCE(secret_list, '') FROM c UNION ALL
|
|
||||||
SELECT 'CONTAINERDBID=' || COALESCE(id, '') FROM c UNION ALL
|
|
||||||
SELECT 'GIT=' || COALESCE(git, '') FROM c UNION ALL
|
|
||||||
SELECT 'BACKUP_SLOTS=' || COALESCE(backup_slots, '') FROM c UNION ALL
|
|
||||||
SELECT 'HDD=' || COALESCE(hdd, '') FROM c UNION ALL
|
|
||||||
SELECT 'WORKERS=' || COALESCE(workers, '') FROM c UNION ALL
|
|
||||||
SELECT 'DOMAINS_SLOTS=' || COALESCE(domains_slots, '') FROM c;
|
|
||||||
")
|
|
||||||
|
|
||||||
# Parse SECRET_LIST: backslash-separated key=value pairs (e.g. worex1=1\pswl=22)
|
|
||||||
# - printf avoids echo interpreting escape sequences
|
|
||||||
# - tr -d '\r\n' strips any embedded newlines the DB value may contain
|
|
||||||
# - tr '\\' '\n' turns each backslash separator into a real newline
|
|
||||||
# - %%=* / #*= split on the FIRST = only, so base64 values like tok=ab== are safe
|
|
||||||
if [ -n "$SECRET_LIST" ]; then
|
|
||||||
while IFS= read -r pair || [ -n "$pair" ]; do
|
|
||||||
_key="${pair%%=*}"
|
|
||||||
_value="${pair#*=}"
|
|
||||||
if [ -n "$_key" ]; then
|
|
||||||
export "$_key=$_value"
|
|
||||||
fi
|
|
||||||
done < <(printf '%s' "$SECRET_LIST" | tr -d '\r\n' | tr '\\' '\n')
|
|
||||||
fi
|
|
||||||
<<<<<<< HEAD
|
|
||||||
export ODOO_DB_PASSWORD=$psql
|
|
||||||
=======
|
|
||||||
done < <(sqlite3 "$DB_PATH" "
|
|
||||||
SELECT 'UUID=' || UUID FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'EMAIL=' || email FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'EXPIRES=' || expires FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'TAGS=' || replace(replace(tags, char(10), ''), char(13), '') FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'ENV=' || replace(replace(env, char(10), ''), char(13), '') FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'IMAGE=' || image FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'DOMAINS=' || domains FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'STATUS=' || status FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'CREATED=' || created FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'SECRET_LIST=' || COALESCE(secret_list, '') FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'CONTAINERDBID=' || id FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'BUMP=' || bump FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'GIT=' || COALESCE(git, '') FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'BACKUP_SLOTS=' || COALESCE(backup_slots, '') FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'HDD=' || COALESCE(hdd, '') FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'WORKERS=' || COALESCE(workers, '') FROM containers WHERE UUID='$UUID'
|
|
||||||
UNION ALL SELECT 'DOMAINS_SLOTS=' || COALESCE(domains_slots, '') FROM containers WHERE UUID='$UUID';
|
|
||||||
")
|
|
||||||
|
|
||||||
|
|
||||||
# Parse ENV JSON into individual env vars
|
|
||||||
eval $(echo "$ENV" | jq -r 'to_entries | .[] | "export \(.key | ascii_upcase)=\(.value)"')
|
|
||||||
|
|
||||||
# Parse SECRET_LIST (backslash-separated key=value pairs, e.g. worex1=1\pswl=22)
|
|
||||||
if [ -n "$SECRET_LIST" ]; then
|
|
||||||
while IFS='=' read -r key value; do
|
|
||||||
if [ -n "$key" ]; then
|
|
||||||
export "$key=$value"
|
|
||||||
fi
|
|
||||||
done < <(echo "$SECRET_LIST" | tr '\\' '\n')
|
|
||||||
fi
|
|
||||||
|
|
||||||
>>>>>>> c583649 (hmm)
|
|
||||||
}
|
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Usage: initContainer <container_id> <software> [db_name]
|
||||||
|
#
|
||||||
|
# Creates /4server/data/<container_id>/config/container.env
|
||||||
|
#
|
||||||
|
# <container_id> : new codename (e.g. "banana")
|
||||||
|
# <software> : n8n | ODOO_17 | ODOO_18 | ODOO_19
|
||||||
|
# [db_name] : optional — postgres DB/user name to use.
|
||||||
|
# Defaults to <container_id>.
|
||||||
|
# Pass the OLD container_id here when migrating an existing container
|
||||||
|
# so the existing postgres database is reused.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# New container:
|
||||||
|
# initContainer banana ODOO_19
|
||||||
|
#
|
||||||
|
# Migrating existing container (old container_id kept as DB name):
|
||||||
|
# initContainer banana ODOO_19 005-003-91f3922c-8cc2-4950-aa57-808775c341ff
|
||||||
|
|
||||||
|
CONTAINER_ID="$1"
|
||||||
|
SOFTWARE="$2"
|
||||||
|
DB_NAME="${3:-$1}"
|
||||||
|
|
||||||
|
if [[ -z "$CONTAINER_ID" || -z "$SOFTWARE" ]]; then
|
||||||
|
echo "Usage: $0 <container_id> <software> [db_name]"
|
||||||
|
echo " software: n8n | ODOO_17 | ODOO_18 | ODOO_19"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$SOFTWARE" in
|
||||||
|
n8n|ODOO_17|ODOO_18|ODOO_19) ;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Unknown software type: $SOFTWARE"
|
||||||
|
echo " Valid values: n8n | ODOO_17 | ODOO_18 | ODOO_19"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CONFIG_DIR="/4server/data/$CONTAINER_ID/config"
|
||||||
|
mkdir -p "$CONFIG_DIR"
|
||||||
|
|
||||||
|
cat > "$CONFIG_DIR/container.env" <<EOF
|
||||||
|
SOFTWARE=$SOFTWARE
|
||||||
|
DB_NAME=$DB_NAME
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Created $CONFIG_DIR/container.env"
|
||||||
|
echo " SOFTWARE=$SOFTWARE"
|
||||||
|
echo " DB_NAME=$DB_NAME"
|
||||||
+122
-12
@@ -1,16 +1,126 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Migration script — run once per server after deploying updated code.
|
||||||
|
# Safe to re-run: each step checks whether it is needed before acting.
|
||||||
|
|
||||||
sqlite3 /4server/data/contracts.db <<EOF
|
DB="/4server/data/contracts.db"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Helper: check whether a column exists
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
has_column() {
|
||||||
|
sqlite3 "$DB" "SELECT COUNT(*) FROM pragma_table_info('containers') WHERE name='$1';"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 1 — Add columns introduced over time (silently skip if present)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
echo "Step 1: adding missing columns..."
|
||||||
|
sqlite3 "$DB" 2>/dev/null <<'EOF' || true
|
||||||
ALTER TABLE containers ADD COLUMN env_list TEXT;
|
ALTER TABLE containers ADD COLUMN env_list TEXT;
|
||||||
UPDATE containers
|
|
||||||
SET env_list = (
|
|
||||||
SELECT group_concat(key || '=' || value, ', ')
|
|
||||||
FROM json_each(containers.env)
|
|
||||||
);
|
|
||||||
ALTER TABLE containers ADD COLUMN secret_list TEXT;
|
|
||||||
UPDATE containers
|
|
||||||
SET secret_list = (
|
|
||||||
SELECT group_concat(key || '=' || value, ', ')
|
|
||||||
FROM json_each(containers.secret)
|
|
||||||
);
|
|
||||||
EOF
|
EOF
|
||||||
|
sqlite3 "$DB" 2>/dev/null <<'EOF' || true
|
||||||
|
ALTER TABLE containers ADD COLUMN secret_list TEXT;
|
||||||
|
EOF
|
||||||
|
sqlite3 "$DB" 2>/dev/null <<'EOF' || true
|
||||||
|
ALTER TABLE containers ADD COLUMN utm_source text;
|
||||||
|
EOF
|
||||||
|
sqlite3 "$DB" 2>/dev/null <<'EOF' || true
|
||||||
|
ALTER TABLE containers ADD COLUMN utm_medium text;
|
||||||
|
EOF
|
||||||
|
sqlite3 "$DB" 2>/dev/null <<'EOF' || true
|
||||||
|
ALTER TABLE containers ADD COLUMN utm_campaign text;
|
||||||
|
EOF
|
||||||
|
sqlite3 "$DB" 2>/dev/null <<'EOF' || true
|
||||||
|
ALTER TABLE containers ADD COLUMN container_id CHAR(50);
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 2 — Backfill container_id from UUID
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
echo "Step 2: backfilling container_id from UUID..."
|
||||||
|
sqlite3 "$DB" <<'EOF'
|
||||||
|
UPDATE containers
|
||||||
|
SET container_id = UUID
|
||||||
|
WHERE container_id IS NULL OR container_id = '';
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 3 — Backfill utm_* from old affiliate JSON (if column still exists)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if [ "$(has_column affiliate)" = "1" ]; then
|
||||||
|
echo "Step 3: backfilling utm fields from affiliate JSON..."
|
||||||
|
sqlite3 "$DB" <<'EOF'
|
||||||
|
UPDATE containers
|
||||||
|
SET utm_source = COALESCE(utm_source, json_extract(affiliate, '$.utm_source')),
|
||||||
|
utm_medium = COALESCE(utm_medium, json_extract(affiliate, '$.utm_medium')),
|
||||||
|
utm_campaign = COALESCE(utm_campaign, json_extract(affiliate, '$.utm_campaign'))
|
||||||
|
WHERE affiliate IS NOT NULL AND affiliate != '';
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
echo "Step 3: affiliate column not present — skipping utm backfill."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 4 — Remove affiliate column via full table rebuild.
|
||||||
|
# Also adds container_id UNIQUE constraint and locks in the canonical
|
||||||
|
# column order to match init_db().
|
||||||
|
# Only runs if affiliate still exists OR container_id lacks its UNIQUE index.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
NEEDS_REBUILD=0
|
||||||
|
[ "$(has_column affiliate)" = "1" ] && NEEDS_REBUILD=1
|
||||||
|
|
||||||
|
# If affiliate is already gone, the rebuild already ran — no need to repeat
|
||||||
|
# (container_id UNIQUE is guaranteed by the rebuild itself)
|
||||||
|
|
||||||
|
if [ "$NEEDS_REBUILD" = "1" ]; then
|
||||||
|
echo "Step 4: rebuilding table (remove affiliate, add container_id UNIQUE)..."
|
||||||
|
sqlite3 "$DB" <<'EOF'
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE containers_new (
|
||||||
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
UUID CHAR(50) UNIQUE,
|
||||||
|
container_id CHAR(50) UNIQUE,
|
||||||
|
email CHAR(100),
|
||||||
|
expires DATE,
|
||||||
|
tags TEXT,
|
||||||
|
env TEXT,
|
||||||
|
image CHAR(50),
|
||||||
|
history TEXT,
|
||||||
|
comment TEXT,
|
||||||
|
domains TEXT,
|
||||||
|
status CHAR(20),
|
||||||
|
created DATE,
|
||||||
|
bump DATE,
|
||||||
|
secret TEXT,
|
||||||
|
contract TEXT,
|
||||||
|
git integer,
|
||||||
|
backup_slots integer,
|
||||||
|
hdd integer,
|
||||||
|
workers integer,
|
||||||
|
utm_source text,
|
||||||
|
utm_medium text,
|
||||||
|
utm_campaign text,
|
||||||
|
domains_slots integer,
|
||||||
|
secret_list TEXT,
|
||||||
|
env_list TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO containers_new
|
||||||
|
SELECT ID, UUID, container_id, email, expires, tags, env, image, history,
|
||||||
|
comment, domains, status, created, bump, secret, contract, git,
|
||||||
|
backup_slots, hdd, workers, utm_source, utm_medium, utm_campaign,
|
||||||
|
domains_slots, secret_list, env_list
|
||||||
|
FROM containers;
|
||||||
|
|
||||||
|
DROP TABLE containers;
|
||||||
|
ALTER TABLE containers_new RENAME TO containers;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
EOF
|
||||||
|
echo "Step 4: table rebuilt."
|
||||||
|
else
|
||||||
|
echo "Step 4: table already up to date — skipping rebuild."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Migration complete."
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Load functions
|
|
||||||
source /4server/sbin/ODOO_19/ODOO_19.lib
|
|
||||||
if [[ -z "$UUID" ]]; then
|
|
||||||
echo "Error: UUID not set. Aborting."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
POSTGRES_HOST="${POSTGRES_HOST:-beedb}"
|
|
||||||
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
|
||||||
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
|
|
||||||
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
|
|
||||||
ODOO_DB_USER="${UUID}"
|
|
||||||
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
|
||||||
|
|
||||||
BASEURL="${BASEURL:-/4server/data/$UUID}"
|
|
||||||
BACKUPURL="/4backup/$UUID"
|
|
||||||
|
|
||||||
doas docker stop "$UUID"
|
|
||||||
doas docker rm "$UUID"
|
|
||||||
|
|
||||||
if [ -n "${UUID:-}" ]; then
|
|
||||||
echo "Removing directory: $BASEURL"
|
|
||||||
#doas rm -rf "$BASEURL"
|
|
||||||
echo "Removing backup directory $BACKUPURL"
|
|
||||||
#doas rm -rf $BACKUPURL
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
echo "Dropping PostgreSQL database: $UUID (if exists)..."
|
|
||||||
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
|
|
||||||
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
|
|
||||||
-c "DROP DATABASE IF EXISTS \"$UUID\";"
|
|
||||||
|
|
||||||
echo "Dropping PostgreSQL user: $ODOO_DB_USER (if exists)..."
|
|
||||||
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
|
|
||||||
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres <<EOF
|
|
||||||
DO
|
|
||||||
\$do\$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (
|
|
||||||
SELECT
|
|
||||||
FROM pg_catalog.pg_user
|
|
||||||
WHERE usename = '${ODOO_DB_USER}') THEN
|
|
||||||
|
|
||||||
-- Drop user safely
|
|
||||||
EXECUTE 'DROP USER "' || '${ODOO_DB_USER}' || '"';
|
|
||||||
END IF;
|
|
||||||
END
|
|
||||||
\$do\$;
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "✅ Database '$UUID' and user '$ODOO_DB_USER' removed (if they existed)."
|
|
||||||
Executable
+54
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Unified Odoo nuke script (all versions).
|
||||||
|
# CONTAINER_ID, DB_NAME, SOFTWARE already exported by nukeContainer.
|
||||||
|
|
||||||
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of nuke/
|
||||||
|
source "$SBIN_ROOT/helpers"
|
||||||
|
source "$SBIN_ROOT/odoo.lib"
|
||||||
|
|
||||||
|
[ -z "$CONTAINER_ID" ] && die "CONTAINER_ID not set"
|
||||||
|
|
||||||
|
load_container_config "$CONTAINER_ID"
|
||||||
|
|
||||||
|
POSTGRES_HOST="${POSTGRES_HOST:-beedb}"
|
||||||
|
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
||||||
|
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
|
||||||
|
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
|
||||||
|
|
||||||
|
DB_NAME="${DB_NAME:-$CONTAINER_ID}"
|
||||||
|
ODOO_DB_USER="$DB_NAME"
|
||||||
|
|
||||||
|
BASEURL="/4server/data/$CONTAINER_ID"
|
||||||
|
BACKUPURL="/BACKUP/$CONTAINER_ID"
|
||||||
|
|
||||||
|
log "Nuking container $CONTAINER_ID (DB_NAME=$DB_NAME)"
|
||||||
|
|
||||||
|
doas docker stop "$CONTAINER_ID" || warn "docker stop failed (may already be stopped)"
|
||||||
|
doas docker rm "$CONTAINER_ID" || warn "docker rm failed (may not exist)"
|
||||||
|
|
||||||
|
log "Dropping PostgreSQL database: $DB_NAME"
|
||||||
|
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
|
||||||
|
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
|
||||||
|
-c "DROP DATABASE IF EXISTS \"$DB_NAME\";" \
|
||||||
|
|| die "failed to drop database $DB_NAME"
|
||||||
|
|
||||||
|
log "Dropping PostgreSQL user: $ODOO_DB_USER"
|
||||||
|
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
|
||||||
|
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres <<EOF
|
||||||
|
DO
|
||||||
|
\$do\$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT FROM pg_catalog.pg_user WHERE usename = '${ODOO_DB_USER}') THEN
|
||||||
|
EXECUTE 'DROP USER "' || '${ODOO_DB_USER}' || '"';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
\$do\$;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log "Database '$DB_NAME' and user '$ODOO_DB_USER' removed"
|
||||||
|
|
||||||
|
# Uncomment when ready to also wipe data directories:
|
||||||
|
# log "Removing data directory: $BASEURL"
|
||||||
|
# doas rm -rf "$BASEURL"
|
||||||
|
# log "Removing backup directory: $BACKUPURL"
|
||||||
|
# doas rm -rf "$BACKUPURL"
|
||||||
+20
-39
@@ -1,68 +1,49 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Usage: ./start_by_uuid.sh <uuid>
|
|
||||||
# Example: ./start_by_uuid.sh abc-001-xxxx-xxxx-xxxx
|
|
||||||
exec > /4server/data/log/nukeContainer.log 2>&1
|
exec > /4server/data/log/nukeContainer.log 2>&1
|
||||||
echo "$(date '+%Y-%m-%d %H:%M') Nuke container $1"
|
|
||||||
|
|
||||||
source /4server/sbin/helpers
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")"
|
||||||
|
source "$SBIN/helpers"
|
||||||
|
|
||||||
BIN_PATH="/4server/sbin"
|
BIN_PATH="$SBIN"
|
||||||
|
CONTAINER_ID="$1"
|
||||||
|
|
||||||
UUID="$1"
|
[ -z "$CONTAINER_ID" ] && die "Usage: $0 <uuid>"
|
||||||
|
log "Nuke container $CONTAINER_ID"
|
||||||
if [[ -z "$UUID" ]]; then
|
|
||||||
echo "Usage: $0 <uuid>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
get_container_status() {
|
get_container_status() {
|
||||||
local uuid="$1"
|
local container_id="$1"
|
||||||
# Get the container ID or name matching the UUID
|
# Get the Docker container ID or name matching the container_id
|
||||||
CONTAINER_ID=$(docker ps -a --filter "name=$uuid" --format "{{.ID}}")
|
DOCKER_ID=$(docker ps -a --filter "name=$container_id" --format "{{.ID}}")
|
||||||
if [[ -z "$CONTAINER_ID" ]]; then
|
if [[ -z "$DOCKER_ID" ]]; then
|
||||||
echo "not_found"
|
echo "not_found"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
STATUS=$(docker inspect -f '{{.State.Status}}' "$CONTAINER_ID")
|
STATUS=$(docker inspect -f '{{.State.Status}}' "$DOCKER_ID")
|
||||||
echo "$STATUS"
|
echo "$STATUS"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if container exists
|
STATUS=$(get_container_status "$CONTAINER_ID")
|
||||||
STATUS=$(get_container_status "$UUID")
|
[ "$STATUS" = "running" ] && die "Container $CONTAINER_ID is still running — stop it first"
|
||||||
if [[ "$STATUS" == "running" ]]; then
|
|
||||||
echo "Container $UUID is still running. Aborting deletion."
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
load_container_config "$CONTAINER_ID"
|
||||||
get_contract_info
|
get_contract_info
|
||||||
|
|
||||||
# Extract the second part of UUID (split by "-")
|
case "$SOFTWARE" in
|
||||||
SECOND_PART=$(echo "$UUID" | cut -d'-' -f2)
|
n8n)
|
||||||
|
|
||||||
# Decide which script to run
|
|
||||||
case "$SECOND_PART" in
|
|
||||||
001)
|
|
||||||
"$BIN_PATH/nuke/n8n"
|
"$BIN_PATH/nuke/n8n"
|
||||||
;;
|
;;
|
||||||
002)
|
ODOO_17|ODOO_18|ODOO_19)
|
||||||
"$BIN_PATH/nuke/ODOO_19"
|
"$BIN_PATH/nuke/odoo"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown UUID type: $SECOND_PART"
|
die "Unknown SOFTWARE type '${SOFTWARE}' for container_id $CONTAINER_ID — run: initContainer $CONTAINER_ID <n8n|ODOO_17|ODOO_18|ODOO_19>"
|
||||||
exit 2
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
#sqlite3 "/4server/data/contracts.db" <<SQL
|
log "Container $CONTAINER_ID successfully nuked."
|
||||||
#DELETE FROM containers WHERE UUID='$UUID';
|
|
||||||
#SQL
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
echo "Container $UUID successfully nuked."
|
|
||||||
|
|
||||||
Executable
+107
@@ -0,0 +1,107 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Shared library for all Odoo containers (17 / 18 / 19).
|
||||||
|
# Sourced by start/odoo, nuke/odoo, and admin tools.
|
||||||
|
# Requires: helpers already sourced (provides log/warn/die).
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# dump_config — print all resolved variables for debugging
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
dump_config() {
|
||||||
|
log "========== Odoo Container Configuration =========="
|
||||||
|
log "CONTAINER_ID: $CONTAINER_ID"
|
||||||
|
log "DB_NAME: $DB_NAME"
|
||||||
|
log "IMAGE_TAG: $IMAGE_TAG"
|
||||||
|
log "BRANCH: $BRANCH"
|
||||||
|
log ""
|
||||||
|
log "PostgreSQL Host: $POSTGRES_HOST"
|
||||||
|
log "PostgreSQL Port: $POSTGRES_PORT"
|
||||||
|
log "ODOO DB User: $ODOO_DB_USER"
|
||||||
|
log ""
|
||||||
|
log "BASEURL: $BASEURL"
|
||||||
|
log "SERVER_IP: $SERVER_IP"
|
||||||
|
log "PORT: $PORT"
|
||||||
|
log "=================================================="
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# build_traefik_rule
|
||||||
|
# Reads CONTAINER_ID and BASEURL from env. Resolves each candidate domain via
|
||||||
|
# nslookup and builds a Traefik Host() rule containing only domains that point
|
||||||
|
# to this server. Exports DOMAIN_LABEL.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
build_traefik_rule() {
|
||||||
|
local server_ip
|
||||||
|
server_ip=$(ip -4 addr show eth0 | awk '/inet/ {print $2}' | cut -d/ -f1 | head -n1)
|
||||||
|
|
||||||
|
local domains=("$CONTAINER_ID.odoo4projects.com")
|
||||||
|
if [ -f "$BASEURL/etc/domain" ]; then
|
||||||
|
while IFS= read -r domain || [ -n "$domain" ]; do
|
||||||
|
[ -z "$domain" ] && continue
|
||||||
|
domains+=("$domain")
|
||||||
|
done < "$BASEURL/etc/domain"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local rule=""
|
||||||
|
for d in "${domains[@]}"; do
|
||||||
|
local resolved_ip
|
||||||
|
resolved_ip=$(nslookup "$d" 2>/dev/null | awk '/^Address: / { print $2 }' | head -n1 || true)
|
||||||
|
if [ -z "$resolved_ip" ]; then
|
||||||
|
warn "build_traefik_rule: could not resolve $d, skipping"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ "$resolved_ip" = "$server_ip" ]; then
|
||||||
|
rule+=" || Host(\`$d\`)"
|
||||||
|
else
|
||||||
|
warn "build_traefik_rule: $d resolves to $resolved_ip, not $server_ip — skipping"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
rule="${rule# || }"
|
||||||
|
if [ -z "$rule" ]; then
|
||||||
|
die "build_traefik_rule: no valid domains resolved for $CONTAINER_ID — cannot start container"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOMAIN_LABEL="traefik.http.routers.$CONTAINER_ID.rule=$rule"
|
||||||
|
log "Traefik rule: $DOMAIN_LABEL"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# check_and_create_db
|
||||||
|
# Creates the postgres user and database if they don't exist yet.
|
||||||
|
# On a brand-new container this triggers a restore from default.zip.
|
||||||
|
# Requires: DB_NAME, ODOO_DB_USER, ODOO_DB_PASSWORD, POSTGRES_* from env.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
check_and_create_db() {
|
||||||
|
local _dbname="${DB_NAME:-$CONTAINER_ID}"
|
||||||
|
local _dbuser="${ODOO_DB_USER:-$_dbname}"
|
||||||
|
|
||||||
|
log "check_and_create_db: user=$_dbuser db=$_dbname host=$POSTGRES_HOST"
|
||||||
|
|
||||||
|
# --- user ---
|
||||||
|
local _user_result
|
||||||
|
_user_result=$(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='$_dbuser';" 2>&1) \
|
||||||
|
|| die "check_and_create_db: psql connection failed: $_user_result"
|
||||||
|
|
||||||
|
if ! echo "$_user_result" | grep -q 1; then
|
||||||
|
log "Creating PostgreSQL user $_dbuser"
|
||||||
|
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
|
||||||
|
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" \
|
||||||
|
-d postgres -c "CREATE USER \"$_dbuser\" WITH PASSWORD '$ODOO_DB_PASSWORD';" \
|
||||||
|
|| die "check_and_create_db: failed to create user $_dbuser"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- database ---
|
||||||
|
local _db_result
|
||||||
|
_db_result=$(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='$_dbname';" 2>&1) \
|
||||||
|
|| die "check_and_create_db: psql connection failed: $_db_result"
|
||||||
|
|
||||||
|
if ! echo "$_db_result" | grep -q 1; then
|
||||||
|
log "Database $_dbname not found — restoring from default.zip"
|
||||||
|
"$SBIN_ROOT/odoo/restore" "$CONTAINER_ID" default.zip \
|
||||||
|
|| die "check_and_create_db: restore failed for $_dbname"
|
||||||
|
fi
|
||||||
|
}
|
||||||
Executable
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Usage: odoo/restore <container_id> <file>
|
||||||
|
# Restores an Odoo database from a backup zip inside the container.
|
||||||
|
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "Usage: $0 <container_id> <file>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CONTAINER_ID="$1"
|
||||||
|
|
||||||
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of odoo/
|
||||||
|
source "$SBIN_ROOT/helpers"
|
||||||
|
|
||||||
|
load_container_config "$CONTAINER_ID"
|
||||||
|
get_contract_info
|
||||||
|
|
||||||
|
DB_NAME="${DB_NAME:-$CONTAINER_ID}"
|
||||||
|
|
||||||
|
log "Restoring $2 into CONTAINER_ID=$CONTAINER_ID DB_NAME=$DB_NAME"
|
||||||
|
log "Postgres host: $POSTGRES_HOST"
|
||||||
|
|
||||||
|
BACKUP="/mnt/backup/$2"
|
||||||
|
TEMPLATE="/mnt/db_images/$2"
|
||||||
|
|
||||||
|
if doas docker exec "$CONTAINER_ID" /bin/bash -c "[ -f $BACKUP ]"; then
|
||||||
|
FILENAME="$BACKUP"
|
||||||
|
elif doas docker exec "$CONTAINER_ID" /bin/bash -c "[ -f $TEMPLATE ]"; then
|
||||||
|
FILENAME="$TEMPLATE"
|
||||||
|
else
|
||||||
|
die "restore: file $2 not found at $BACKUP or $TEMPLATE inside container $CONTAINER_ID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Using file: $FILENAME"
|
||||||
|
|
||||||
|
# Terminate active connections then drop/recreate
|
||||||
|
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
|
||||||
|
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -d postgres -c "
|
||||||
|
SELECT pg_terminate_backend(pid) FROM pg_stat_activity
|
||||||
|
WHERE datname = '$DB_NAME' AND pid <> pg_backend_pid();" \
|
||||||
|
|| die "restore: failed to terminate connections on $DB_NAME"
|
||||||
|
|
||||||
|
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
|
||||||
|
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -d postgres \
|
||||||
|
-c "DROP DATABASE IF EXISTS \"$DB_NAME\";" \
|
||||||
|
|| die "restore: failed to drop database $DB_NAME"
|
||||||
|
|
||||||
|
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
|
||||||
|
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
|
||||||
|
-c "ALTER ROLE \"$DB_NAME\" CREATEDB;" \
|
||||||
|
|| die "restore: failed to grant CREATEDB on $DB_NAME"
|
||||||
|
|
||||||
|
doas docker exec "$CONTAINER_ID" rm -rf /home/odoo/.local/share/Odoo/filestore
|
||||||
|
doas docker exec "$CONTAINER_ID" rm -rf /root/.local/share/Odoo/filestore
|
||||||
|
|
||||||
|
doas docker exec "$CONTAINER_ID" odoo db \
|
||||||
|
--db_host beedb \
|
||||||
|
-w "$ODOO_DB_PASSWORD" \
|
||||||
|
-r "$DB_NAME" \
|
||||||
|
load "$DB_NAME" "$FILENAME" -f \
|
||||||
|
|| die "restore: odoo db load failed for $DB_NAME"
|
||||||
|
|
||||||
|
PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql \
|
||||||
|
-h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres \
|
||||||
|
-c "ALTER ROLE \"$DB_NAME\" NOCREATEDB;" \
|
||||||
|
|| die "restore: failed to revoke CREATEDB on $DB_NAME"
|
||||||
|
|
||||||
|
doas docker exec "$CONTAINER_ID" cp -r /root/.local/share/Odoo/filestore /home/odoo/.local/share/Odoo/filestore
|
||||||
|
doas docker exec "$CONTAINER_ID" chown -R odoo:odoo /home/odoo/.local
|
||||||
|
doas docker exec "$CONTAINER_ID" mkdir -p /var/lib/odoo/.local/share/Odoo/
|
||||||
|
doas docker exec "$CONTAINER_ID" ln -s /home/odoo/.local/share/Odoo/filestore /var/lib/odoo/.local/share/Odoo/filestore
|
||||||
|
|
||||||
|
log "Restore complete, restarting container $CONTAINER_ID"
|
||||||
|
docker restart "$CONTAINER_ID"
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/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,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>"
|
||||||
@@ -9,14 +9,14 @@ fi
|
|||||||
SEARCH="$1"
|
SEARCH="$1"
|
||||||
DB_PATH="/4server/data/contracts.db"
|
DB_PATH="/4server/data/contracts.db"
|
||||||
|
|
||||||
# Get all UUIDs where tags include the search string
|
# Get all container_ids where tags include the search string
|
||||||
uuids=$(sqlite3 "$DB_PATH" "SELECT UUID FROM containers WHERE tags LIKE '%$SEARCH%';")
|
container_ids=$(sqlite3 "$DB_PATH" "SELECT UUID FROM containers WHERE tags LIKE '%$SEARCH%';")
|
||||||
|
|
||||||
# Loop through each UUID
|
# Loop through each container_id
|
||||||
for uuid in $uuids; do
|
for container_id in $container_ids; do
|
||||||
# Check if a container with this name is running
|
# Check if a container with this name is running
|
||||||
if doas docker ps --format '{{.Names}}' | grep -qx "$uuid"; then
|
if doas docker ps --format '{{.Names}}' | grep -qx "$container_id"; then
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') Restarting $uuid"
|
echo "$(date '+%Y-%m-%d %H:%M:%S') Restarting $container_id"
|
||||||
/4server/sbin/startContainer $uuid
|
"$(dirname "$(realpath "${BASH_SOURCE[0]:-$0}")")/startContainer" "$container_id"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
+4
-4
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# rotateLogs — rotate odoo.log for all UUID containers, keep 4 weeks
|
# rotateLogs — rotate odoo.log for all CONTAINER_ID containers, keep 4 weeks
|
||||||
# Runs as a daemon: waits for midnight, rotates, then waits for next midnight
|
# Runs as a daemon: waits for midnight, rotates, then waits for next midnight
|
||||||
|
|
||||||
RETENTION_DAYS=28
|
RETENTION_DAYS=28
|
||||||
@@ -17,7 +17,7 @@ while true; do
|
|||||||
for log in /4server/data/00*/logs/odoo.log; do
|
for log in /4server/data/00*/logs/odoo.log; do
|
||||||
[[ -f "$log" ]] || continue
|
[[ -f "$log" ]] || continue
|
||||||
DIR=$(dirname "$log")
|
DIR=$(dirname "$log")
|
||||||
UUID=$(basename "$(dirname "$DIR")")
|
CONTAINER_ID=$(basename "$(dirname "$DIR")")
|
||||||
|
|
||||||
STAMP=$(date '+%Y-%m-%d_%H-%M')
|
STAMP=$(date '+%Y-%m-%d_%H-%M')
|
||||||
ARCHIVE="$DIR/odoo.log.$STAMP"
|
ARCHIVE="$DIR/odoo.log.$STAMP"
|
||||||
@@ -33,11 +33,11 @@ for log in /4server/data/00*/logs/odoo.log; do
|
|||||||
chmod "$PERMS" "${ARCHIVE}.gz"
|
chmod "$PERMS" "${ARCHIVE}.gz"
|
||||||
chown "$OWNER" "${ARCHIVE}.gz"
|
chown "$OWNER" "${ARCHIVE}.gz"
|
||||||
|
|
||||||
echo "$UUID rotated -> $(basename "${ARCHIVE}.gz")"
|
echo "$CONTAINER_ID rotated -> $(basename "${ARCHIVE}.gz")"
|
||||||
|
|
||||||
# Delete rotated logs older than 4 weeks
|
# Delete rotated logs older than 4 weeks
|
||||||
find "$DIR" -name 'odoo.log.*.gz' -mtime +$RETENTION_DAYS -exec rm -f {} \; \
|
find "$DIR" -name 'odoo.log.*.gz' -mtime +$RETENTION_DAYS -exec rm -f {} \; \
|
||||||
-exec echo "$UUID deleted: {}" \;
|
-exec echo "$CONTAINER_ID deleted: {}" \;
|
||||||
done
|
done
|
||||||
|
|
||||||
done
|
done
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
#!/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 17 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.odoo4projects.com" "$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 domains $UUID.odoo4projects.com and $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"
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
find "$BASEURL" -type d -exec chmod 777 {} \;
|
|
||||||
PORT=$((CONTAINERDBID + 2200))
|
|
||||||
mkdir -p ${ETC_DIR}
|
|
||||||
touch ${ETC_DIR}gitpath
|
|
||||||
|
|
||||||
mkdir -p ${GIT_DIR}keys
|
|
||||||
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 "$(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_17:$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 "traefic.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_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" 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 chown -R odoo:odoo /home/odoo/.local
|
|
||||||
docker exec $UUID chown -R odoo:odoo /var/lib/odoo/.local/share/Odoo
|
|
||||||
docker exec $UUID chown -R odoo:odoo /mnt/*
|
|
||||||
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
|
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') START ODOO 17 finished for $UUID"
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
#!/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 18 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.odoo4projects.com" "$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 domains $UUID.odoo4projects.com and $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"
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
find "$BASEURL" -type d -exec chmod 777 {} \;
|
|
||||||
PORT=$((CONTAINERDBID + 2200))
|
|
||||||
mkdir -p ${ETC_DIR}
|
|
||||||
touch ${ETC_DIR}gitpath
|
|
||||||
|
|
||||||
mkdir -p ${GIT_DIR}keys
|
|
||||||
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 "$(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_18:$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 "traefic.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_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" 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 chown -R odoo:odoo /home/odoo/.local
|
|
||||||
docker exec $UUID chown -R odoo:odoo /var/lib/odoo/.local/share/Odoo
|
|
||||||
docker exec $UUID chown -R odoo:odoo /mnt/*
|
|
||||||
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
|
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') START ODOO 18 finished for $UUID"
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
#!/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 19 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.odoo4projects.com" "$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 domains $UUID.odoo4projects.com and $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_19:$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_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 /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 19 finished for $UUID"
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
+24
-26
@@ -1,31 +1,29 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#--label "traefik.http.routers.${UUID}.middlewares=cors-headers@file" \
|
#--label "traefik.http.routers.${CONTAINER_ID}.middlewares=cors-headers@file" \
|
||||||
|
echo "Start N8N container ${CONTAINER_ID}"
|
||||||
|
|
||||||
# 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/${CONTAINER_ID}/n8n
|
||||||
|
mkdir -p /4server/data/${CONTAINER_ID}/data
|
||||||
|
mkdir -p /4server/data/${CONTAINER_ID}/backup
|
||||||
|
|
||||||
mkdir -p /4server/data/${UUID}/n8n
|
chmod 777 /4server/data/${CONTAINER_ID}/n8n
|
||||||
mkdir -p /4server/data/${UUID}/data
|
chmod 777 /4server/data/${CONTAINER_ID}/data
|
||||||
mkdir -p /4server/data/${UUID}/backup
|
chmod 777 /4server/data/${CONTAINER_ID}/backup
|
||||||
|
|
||||||
chmod 777 /4server/data/${UUID}/n8n
|
|
||||||
chmod 777 /4server/data/${UUID}/data
|
|
||||||
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 "^${CONTAINER_ID}$"; then
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') Stopping and removing existing container $UUID"
|
echo "$(date '+%Y-%m-%d %H:%M') - stopping existing container $CONTAINER_ID"
|
||||||
docker stop "$UUID"
|
docker stop "$CONTAINER_ID"
|
||||||
docker rm "$UUID"
|
docker rm "$CONTAINER_ID"
|
||||||
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 "$CONTAINER_ID" \
|
||||||
-p 5678 \
|
-p 5678 \
|
||||||
--cap-add=SYS_ADMIN \
|
--cap-add=SYS_ADMIN \
|
||||||
--security-opt seccomp=unconfined \
|
--security-opt seccomp=unconfined \
|
||||||
@@ -37,7 +35,7 @@ docker run -d \
|
|||||||
-e N8N_SMTP_PASS="Airbus12N@N" \
|
-e N8N_SMTP_PASS="Airbus12N@N" \
|
||||||
-e N8N_SMTP_SENDER="n8n <n8n@odoo4projects.com>" \
|
-e N8N_SMTP_SENDER="n8n <n8n@odoo4projects.com>" \
|
||||||
-e N8N_SMTP_SSL=false \
|
-e N8N_SMTP_SSL=false \
|
||||||
-e N8N_HOST="${UUID}.odoo4projects.com" \
|
-e N8N_HOST="${CONTAINER_ID}.odoo4projects.com" \
|
||||||
-e N8N_PORT=5678 \
|
-e N8N_PORT=5678 \
|
||||||
-e N8N_PROTOCOL=https \
|
-e N8N_PROTOCOL=https \
|
||||||
-e N8N_FORMDATA_FILE_SIZE_MAX=3000 \
|
-e N8N_FORMDATA_FILE_SIZE_MAX=3000 \
|
||||||
@@ -45,20 +43,20 @@ docker run -d \
|
|||||||
-e NODE_ENV=production \
|
-e NODE_ENV=production \
|
||||||
-e EXECUTIONS_PRUNE=true \
|
-e EXECUTIONS_PRUNE=true \
|
||||||
-e EXECUTIONS_PRUNE_MAX_AGE=48 \
|
-e EXECUTIONS_PRUNE_MAX_AGE=48 \
|
||||||
-e WEBHOOK_URL="https://${UUID}.odoo4projects.com/" \
|
-e WEBHOOK_URL="https://${CONTAINER_ID}.odoo4projects.com/" \
|
||||||
-e GENERIC_TIMEZONE="UTC-3" \
|
-e GENERIC_TIMEZONE="UTC-3" \
|
||||||
-e N8N_BLOCK_ENV_ACCESS_AND_PROCESS_INFORMATION=true \
|
-e N8N_BLOCK_ENV_ACCESS_AND_PROCESS_INFORMATION=true \
|
||||||
-e N8N_CUSTOM_EXTENSIONS="/usr/local/share/n8n/custom" \
|
-e N8N_CUSTOM_EXTENSIONS="/usr/local/share/n8n/custom" \
|
||||||
-v "/4server/data/${UUID}/n8n:/home/node/.n8n" \
|
-v "/4server/data/${CONTAINER_ID}/n8n:/home/node/.n8n" \
|
||||||
-v "/4server/data/${UUID}/data:/data" \
|
-v "/4server/data/${CONTAINER_ID}/data:/data" \
|
||||||
-v "/4server/data/${UUID}/backup:/backup" \
|
-v "/4server/data/${CONTAINER_ID}/backup:/backup" \
|
||||||
--label "traefik.enable=true" \
|
--label "traefik.enable=true" \
|
||||||
--label "traefik.http.routers.${UUID}.rule=Host(\`${UUID}.odoo4projects.com\`) || Host(\`${UUID}.${HOSTNAME}.odoo4projects.com\`)" \
|
--label "traefik.http.routers.${CONTAINER_ID}.rule=Host(\`${CONTAINER_ID}.odoo4projects.com\`)" \
|
||||||
--label "traefik.http.routers.${UUID}.entrypoints=web,websecure" \
|
--label "traefik.http.routers.${CONTAINER_ID}.entrypoints=web,websecure" \
|
||||||
--label "traefik.http.routers.${UUID}.tls=true" \
|
--label "traefik.http.routers.${CONTAINER_ID}.tls=true" \
|
||||||
--label "traefik.http.routers.${UUID}.tls.certresolver=production" \
|
--label "traefik.http.routers.${CONTAINER_ID}.tls.certresolver=production" \
|
||||||
--label "traefik.http.services.${UUID}.loadbalancer.server.port=5678" \
|
--label "traefik.http.services.${CONTAINER_ID}.loadbalancer.server.port=5678" \
|
||||||
--network 4server_4projects \
|
--network 4server_4projects \
|
||||||
docker.odoo4projects.com/4projects/n8n:release
|
docker.odoo4projects.com/4projects/n8n:release
|
||||||
|
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') docker run for $UUID finished with exit code $?"
|
echo "Started $1"
|
||||||
Executable
+92
@@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Unified Odoo start script for all versions (17 / 18 / 19).
|
||||||
|
# Called by startContainer with IMAGE_TAG exported, e.g.:
|
||||||
|
# IMAGE_TAG=odoo_19 /4server/sbin/start/odoo
|
||||||
|
#
|
||||||
|
# Requires (exported by startContainer before this is called):
|
||||||
|
# CONTAINER_ID, IMAGE_TAG, SOFTWARE, DB_NAME
|
||||||
|
|
||||||
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN_ROOT="$(dirname "$(dirname "$_self")")" # parent of start/
|
||||||
|
source "$SBIN_ROOT/helpers"
|
||||||
|
source "$SBIN_ROOT/odoo.lib"
|
||||||
|
load_container_config "$CONTAINER_ID"
|
||||||
|
get_contract_info
|
||||||
|
|
||||||
|
BRANCH="${BRANCH:-release}"
|
||||||
|
DB_NAME="${DB_NAME:-$CONTAINER_ID}"
|
||||||
|
ODOO_DB_USER="$DB_NAME"
|
||||||
|
|
||||||
|
log "Starting Odoo container: CONTAINER_ID=$CONTAINER_ID IMAGE_TAG=$IMAGE_TAG DB_NAME=$DB_NAME"
|
||||||
|
|
||||||
|
BASEURL="/4server/data/$CONTAINER_ID"
|
||||||
|
DATA_DIR="$BASEURL/odoo"
|
||||||
|
CUSTOM_DIR="$BASEURL/git/$CONTAINER_ID/custom"
|
||||||
|
ENTERPRISE_DIR="$BASEURL/git/$CONTAINER_ID/enterprise"
|
||||||
|
LOGS_DIR="$BASEURL/logs"
|
||||||
|
CONFIG_DIR="$BASEURL/config"
|
||||||
|
CC_DIR="$BASEURL/cc"
|
||||||
|
BACKUP_DIR="/BACKUP/$CONTAINER_ID"
|
||||||
|
GIT_DIR="$BASEURL/git-server"
|
||||||
|
INSTALL_DIR="$BASEURL/install"
|
||||||
|
SSH_DIR="$BASEURL/.ssh"
|
||||||
|
ETC_DIR="$BASEURL/etc"
|
||||||
|
|
||||||
|
PORT=$((CONTAINERDBID + 2200))
|
||||||
|
log "SSH port: $PORT"
|
||||||
|
|
||||||
|
# --- prepare directories ---
|
||||||
|
find "$BASEURL" -type d -exec chmod 777 {} \;
|
||||||
|
mkdir -p "$ETC_DIR" "$BACKUP_DIR" "$GIT_DIR/keys" "$INSTALL_DIR" "$SSH_DIR"
|
||||||
|
chmod 777 "$ETC_DIR" "$BACKUP_DIR" "$GIT_DIR"
|
||||||
|
touch "$GIT_DIR/keys/id_rsa.pub"
|
||||||
|
echo "git clone \"ssh://git@${CONTAINER_ID}.odoo4projects.com:${PORT}/git-server/repos/odoo.git\"" > "$ETC_DIR/gitpath"
|
||||||
|
|
||||||
|
# --- build Traefik domain rule (exports DOMAIN_LABEL) ---
|
||||||
|
build_traefik_rule
|
||||||
|
|
||||||
|
# --- stop and remove any existing container ---
|
||||||
|
docker stop "$CONTAINER_ID" 2>/dev/null || true
|
||||||
|
docker rm "$CONTAINER_ID" 2>/dev/null || true
|
||||||
|
|
||||||
|
# --- run ---
|
||||||
|
docker run -d --name "$CONTAINER_ID" \
|
||||||
|
--network 4server_4projects \
|
||||||
|
--restart=always \
|
||||||
|
-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 CONTAINER_ID="$CONTAINER_ID" \
|
||||||
|
-e DB_NAME="$DB_NAME" \
|
||||||
|
-e HDD="$HDD" \
|
||||||
|
-e DOMAINS_SLOTS="$DOMAINS_SLOTS" \
|
||||||
|
-e BACKUP_SLOTS="$BACKUP_SLOTS" \
|
||||||
|
-e WORKER="$WORKER" \
|
||||||
|
-e GIT="$GIT" \
|
||||||
|
--label "$DOMAIN_LABEL" \
|
||||||
|
--label "traefik.http.services.$CONTAINER_ID.loadbalancer.server.port=8069" \
|
||||||
|
--label "traefik.http.routers.$CONTAINER_ID.entrypoints=web,websecure" \
|
||||||
|
--label "traefik.http.routers.$CONTAINER_ID.tls=true" \
|
||||||
|
--label "traefik.http.routers.$CONTAINER_ID.tls.certresolver=production" \
|
||||||
|
--label "traefik.http.routers.$CONTAINER_ID.service=$CONTAINER_ID" \
|
||||||
|
"docker.odoo4projects.com/4projects/${IMAGE_TAG}:${BRANCH}" \
|
||||||
|
|| die "docker run failed for $CONTAINER_ID"
|
||||||
|
|
||||||
|
# --- fix ownership inside container ---
|
||||||
|
docker exec "$CONTAINER_ID" chown -R odoo:odoo /home/odoo/.local
|
||||||
|
docker exec "$CONTAINER_ID" chown -R odoo:odoo /mnt
|
||||||
|
docker exec "$CONTAINER_ID" chown -R git:git /git-server
|
||||||
|
chmod 777 "$CC_DIR"
|
||||||
|
|
||||||
|
# --- ensure DB/user exist, restore from default.zip if brand new ---
|
||||||
|
check_and_create_db
|
||||||
+20
-41
@@ -1,67 +1,46 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Usage: ./start_by_uuid.sh <uuid>
|
|
||||||
# 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"
|
|
||||||
cd/4server
|
|
||||||
source /4server/sbin/helpers
|
|
||||||
|
|
||||||
BIN_PATH="/4server/sbin"
|
_self="$(realpath "${BASH_SOURCE[0]:-$0}")"; SBIN="$(dirname "$_self")"
|
||||||
|
source "$SBIN/helpers"
|
||||||
|
|
||||||
UUID="$1"
|
BIN_PATH="$SBIN"
|
||||||
|
CONTAINER_ID="$1"
|
||||||
|
|
||||||
if [[ -z "$UUID" ]]; then
|
[ -z "$CONTAINER_ID" ] && die "Usage: $0 <uuid>"
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') Usage: $0 <uuid>"
|
log "Start container $CONTAINER_ID"
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! "$UUID" =~ ^[0-9a-fA-F-]+$ ]]; then
|
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: Invalid UUID format: $UUID"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
DOMAIN_FILE="/4server/data/$UUID/etc/domain"
|
DOMAIN_FILE="/4server/data/$CONTAINER_ID/etc/domain"
|
||||||
DB_FILE="/4server/data/contracts.db"
|
DB_FILE="/4server/data/contracts.db"
|
||||||
if [ -f "$DOMAIN_FILE" ]; then
|
if [ -f "$DOMAIN_FILE" ]; then
|
||||||
DOMAINS=$(paste -sd "," "$DOMAIN_FILE")
|
DOMAINS=$(paste -sd "," "$DOMAIN_FILE")
|
||||||
# Escape single quotes for SQLite ('' is the standard SQLite escape for ')
|
# Escape single quotes for SQLite ('' is the standard SQLite escape for ')
|
||||||
DOMAINS_SAFE="${DOMAINS//\'/\'\'}"
|
DOMAINS_SAFE="${DOMAINS//\'/\'\'}"
|
||||||
UUID_SAFE="${UUID//\'/\'\'}"
|
CONTAINER_ID_SAFE="${CONTAINER_ID//\'/\'\'}"
|
||||||
sqlite3 "$DB_FILE" "UPDATE containers SET domains='$DOMAINS_SAFE' WHERE UUID='$UUID_SAFE';"
|
sqlite3 "$DB_FILE" "UPDATE containers SET domains='$DOMAINS_SAFE' WHERE UUID='$CONTAINER_ID_SAFE';"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
load_container_config "$CONTAINER_ID"
|
||||||
get_contract_info
|
get_contract_info
|
||||||
|
|
||||||
# Extract the second part of UUID (split by "-")
|
# Decide which script to run based on SOFTWARE from container.env
|
||||||
SECOND_PART=$(echo "$UUID" | cut -d'-' -f2)
|
case "$SOFTWARE" in
|
||||||
|
n8n)
|
||||||
# Decide which script to run
|
|
||||||
case "$SECOND_PART" in
|
|
||||||
001)
|
|
||||||
"$BIN_PATH/start/n8n"
|
"$BIN_PATH/start/n8n"
|
||||||
;;
|
;;
|
||||||
002)
|
ODOO_17)
|
||||||
"$BIN_PATH/start/ODOO_18"
|
IMAGE_TAG=odoo_17 "$BIN_PATH/start/odoo"
|
||||||
;;
|
;;
|
||||||
|
ODOO_18)
|
||||||
|
IMAGE_TAG=odoo_18 "$BIN_PATH/start/odoo"
|
||||||
003)
|
|
||||||
"$BIN_PATH/start/ODOO_19"
|
|
||||||
;;
|
;;
|
||||||
|
ODOO_19)
|
||||||
004)
|
IMAGE_TAG=odoo_19 "$BIN_PATH/start/odoo"
|
||||||
"$BIN_PATH/start/ODOO_17"
|
|
||||||
;;
|
;;
|
||||||
|
|
||||||
005)
|
|
||||||
"$BIN_PATH/start/ODOO_20"
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
*)
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') Unknown UUID type: $SECOND_PART"
|
die "Unknown SOFTWARE type '${SOFTWARE}' for container_id $CONTAINER_ID — run: initContainer $CONTAINER_ID <n8n|ODOO_17|ODOO_18|ODOO_19>"
|
||||||
exit 2
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
+1
-17
@@ -1,8 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Collect server runtime stats (uptime, top, cgroup, mounts, hdd) and return as JSON."""
|
"""Collect server runtime stats (uptime, top, cgroup, mounts) and return as JSON."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
@@ -33,20 +32,6 @@ 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 = {}
|
||||||
|
|
||||||
@@ -54,7 +39,6 @@ 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))
|
||||||
|
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,4 +1 @@
|
|||||||
API_KEY={{API_KEY}}
|
API_KEY={{API_KEY}}
|
||||||
PROVIDER={{PROVIDER}}
|
|
||||||
COST={{COST}}
|
|
||||||
CONTRACT_END={{CONTRACT_END}}
|
|
||||||
@@ -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.7
|
image: docker.io/library/traefik:3.6
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
@@ -38,4 +38,5 @@ 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.16.0/20
|
ip_range: 10.5.0.0/26
|
||||||
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
permit nopass 4server as root
|
permit nopass 4server as root
|
||||||
permit nopass root as root
|
|
||||||
@@ -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/16 md5
|
host all all 10.5.0.1/32 md5
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
listen_addresses = '*'
|
listen_addresses = '*'
|
||||||
max_connections = 300
|
max_connections = 150
|
||||||
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
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
https://dl-cdn.alpinelinux.org/alpine/v3.24/main
|
https://dl-cdn.alpinelinux.org/alpine/v3.22/main
|
||||||
https://dl-cdn.alpinelinux.org/alpine/v3.24/community
|
https://dl-cdn.alpinelinux.org/alpine/v3.22/community
|
||||||
|
|
||||||
@@ -99,3 +99,5 @@ http:
|
|||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: "http://10.5.0.1:8888"
|
- url: "http://10.5.0.1:8888"
|
||||||
|
|
||||||
|
|
||||||
+2
-4
@@ -31,13 +31,11 @@ rex doas pip install --root-user-action ignore --break-system-packages --no-cach
|
|||||||
|
|
||||||
### own bins
|
### own bins
|
||||||
echo "Running prsync ./sbin"
|
echo "Running prsync ./sbin"
|
||||||
prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/
|
prsync -h "/app/host_vars/hosts" -avz ./sbin/ /usr/sbin/
|
||||||
|
|
||||||
### POSTGRESS
|
### POSTGRESS
|
||||||
rex mkdir -p /4server/data/postgres/etc
|
rex mkdir -p /4server/data/postgres/etc
|
||||||
template templates/pg_hba.conf /4server/data/postgres/data/pg_hba.conf
|
template templates/pq_hba.conf /4server/data/postgres/etc/
|
||||||
template templates/postgresql.conf /4server/data/postgres/data/postgresql.conf
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
rex doas apk update
|
||||||
|
rex doas apk fix
|
||||||
|
rex doas apk upgrade
|
||||||
|
rex doas reboot
|
||||||
+1
-8
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
echo "Running prsync ./sbin"
|
echo "Running prsync ./sbin"
|
||||||
prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/
|
prsync -h "/app/host_vars/hosts" -avz ./sbin/ /usr/sbin/
|
||||||
template templates/.profile /home/4server/.profile
|
template templates/.profile /home/4server/.profile
|
||||||
template templates/postgresql.conf /4server/data/postgres/data/postgresql.conf
|
template templates/postgresql.conf /4server/data/postgres/data/postgresql.conf
|
||||||
|
|
||||||
@@ -12,10 +12,3 @@ 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/
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
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.
Reference in new issue
Block a user