This commit is contained in:
oliver
2026-04-21 17:29:54 -03:00
commit 3deb54f5aa
116 changed files with 4551 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAOKT5um7fzviD27odH4GTW5oos0xAeP3DW7OhTqbEyAgAAAJhBIAtnQSAL
ZwAAAAtzc2gtZWQyNTUxOQAAACAOKT5um7fzviD27odH4GTW5oos0xAeP3DW7OhTqbEyAg
AAAEAk4EqWEVc34Dht1e/vD0ajtgSCo6xq30YPtvnrNuL3PQ4pPm6bt/O+IPbuh0fgZNbm
iizTEB4/cNbs6FOpsTICAAAADm9saXZlckBtYXRlLTE2AQIDBAUGBw==
-----END OPENSSH PRIVATE KEY-----
+2
View File
@@ -0,0 +1,2 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA4pPm6bt/O+IPbuh0fgZNbmiizTEB4/cNbs6FOpsTIC oliver@mate-16
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACD64E32udubuP0PEhxgoxR35jgHFUzlSRONXfGgsxNPswAAAJhgli31YJYt
9QAAAAtzc2gtZWQyNTUxOQAAACD64E32udubuP0PEhxgoxR35jgHFUzlSRONXfGgsxNPsw
AAAEC+wOOEwsAkPIhD02f+Yw5MYaCZZ5hhU9ZG62CcdH4+XvrgTfa525u4/Q8SHGCjFHfm
OAcVTOVJE41d8aCzE0+zAAAADm9saXZlckBtYXRlLTE2AQIDBAUGBw==
-----END OPENSSH PRIVATE KEY-----
+2
View File
@@ -0,0 +1,2 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrgTfa525u4/Q8SHGCjFHfmOAcVTOVJE41d8aCzE0+z oliver@mate-16
+7
View File
@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDEN4FtZTrBFBkDDy7hTptxeoyPvpHDbSKuJxEGWusaMgAAAJjCrJXhwqyV
4QAAAAtzc2gtZWQyNTUxOQAAACDEN4FtZTrBFBkDDy7hTptxeoyPvpHDbSKuJxEGWusaMg
AAAEAUwrtarcT6FifBEUGdm8F32i2lZ3rEeYyKQo5n0wRyxcQ3gW1lOsEUGQMPLuFOm3F6
jI++kcNtIq4nEQZa6xoyAAAADm9saXZlckBtYXRlLTE2AQIDBAUGBw==
-----END OPENSSH PRIVATE KEY-----
+2
View File
@@ -0,0 +1,2 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQ3gW1lOsEUGQMPLuFOm3F6jI++kcNtIq4nEQZa6xoy ansible@mumbai
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDAXv2aFH8xzLWB5zvtJI1SkgDnB/OMhmkFQQa/vzhk8gAAAJjM4MTdzODE
3QAAAAtzc2gtZWQyNTUxOQAAACDAXv2aFH8xzLWB5zvtJI1SkgDnB/OMhmkFQQa/vzhk8g
AAAEDVHiNzHN0zUBHU5ui2U00xxXs4UIVGVRW1PCEhxpD/iMBe/ZoUfzHMtYHnO+0kjVKS
AOcH84yGaQVBBr+/OGTyAAAADm9saXZlckBtYXRlLTE2AQIDBAUGBw==
-----END OPENSSH PRIVATE KEY-----
+2
View File
@@ -0,0 +1,2 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMBe/ZoUfzHMtYHnO+0kjVKSAOcH84yGaQVBBr+/OGTy oliver@mate-16
+9
View File
@@ -0,0 +1,9 @@
aaa-bbb-UUID
aaa = server
001 = manchester
002 = boston
bbb = image
001 = n8n
002 = ODOO_18
003 = ODOO_19
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
set -e
export BORG_REPO="ssh://e7e9h45y@e7e9h45y.repo.borgbase.com/./repo"
export BORG_RSH="ssh -i ~/.ssh/borg-backup"
echo "Applying retention policy..."
borg prune \
--keep-daily=7 \
--keep-weekly=8 \
--keep-monthly=12 \
--stats
echo "Done: pruning complete"
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
rsync -avz --progress -e ssh mumbai:/4server/sbin/* /app/sbin/
+15
View File
@@ -0,0 +1,15 @@
# ~/.bashrc
echo "command: mount_volume <volume>"
echo "alias: set_prod"
export hosts_file="/app/hosts.dev"
export PS1="\[\e[32m\]\h:\w\$\[\e[0m\] "
df -h .
set_prod() {
export HOSTS_FILE="/app/hosts.all"
echo "HOSTS_FILE set to: $HOSTS_FILE"
}
cd /4server
+21
View File
@@ -0,0 +1,21 @@
#!/sbin/openrc-run
name="4server-api"
description="4server API Service"
command="/4server/sbin/api"
command_args=""
pidfile="/run/${RC_SVCNAME}.pid"
command_background="yes"
output_log="/4server/data/api.log"
error_log="/4server/data/api.log"
depend() {
need net
use logger dns
after firewall
}
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDBzCCAe8CFA4xhYG29I1JGz2K+LN79to0c5HrMA0GCSqGSIb3DQEBCwUAMEAx
PjA8BgNVBAMMNTAwMS0wMDEtMTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0
MTc0MDAwLm9kOG4uY29tMB4XDTI1MDgyNTA0Mjk1OVoXDTI2MDgyNTA0Mjk1OVow
QDE+MDwGA1UEAww1MDAxLTAwMS0xMjNlNDU2Ny1lODliLTEyZDMtYTQ1Ni00MjY2
MTQxNzQwMDAub2Q4bi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDPRqge1j5i2Mtygzh4Ep3S4WAVRUbygFgliBslkrO4d4mxusOLhzBn5JJY+NWA
pNsvnojMcGlgeIugsi3MeMn2/ay88Y5THPrHXqf4jTJB8DvlYbi41HfBX6rstF4z
2IZ4gIp6aem6wVuIcI6DKlPlEQss09aFkTrp4jKvPPCq3tgbcI4PkHvHm4fpzIjW
5I8JrQBqBHrNmYQfUT+ZEABKj0XQMH+CceNOIw18ChKoHIJbIpqAKO0zMYiQ6fCZ
y4OWJCHk7ekXNdNPjt2K1lh1doNK6gPxjsIuh5Pxd+BANoumqMCFbLNs8bdwO7p7
Po5uFbU5RB4L5KSoxYPgmFZ5AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAD/1Wxi
xzOkSNHG3Fu50q0m4qxEEoNYvxFkB2EUSA5DKGeCQq46tKpjNR+zZzG2fbn711v9
qSNuZEhxajQkQ0oR7CbMDs8Ql/WlregdZTv053liBHFkpwYVRSaaE2LnxvnlbmMq
eNIlPldmN/b7Rs07e5GcIkZ8mMQrbT2TuQV4Q7be8qjex8zF7OzLF1ok//C2SMwF
0qd5Z9e4rycI2XleE73Y/Vdl1vO+/RbZDfhhp12gYi/A+jywMYgckVEjUKQ2/9FT
UIaDtDEh9oYirBEyqmJPsZi2nqt1UJKDctqGykmZZwymAtfYPnPNVWYgp7nU210c
hc8zQDaZyEZUEYw=
-----END CERTIFICATE-----
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIChTCCAW0CAQAwQDE+MDwGA1UEAww1MDAxLTAwMS0xMjNlNDU2Ny1lODliLTEy
ZDMtYTQ1Ni00MjY2MTQxNzQwMDAub2Q4bi5jb20wggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDPRqge1j5i2Mtygzh4Ep3S4WAVRUbygFgliBslkrO4d4mx
usOLhzBn5JJY+NWApNsvnojMcGlgeIugsi3MeMn2/ay88Y5THPrHXqf4jTJB8Dvl
Ybi41HfBX6rstF4z2IZ4gIp6aem6wVuIcI6DKlPlEQss09aFkTrp4jKvPPCq3tgb
cI4PkHvHm4fpzIjW5I8JrQBqBHrNmYQfUT+ZEABKj0XQMH+CceNOIw18ChKoHIJb
IpqAKO0zMYiQ6fCZy4OWJCHk7ekXNdNPjt2K1lh1doNK6gPxjsIuh5Pxd+BANoum
qMCFbLNs8bdwO7p7Po5uFbU5RB4L5KSoxYPgmFZ5AgMBAAGgADANBgkqhkiG9w0B
AQsFAAOCAQEAktJBSt2lSbrsmUlhG+6AZ4lo52qOwmxxTQ7steEfkMp6zOvO3FXk
meOiU59fFyOkH0pUpJo4RolZPfSyzdi0R9fV5wR/a1eqaiNyzReTPyyXKP2SMdzu
Xav3ldaMOGp6gPa3qmyQ6nQJjVJWj/FulCslIAv55Qk2xMlRQYV+IIK4Gggl74d9
Kwbq3MDvMeLJS5Qzr/hHqWmPWiUKbs1DbTajSe63B36/yMEi8VYjdWw7K86kS2X/
0yy1M9+HOHPG1Ch5zHaa64iioo1iaMxTqBgOvJTsuCtNX5oflj56STBozNvzMq1o
0/E9/uxW1TDL8iDYp/k2krT4k1M0rKq6Tg==
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDPRqge1j5i2Mty
gzh4Ep3S4WAVRUbygFgliBslkrO4d4mxusOLhzBn5JJY+NWApNsvnojMcGlgeIug
si3MeMn2/ay88Y5THPrHXqf4jTJB8DvlYbi41HfBX6rstF4z2IZ4gIp6aem6wVuI
cI6DKlPlEQss09aFkTrp4jKvPPCq3tgbcI4PkHvHm4fpzIjW5I8JrQBqBHrNmYQf
UT+ZEABKj0XQMH+CceNOIw18ChKoHIJbIpqAKO0zMYiQ6fCZy4OWJCHk7ekXNdNP
jt2K1lh1doNK6gPxjsIuh5Pxd+BANoumqMCFbLNs8bdwO7p7Po5uFbU5RB4L5KSo
xYPgmFZ5AgMBAAECggEAXDMx1YW3eoC0/tsf40lkqslV1CBczsIIc6l3ad830nZZ
6ZUKJqacAZrK/oixb+flF+mNMGNQfHkiovifJrUUIan1jJZmmNHrO4P/c7BbCrmx
6vbtFEpuerXzchdJUAagyjljX9B9B3W7IZXvzqilaN+L+QTCB+fyLNdRdGHHLDnn
HflKCPVQS/PWTBLufxtmmZ59uqLkWzOio8d710qrxPankejXb2kvuDjWuWu9Rdja
lxR8IdZaG32hPhrkFL7fxlQjLuRsPIP0GMaQeEOOvyTi2e5BBCE1CiCxT78e8cIP
eae42jNHelhkj/FA6vLcQwPAis1TZ8e3+GkUt357gQKBgQDtxaBm8PT6a+R9GNzs
c5gCWwm21YUqzbCl4Q/8Mnyn2wqQn+/RmfMwr1uNLAiGkiB1JNjbO0pzoeJyk03F
vDxd/QyicSfYizndj+XIEmLRyUwixXZzpqoJLYHoiwrl6f/vVQfDLgUJYbMFHzQW
xuuLGhqFjbLgKlVwGEd0O2QiawKBgQDfKoi0upLabWHgYmsFm7HxqUECi3/iJDrx
JW/qDOBQtgfUTi01JFg0/xctQRdERh+8bBwOCWnkMdaPtJTXjJo1AgO+XzF0BB1B
FooXcqe7eokPUeBb3P/CdXNNrfBcisDbTX2za0AG8ETja4jBhliR45O+S58G5hku
8W59g+BLqwKBgChQoenSYTc0pAEx/gN5dgSwOu1tNq8TQShfCL7SMKClWx06gQcg
+0L9+J/vH2Lx098I6FwDqZQBlsumfkFQsUueZE4GsaLduGoAxA0wUOERKH+cy4DA
eYQk2yn6qVZiXqrN2AsX+nKkxh3QNJzIDZgATQ7n/7RSeToQY80pZMkRAoGAe4s3
fR3gmI1/ZtH1P3iPDSLO+5KwrEe0XbWE/EQ+lk//i5fvzQCe2E/zy7jCIajUfuI2
scqiVZMFni6xS5bp87h2zBg0724rp9HLhumRU+elItcH5rM037lXqMRHUWP7Gi0P
DpmsK2suJ9xrK/+s3q7nJq0Ej7QocuVzboboT9sCgYEA2aaRafzP6v/quJwc33RZ
0PFxPXDKV9MuXjuve+7d37iSo3A/1h8/hdwg48r6OnQUCtLOvkZ1t21UKlbGY6F9
qw8VkMxmCPgIZHqLfOT7TNsHoF8eZAN6HMGkI/+SzpYpaA3NcB0tajJk8+8PHJI6
Rg/Hkv4zpzmiPDN7F6l89rQ=
-----END PRIVATE KEY-----
+30
View File
@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFNzCCAx+gAwIBAgIUCIJuU/7oPy1PrksppEPukIs4MfgwDQYJKoZIhvcNAQEL
BQAwEjEQMA4GA1UEAwwHKi5sb2NhbDAeFw0yNTA4MDgyMTMyMzlaFw0yNjA4MDgy
MTMyMzlaMBIxEDAOBgNVBAMMByoubG9jYWwwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQC+uBU5mo8h8LK00Hrw9AtaPI2yXBaVh5S8KrAJ0eoUSxc0gg7q
dwsD9+boyaDbiePcllTLvmIKqX8K2TbiucEaqNGzJauop0+UZjjCQrzuq+cD6xPh
+1bzcWN+oLubUtv4wi3mRNCtP56YyM4c72OweXB9Mhi9Z8e2caTjCLdcCS10i5Oy
NWYZFhnjBxXJoElTt4HZFLDj60Iqi9thVGO4virv7VBwOvAKaCgOOuagPtISgHO7
1t1hV9TNTHRcE37xpOZT6moPsEBitkszwPx24SgATGrG5J8UbDJ5EdY+kA4wD0mU
hi9pUWaRlKWQjqRRszvsSnbQUPHORHSUFFpycworeNUBCmTs5jm0/+RqI4TLTUX6
ZbJ6azgGpgbJtMbMlywW1Yuy9ACrSP/jncKekiR+0uQ5s+y2crT+aeuzHsyMtUUn
TI1ExsOE/QWGH7MV298D+jvSSWg4WTf3dzAiFsDxP4JtDZ1NmDwm6Pjmano1Y57g
uU++4RvYN6YKxDnkcWXIZFpUvW+dr7oLZaOcqwCx4KVCFo4e2qqigYgWgz8r05iE
ngj7UZO70n3dZrkL4Iu2tFATHLBy1SYZIu3ewZodOeK54q63bYtVFj7ECAE4Eb7J
6DgjOtN3GH9E2aKMjzFRvWzItRufLWIycPN/tAOh6dOPuX9oZQf71sxe3wIDAQAB
o4GEMIGBMB0GA1UdDgQWBBSGznETTeVc5FVFEGbdVUzR5jfQADAfBgNVHSMEGDAW
gBSGznETTeVc5FVFEGbdVUzR5jfQADAPBgNVHRMBAf8EBTADAQH/MC4GA1UdEQQn
MCWCCWFwcC5sb2NhbIIJYXBpLmxvY2Fsgg10cmFlZmlrLmxvY2FsMA0GCSqGSIb3
DQEBCwUAA4ICAQCl+LRB+6Rz0EJFbZnhLWvumY2KegS+QkB6YUDycJIuq/2Q9RWB
Z0yV94asZcvHE21/BHhnMk4Qa2PsQn8gQIGCAhj+/2DVt5mGwWVgoes1gtAg6okH
YYKhTljjfpMFqyp/lyzanzF4VdnhzDKpaRLxKwuCf0xe9V03S4/fri/tVjxpjUyc
eaTgfDlzJgQu2rZZz8dG7fltCEhl9gBGbQ3WWaSDYOW49UXqS3LR0eBZ4s/RAG7Z
LiBIKzOFQjLplaODsCOpOguzRfL6O2WXDADbuh7XAQmmhkfsuruPvP/5E1G1hb6K
khsKyiYo4WLpdGJACezN/jmQVcqULz8iLI/jRaoT5g3dwvBkzyolIF+A6a33D3Ph
vQd5ta6BT/EWTBp4T5MSyvd03rkqV0oCHeF+wTQ3iR4b5jrxlVtqCFlsK32NrB9e
ZAboJitgxLgs6ZKXhoxCGjtZdpgYyxqgEOtJazzNitNxB8Xyb3hCc2t7VPpRUfUa
gyddQFd1yZmhPZqhugXI+LL7xO7HHyrz+CwqeWkObJNDRIe6Me4Rxo9H0ZQfjLa1
fAgxubtAsGr0AwQSg3X/PamEhVdjvCBCtadgHQZQLaP7ilPBcER/xBQ1jbI1LYzF
BTCypCFykXbDxxbOwzhwRLoUHzWS2XAYT7vOHE60AokMKwArz9s3Hu+wUg==
-----END CERTIFICATE-----
+52
View File
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC+uBU5mo8h8LK0
0Hrw9AtaPI2yXBaVh5S8KrAJ0eoUSxc0gg7qdwsD9+boyaDbiePcllTLvmIKqX8K
2TbiucEaqNGzJauop0+UZjjCQrzuq+cD6xPh+1bzcWN+oLubUtv4wi3mRNCtP56Y
yM4c72OweXB9Mhi9Z8e2caTjCLdcCS10i5OyNWYZFhnjBxXJoElTt4HZFLDj60Iq
i9thVGO4virv7VBwOvAKaCgOOuagPtISgHO71t1hV9TNTHRcE37xpOZT6moPsEBi
tkszwPx24SgATGrG5J8UbDJ5EdY+kA4wD0mUhi9pUWaRlKWQjqRRszvsSnbQUPHO
RHSUFFpycworeNUBCmTs5jm0/+RqI4TLTUX6ZbJ6azgGpgbJtMbMlywW1Yuy9ACr
SP/jncKekiR+0uQ5s+y2crT+aeuzHsyMtUUnTI1ExsOE/QWGH7MV298D+jvSSWg4
WTf3dzAiFsDxP4JtDZ1NmDwm6Pjmano1Y57guU++4RvYN6YKxDnkcWXIZFpUvW+d
r7oLZaOcqwCx4KVCFo4e2qqigYgWgz8r05iEngj7UZO70n3dZrkL4Iu2tFATHLBy
1SYZIu3ewZodOeK54q63bYtVFj7ECAE4Eb7J6DgjOtN3GH9E2aKMjzFRvWzItRuf
LWIycPN/tAOh6dOPuX9oZQf71sxe3wIDAQABAoICAFzKl5kVN/qdb3VF0esV8cgP
miljYKGT+6upYUkF1svU1Q95D+TH0pY1sSUlpJvr9O9IPS18DZt+aA9RK8EX+3oL
FSwCcgh2juN28LqjWeUNwjJH176lWOLNEklzzpN9twTLBSX56UXBpFpVqOKvHmOo
UjC3hQ3yRlrf5AeKIBwpYvJHTq7wCCLAfAvXUKRu1f5jVEvYI1BhECo/LZenRXWH
IMDnR7GzG0MU9hgmVDs3FWJnGOgVXFSWNTVFs39xBNxxDJdbgAruCAV/CAvAI5V7
asjqZTEr3rJDCjOZmBGMaTq81WHr/3lQX4UJO5yfqhcOC2OlvzUPjPZ8m/PIC1C4
rOg6EqEA5X+VOspxbJGQVlsA1R1CkI499s5CERWQ8Z9Gb5kr4/SzKBnp8DAbngNR
rZxuT4pch7rHZgEDiW8h18aRN3LDvjUPF2pvowEKPRmdQJ6xTi22GuyL3pl3M8Wg
3snIl0sdfsnarWTV545bm1nIZ+4agfIzRjIc+Z4ACx3k9NBObkHxdq3Grscgl+cr
OtuQYt7T0EDfPOGqXgZ2/imdtovIYOz7BHzlchZGIFmgtSFfgZCGcWQWiXdlGmOC
EJQ9gwqLmCG4i1V8UIb2NQXPF77rHpOz/psptCxj7gvTwp8yFURL6hdqoUpzl7l+
H3QGUAl1N6vvCJry0HLBAoIBAQDmue87L+x6BBg+0g2SAO0Ivl5w2Q4KKGZREgld
7CGVyDCcZGs1EHsATZgNPUH0L9rjegTtSzarGdLPSTnDdc6mataS++3YdmWRYKxo
8so3L074FLW3acisP8YdsH99jrwkrYAm130whIkk8cEZAlSYd3uVj7RypN/WDDWm
UOmGWrQYBduGF3/JvnFbLIbassfzc7Yx2jgFDyFpQur6ZNDK3YUbjGALe07D/TO6
k4AN7NUg8J8e/nF5J2HOtGUTlHScouz3AhJFbBtGJMh2zPburR7iAU4oSCAwbiiG
3AsVqndt1iTUqkEeHUBogx9OiXfLccXxdZkXNz42Tv7ezYePAoIBAQDTnEJJV/sm
NDhMkPjKFLprdpP7n6nNlECXrNogHGTTOiXMUcgVl/CuX8cfOc7ExfJ91Bh2XqN2
H9tgOzlTWEcRORFAJv2ZRSBTfVQAboL1ncZTMXlDR3SqPVC7GC53gk4IAbm2Rs59
jqHqZJdGMHAK747zQAPO8c5qCUgDAO8hP7mrdBU9+Tj9lA0vNyGq4uUqoepUvado
Suk3CggsVjcVffzBIm8u0QHNHVUg3hVHycJTQwSIyWbej/eCx1ZD5/9olO6aREHS
lKr0Bm4+1AdlkXgJE/eoABQa1fiUgEjPbi9q4ORjpFbMbcvnb9Z6sf2VvTZn/wj6
K4JtX50o+YuxAoIBAQCa/RvvLmo0LLv8ty3Wfji8PuVB+QytViYlH3CbXxvQegHt
jKdXphJ6SaVyf0vmtJ7dYAIfRP8cQOSTyiS7YE/JCsvJQOKtHhtsZPxsI2wjVew+
Sesnoi/jRZPYLc/2kANiwAnuDaNTDDT0VFHacu5Q3TJvbXFR8d9K8ji32HKGhjek
S4sDsJVu+Dc9f9O25ZHbwEcLhgNLorZW91TRjxeSruvTbaC3FcX7cgNlud+zevxQ
fFLnhxTCxem16Qhc9sS+09NKumF7sPtBS0Q+ScE246RzPV07QfgdkGI49Weczj4b
0lY7ZYMIr62shyhooX+PcoX9hXmpVrq70KT1FiuRAoIBAQDAN8Gys9usIWU+j7We
guOvuB/GQotQ9akS3e2pm4EuqjQpe+Q/USxMiS3sPGuJLLIQAHhUFbVwGJICwOla
vuaXS3pTBtf3wOYTUNXcKoaFK9M6QMeBCMh914Kc4ONcpZ3SAhc67uik/soviz4q
gNdV57O3XF/ZPKcehN3H9LJDRoqWprSg/eD53uF3ESJhAwfeCQQ+A6SsxNdBqrgv
5gTVXgMZQPkz0qFLO6jXWUSFWE1PqqHUyvXJl4biYcYHmxbTXe27beNIsMj1L143
bgxmA5TA0kV1ctTQZ6sM4dbBrboe4Lg1ltNNkTLWQS3XeBT8Tsq7/tudu6YXSfIN
hViRAoIBAHGypG9v+vToWta0AT4CC3eOvNjzKGtr26oycFsXqQE8Q6ZKohcG0UNj
QnfawjyVhSdq2hS0O0uZuhyeea9nBtL8y8u120rvS10C71er7hG2ywscdJ4Hr5WX
D27RC+U7AwMbcqEy3Vs9vo2c5cBivLGWf/R3SgCecwxX8APysuSXod7DKhNviS4P
f8t8Tui//+PkNV6brOLvu0kITypoFhp9qAexgAuLTXOPNEILugcsfusBwPEjSdAR
LBh1fxSrGPCcRqo+8N4qorki1IE0l/bJBj3p2vREgItmq+OC0KT47Ye0BVJJtrrU
YV/U3ImFkT12e6nwfgrMRfQCZrRsp9g=
-----END PRIVATE KEY-----
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
openssl genrsa -out $1.key 2048
openssl req -new -key $1.key -out $1.csr -subj "/CN=$1"
openssl x509 -req -days 365 -in $1.csr -signkey $1.key -out $1.crt
Executable
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# DISABLE ROOT and PASSWORD LOGIN
rex "doas sh -c 'grep -q \"permit nopass 4server as root\" /etc/doas.d/4server.conf 2>/dev/null || echo \"permit nopass 4server as root\" | tee -a /etc/doas.d/4server.conf > /dev/null'"
rex "doas sh -c 'sed -i \"s/^#\?PasswordAuthentication.*/PasswordAuthentication no/\" /etc/ssh/sshd_config'"
rex "doas sh -c 'sed -i \"s/^#\?PasswordAuthentication.*/PasswordAuthentication no/\" /etc/ssh/sshd_config.d/50-cloud-init.conf'"
rex "doas sed -ri 's/^\s*#?\s*PermitRootLogin\s+.*/PermitRootLogin no/' /etc/ssh/sshd_config"
rex "doas sh -c 'echo \"PermitRootLogin no\" >> /etc/ssh/sshd_config.d/50-cloud-init.conf'"
rex doas apk del linux-virt
rex doas rc-service sshd restart
+5
View File
@@ -0,0 +1,5 @@
-----BEGIN NEBULA CERTIFICATE-----
Cj8KDU9ET080cHJvamVjdHMoqNOhvgYwqKTJogg6IDv7w4DxfOvLDJ6WgjE3V8MZ
k1I6t5GjmBmnyd0Wf0UqQAESQAzBFnjUsemshOlFCJisKbXdqBR83/Fl5aS0xSQj
ZcDIpmgPnslBHTo8oPJLWeuU0Qd9IHNfdQvam2j6YnzVQAE=
-----END NEBULA CERTIFICATE-----
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# Check for argument
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <key_name>"
exit 1
fi
key_name="$1"
target_dir="./$key_name"
# Create directory if it doesn't exist
mkdir -p "$target_dir"
# Full paths for private and public keys
private_key="$target_dir/$key_name"
public_key="$target_dir/$key_name.pub"
# Generate Ed25519 key without passphrase
ssh-keygen -t ed25519 -f "$private_key" -N "" -q
# Confirm creation
echo "SSH key pair created:"
echo "Private key: $private_key"
echo "Public key : $public_key"
+7
View File
@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBH5vr90RFgOJrP2Xjr5I5QBxlonCC7pce56JDJFboPXQAAAJh9gvJGfYLy
RgAAAAtzc2gtZWQyNTUxOQAAACBH5vr90RFgOJrP2Xjr5I5QBxlonCC7pce56JDJFboPXQ
AAAEBSerxP83/u4p/IobVSxko5ZXO+/PPczGW0kopTfLLAykfm+v3REWA4ms/ZeOvkjlAH
GWicILulx7nokMkVug9dAAAAEXJvb3RAMmNjNzhkYzBhZDIwAQIDBA==
-----END OPENSSH PRIVATE KEY-----
+6
View File
@@ -0,0 +1,6 @@
-----BEGIN NEBULA CERTIFICATE-----
CmYKA2RldhIKk5KghQyA/v//DyIDYmVlKI+YrcUGMKekyaIIOiAXY9FKiA1V6ayD
Vx9Ce9UK3YcCF93DNP68WPixdl9LZUognXOojuxdSXZ4IG4v3A8HJ/77YSYnV/il
ywmZ6V2khEESQHUVytAPARrJ0KxKPolUot6cl+UNMo5HOMqg2kxiRZBIUTp5XIME
WfrYcdjlS9af7I34439r6gs4bA2LDGaaMQs=
-----END NEBULA CERTIFICATE-----
+2
View File
@@ -0,0 +1,2 @@
API_KEY=your-secret-api-key
HOSTNAME="dev"
+3
View File
@@ -0,0 +1,3 @@
-----BEGIN NEBULA X25519 PRIVATE KEY-----
96/m6SrUsGWzT6atNvnopzygGhIAaXbBCXT8KAvwKp8=
-----END NEBULA X25519 PRIVATE KEY-----
+1
View File
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEfm+v3REWA4ms/ZeOvkjlAHGWicILulx7nokMkVug9d root@2cc78dc0ad20
+1
View File
@@ -0,0 +1 @@
sydney
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
rex "doas sqlite3 /4server/data/contracts.db <<EOF
UPDATE containers
SET affiliate = '{\"utm_source\":\"OD8N\",\"utm_medium\":\"direct\",\"utm_campaign\":\"none\"}';
EOF"
Executable
+74
View File
@@ -0,0 +1,74 @@
#!/bin/bash
template templates/hostname /etc/hostname
rex doas apk update
rex doas apk add bash doas rsync openssh linux-lts
### activate lts kerner
template templates/extlinux.conf /boot/extlinux.conf
rex doas chown root:root /boot/extlinux.conf
rex doas chmod 644 /boot/extlinux.conf
# ass swap file ????
rex doas mkdir -p /4server
rex doas chmod 777 /4server
# ----- install nabula
echo "prsync nebula bin"
prsync -h "/app/host_vars/hosts" -avz ./sbin/nebula /4server/nebula
rex doas mv /4server/nebula /usr/bin/
rex doas mkdir -p /etc/nebula
rex doas chmod 700 /etc/nebula
template templates/nebula/config.yml /etc/nebula/config.yml
template templates/nebula/host.key /etc/nebula/host.key
template templates/nebula/host.crt /etc/nebula/host.crt
template templates/nebula/ca.crt /etc/nebula/ca.crt
rex doas chmod 700 /etc/nebula
template templates/init.d/nebula /etc/init.d/nebula
rex doas chmod 0755 /etc/init.d/nebula
rex doas chown root:root /etc/init.d/nebula
rex doas rc-update add nebula default
### nebula restart must be last command
template templates/init.d/ping_service /etc/init.d/ping_service
rex doas chmod 0755 /etc/init.d/ping_service
rex doas chown root:root /etc/init.d/ping_service
rex doas rc-update add ping_service default
rex doas rc-service ping_service restart
# ADD USER 4SERVER
rex doas adduser -D -s /bin/bash 4server
SSH_DIR="/home/4server/.ssh"
rex doas mkdir -p "$SSH_DIR"
rex doas chmod 700 "$SSH_DIR"
rex doas chown 4server:4server "$SSH_DIR"
template templates/ssh/id_ed25519.pub /home/4server/.ssh/authorized_keys
rex doas chmod 755 /home/4server
rex doas chmod 700 /home/4server/.ssh
rex doas chmod 600 /home/4server/.ssh/authorized_keys
rex doas chown 4server:4server /home/4server/.ssh/authorized_keys
rex doas usermod -p Ne82Vrx8QfUdNHvLgct 4server
rex doas passwd -u 4server
template templates/.profile /home/4server/.profile
template templates/etc/doas.d/4server.conf /etc/doas.d/4server.conf
rex doas mkdir -p /etc/doas.d
rex doas rc-service sshd restart
rex doas rc-service nebula restart
rex doas reboot
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
rex doas restart
+92
View File
@@ -0,0 +1,92 @@
#!/bin/bash
dump_config (){
echo "========== 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 / $POSTGRES_ADMIN_PASSWORD"
echo "ODOO DB User: $ODOO_DB_USER"
echo "ODOO DB Password: $ODOO_DB_PASSWORD"
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
# -----------------------------
check_and_create_db() {
echo "check and create"
echo "Connecting as $POSTGRES_ADMIN_USER to $POSTGRES_HOST:$POSTGRES_PORT"
# -----------------------------
# 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 "Creating PostgreSQL user $ODOO_DB_USER..."
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';"
fi
# -----------------------------
# Check if database exists
# -----------------------------
DB_EXISTS=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" psql -h "$POSTGRES_HOST" -U "$POSTGRES_ADMIN_USER" -p "$POSTGRES_PORT" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$UUID';" | grep -q 1 && echo "yes" || echo "no")
if [ "$DB_EXISTS" = "no" ]; then
/4server/sbin/ODOO_19/restore $UUID default.zip
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"
}
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
/4server/sbin/ODOO_19/listModules $1
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
import csv
import sys
import zipfile
if len(sys.argv) < 2:
print("Usage: python3 check_odoo_version.py <dump.zip>")
sys.exit(1)
zip_path = sys.argv[1]
base_version = None
with zipfile.ZipFile(zip_path, 'r') as z:
# Assume there is only one .sql file in the zip
sql_files = [f for f in z.namelist() if f.endswith('.sql')]
if not sql_files:
print("No .sql file found in the zip.")
sys.exit(1)
sql_file_name = sql_files[0]
with z.open(sql_file_name, 'r') as f:
# Decode bytes to string
lines = (line.decode('utf-8') for line in f)
# Skip lines until COPY command
for line in lines:
if line.startswith("COPY public.ir_module_module"):
break
# Read the COPY data until the terminator '\.'
reader = csv.reader(lines, delimiter='\t', quotechar='"', escapechar='\\')
for row in reader:
if row == ['\\.']: # End of COPY
break
if len(row) < 12:
continue
module_name = row[7].strip() # 8th column = name
if module_name == "base":
base_version = row[11].strip() # 12th column = latest_version
break
if base_version:
print(base_version.split(".")[0])
else:
print("Base module not found in dump.")
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# Create the tmp directory if it doesn't exist
mkdir -p /4server/tmp/
# Save original stdout
exec 3>&1
# Redirect all other output to log
exec > /4server/data/log/importDb.log 2>&1
echo "$(date '+%Y-%m-%d %H:%M') Import file $1"
# Generate random 8-digit filename
RANDOM_FILE="/4server/tmp/import_$(date '+%Y%m%d_%H%M').zip"
# Download file from Google Drive using gdown
gdown "$1" -O "$RANDOM_FILE"
# Execute dbVersion on the downloaded file and capture output
VERSION=$(/4server/sbin/ODOO_19/dbVersion "$RANDOM_FILE")
# Output JSON to original stdout
cat <<EOF >&3
{
"version":"$VERSION",
"file":"$RANDOM_FILE"
}
EOF
# Close saved stdout
exec 3>&-
+124
View File
@@ -0,0 +1,124 @@
#!/bin/bash
export PATH=/4PROJECTS/bin:$PATH
if [ ! -n "$1" ]; then
echo "Missing Parameters <UUID>"
exit 0
fi
UUID=$1
source /4server/sbin/helpers
get_contract_info
# Query installed modules from database and save CSV in variable
DB_MODULES_CSV=$(PGPASSWORD="$POSTGRES_ADMIN_PASSWORD" PAGER= psql \
-h "$POSTGRES_HOST" \
-U "$POSTGRES_ADMIN_USER" \
-p "$POSTGRES_PORT" \
-d "$UUID" \
--csv \
-c "
SELECT
name,
latest_version,
author,
summary
FROM ir_module_module
WHERE state = 'installed'
ORDER BY name;
")
# Check if container exists and is running
if ! doas docker ps --format "{{.Names}}" | grep -q "^${UUID}$"; then
echo "Error: Container $UUID is not running" >&2
exit 1
fi
# Get addons_path from odoo.conf in container (try multiple possible locations)
ADDONS_PATH_RAW=""
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)
[ -n "$ADDONS_PATH_RAW" ] && break
done
# Also check standard Odoo library locations
ODOO_LIB_PATHS=""
# Check standard docker installation path
if doas docker exec "$UUID" test -d "/lib/python3/dist-packages/odoo/addons" 2>/dev/null; then
ODOO_LIB_PATHS="/lib/python3/dist-packages/odoo/addons"
fi
# 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
ODOO_LIB_PATHS="$ODOO_LIB_PATHS $lib_path"
done
# Collect all module directories from filesystem
FILESYSTEM_MODULES=$(mktemp)
trap "rm -f $FILESYSTEM_MODULES" EXIT
# Collect from addons_path directories
if [ -n "$ADDONS_PATH_RAW" ]; then
OLD_IFS="$IFS"
IFS=',' read -ra ADDPATHS <<< "$ADDONS_PATH_RAW"
IFS="$OLD_IFS"
for addons_dir in "${ADDPATHS[@]}"; do
addons_dir=$(echo "$addons_dir" | xargs)
[ -z "$addons_dir" ] && continue
# Get all directories in this addons path
doas docker exec "$UUID" find "$addons_dir" -maxdepth 1 -type d 2>/dev/null | \
sed "s|^$addons_dir/||" | sed "s|^$addons_dir$||" | grep -v "^$" >> "$FILESYSTEM_MODULES"
done
fi
# Also collect from standard Odoo library locations
if [ -n "$ODOO_LIB_PATHS" ]; then
for lib_path in $ODOO_LIB_PATHS; do
lib_path=$(echo "$lib_path" | xargs)
[ -z "$lib_path" ] && continue
# Get all directories in this library path
doas docker exec "$UUID" find "$lib_path" -maxdepth 1 -type d 2>/dev/null | \
sed "s|^$lib_path/||" | sed "s|^$lib_path$||" | grep -v "^$" >> "$FILESYSTEM_MODULES"
done
fi
# Sort and deduplicate filesystem modules
sort -u "$FILESYSTEM_MODULES" > "${FILESYSTEM_MODULES}.sorted"
mv "${FILESYSTEM_MODULES}.sorted" "$FILESYSTEM_MODULES"
# Process database modules and find missing ones
MISSING_MODULES=$(mktemp)
trap "rm -f $FILESYSTEM_MODULES $MISSING_MODULES" EXIT
# Process each line from database CSV (skip header)
echo "$DB_MODULES_CSV" | tail -n +2 | while IFS= read -r line; do
[ -z "$line" ] && continue
# Extract module name from first CSV field
# Handle quoted CSV: extract first field, remove surrounding quotes
if echo "$line" | grep -q '^"'; then
# First field is quoted - extract between first quote and comma
name=$(echo "$line" | sed 's/^"\([^"]*\)".*/\1/')
else
# First field is not quoted - extract up to first comma
name=$(echo "$line" | cut -d',' -f1)
fi
name=$(echo "$name" | xargs)
[ -z "$name" ] && continue
# Check if module exists in filesystem
if ! grep -Fxq "$name" "$FILESYSTEM_MODULES"; then
# Module not found in filesystem - output the full CSV line
echo "$line" >> "$MISSING_MODULES"
fi
done
# Output missing modules if any were found
if [ -s "$MISSING_MODULES" ]; then
echo "Missing modules:"
echo "$DB_MODULES_CSV" | head -n 1 # Output CSV header
cat "$MISSING_MODULES"
fi
+68
View File
@@ -0,0 +1,68 @@
#!/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
### 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;"
doas docker exec "$UUID" rm -rf /home/odoo/.local/share/Odoo/filestore
doas docker exec "$UUID" rm -rf /root/.local/share/Odoo/filestore
doas docker exec "$UUID" odoo db --db_host beedb -w "$ODOO_DB_PASSWORD" -r "$UUID" load "$UUID" $FILENAME -f
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"
+22
View File
@@ -0,0 +1,22 @@
#!/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"
+60
View File
@@ -0,0 +1,60 @@
#!/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
Executable
+580
View File
@@ -0,0 +1,580 @@
#!/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
secret_list: Optional[str] = None
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,
utm_source, utm_medium, utm_campaign, domains_slots, secret_list, env_list)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
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,
request.secret_list,
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)
+591
View File
@@ -0,0 +1,591 @@
#!/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)
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
exec >> /4server/data/log/backupContainer.log 2>&1
echo "$(date '+%Y-%m-%d %H:%M') Backup container $1"
source /4server/sbin/helpers
BIN_PATH="/4server/sbin"
UUID="$1"
if [[ -z "$UUID" ]]; then
echo "Usage: $0 <uuid>"
exit 1
fi
get_contract_info
# Extract the second part of UUID (split by "-")
SECOND_PART=$(echo "$UUID" | cut -d'-' -f2)
echo "Conatiner Type $UUID"
# Decide which script to run
case "$SECOND_PART" in
001)
"$BIN_PATH/backup/N8N"
;;
002)
"$BIN_PATH/backup/ODOO"
;;
003)
"$BIN_PATH/backup/ODOO"
;;
004)
"$BIN_PATH/backup/ODOO"
;;
*)
echo "Unknown UUID type: $SECOND_PART"
exit 2
;;
esac
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# ssh -i ~/.ssh/borg-backup e7e9h45y@e7e9h45y.repo.borgbase.com
set -e
export BORG_REPO="ssh://e7e9h45y@e7e9h45y.repo.borgbase.com/./repo"
export BORG_RSH="ssh -i ~/.ssh/borg-backup"
export BORG_PASSPHRASE="Airbus12"
HOSTNAME=$(hostname)
DATE=$(date +%Y-%m-%d_%H-%M-%S)
ARCHIVE_NAME="${HOSTNAME}-${DATE}"
# Resolve symlinks externally and feed real paths to borg via --paths-from-stdin.
# - First find: locates all current* symlinks under /BACKUP and resolves them with realpath.
# - Second find: locates all regular current* files under /BACKUP (non-symlinks).
# - sort -u: deduplicates in case a resolved symlink target overlaps with a regular file.
{
find /BACKUP -name 'current*' -type l -print0 | xargs -0 -I {} realpath {}
find /BACKUP -name 'current*' ! -type l
} | sort -u | borg create \
--verbose \
--stats \
--compression lz4 \
--paths-from-stdin \
::$ARCHIVE_NAME
echo "Backup completed: $ARCHIVE_NAME"
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
cd /4server/data/
while :
do
for dir in ???-???-*; do
if [ -d "${dir}/cc" ]; then
if [ -f "${dir}/cc/backup" ]; then
echo "BACKUP for: ${dir%/}"
/4server/sbin/backupContainer ${dir%/} 2
rm "${dir}/cc/backup"
fi
if [ -f "${dir}/cc/restart" ]; then
echo "Restart for: ${dir%/}"
/4server/sbin/startContainer ${dir%/}
rm "${dir}/cc/restart"
fi
if [ -f "${dir}/cc/restore" ]; then
FILENAME=$(head -n 1 "${dir}/cc/restore")
echo "Restore for: ${dir%/} - $FILENAME"
/4server/sbin/ODOO_19/restore ${dir%/} $FILENAME
rm "${dir}/cc/restore"
fi
fi
if [ -d "${dir}/n8n" ]; then
if [ -f "${dir}/n8n/database.sqlite-journal" ]; then
echo "Restart N8N (journal)for: ${dir%/}"
/4server/sbin/startContainer ${dir%/}
fi
fi
done
sleep 60
done
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
while true; do
find /4server/tmp -type f -atime +2 -delete
find /4server/tmp -type d -empty -delete
sleep 1d
done
+14
View File
@@ -0,0 +1,14 @@
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
env
Executable
+39
View File
@@ -0,0 +1,39 @@
#!/bin/sh
# Log file
OUTPUT="/4server/data/log/cpu_idle.log"
# Sampling interval in seconds
INTERVAL=60
while true; do
DATA="" # buffer to store idle samples
# Current date for the measurement period
DATE=$(date "+%Y-%m-%d")
echo "Starting measurement for $DATE"
while [ "$(date +%H:%M)" != "23:45" ]; do
# Get idle CPU percentage
IDLE=$(mpstat 1 1 | awk '/Average/ {print $12}')
# Append to buffer
DATA="$DATA$IDLE\n"
sleep $INTERVAL
done
# Write all data to log file with date
# Only one line per day: Date + space-separated idle samples
echo -n "$DATE " >> "$OUTPUT"
echo -e "$DATA" | tr '\n' ' ' >> "$OUTPUT"
echo >> "$OUTPUT" # newline at the end
echo "Measurement for $DATE written to $OUTPUT"
# Wait until 00:15 to start next day
while [ "$(date +%H:%M)" != "00:15" ]; do
sleep 30
done
done
+61
View File
@@ -0,0 +1,61 @@
#!/bin/bash
# Collect running container IDs
container_ids=$(docker ps -q)
# Start JSON array
echo "["
first=true
for cid in $container_ids; do
# Get basic info
name=$(docker inspect --format '{{.Name}}' "$cid" | sed 's/^\/\(.*\)/\1/')
image=$(docker inspect --format '{{.Config.Image}}' "$cid")
container_id=$(docker inspect --format '{{.Id}}' "$cid")
# RAM usage in bytes (from docker stats)
mem_usage=$(docker stats --no-stream --format "{{.MemUsage}}" "$cid")
# mem_usage looks like "12.34MiB / 1.944GiB" — extract the used part
mem_used=$(echo "$mem_usage" | awk '{print $1}')
# Convert mem_used to MiB as a number
# Support KiB, MiB, GiB units
ram_usage=$(echo "$mem_used" | awk '
{
val = substr($0, 1, length($0)-3)
unit = substr($0, length($0)-2, 3)
if (unit == "KiB") print val / 1024
else if (unit == "MiB") print val
else if (unit == "GiB") print val * 1024
else print 0
}')
# Traefik labels (extract labels JSON)
labels=$(docker inspect --format '{{json .Config.Labels}}' "$cid" | jq -r 'to_entries | map(select(.key | test("^traefik.*"))) | map({(.key): .value}) | add')
# Comma between JSON objects
if [ "$first" = false ]; then
echo ","
fi
first=false
# Output JSON object
jq -n \
--arg name "$name" \
--arg id "$container_id" \
--arg image "$image" \
--argjson ram "$ram_usage" \
--argjson tags "$labels" \
'{
name: $name,
id: $id,
image: $image,
ram_mb: $ram,
traefik_tags: $tags
}'
done
# End JSON array
echo "]"
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
docker exec $1 /gitPull
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
docker exec $1 /gitRollBack
+51
View File
@@ -0,0 +1,51 @@
#!/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
export ODOO_DB_PASSWORD=$psql
}
+87
View File
@@ -0,0 +1,87 @@
#!/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)
}
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
sqlite3 /4server/data/contracts.db <<EOF
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
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+54
View File
@@ -0,0 +1,54 @@
#!/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)."
+68
View File
@@ -0,0 +1,68 @@
#!/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
echo "$(date '+%Y-%m-%d %H:%M') Nuke container $1"
source /4server/sbin/helpers
BIN_PATH="/4server/sbin"
UUID="$1"
if [[ -z "$UUID" ]]; then
echo "Usage: $0 <uuid>"
exit 1
fi
get_container_status() {
local uuid="$1"
# Get the container ID or name matching the UUID
CONTAINER_ID=$(docker ps -a --filter "name=$uuid" --format "{{.ID}}")
if [[ -z "$CONTAINER_ID" ]]; then
echo "not_found"
return
fi
STATUS=$(docker inspect -f '{{.State.Status}}' "$CONTAINER_ID")
echo "$STATUS"
}
# Check if container exists
STATUS=$(get_container_status "$UUID")
if [[ "$STATUS" == "running" ]]; then
echo "Container $UUID is still running. Aborting deletion."
exit 2
fi
get_contract_info
# Extract the second part of UUID (split by "-")
SECOND_PART=$(echo "$UUID" | cut -d'-' -f2)
# Decide which script to run
case "$SECOND_PART" in
001)
"$BIN_PATH/nuke/n8n"
;;
002)
"$BIN_PATH/nuke/ODOO_19"
;;
*)
echo "Unknown UUID type: $SECOND_PART"
exit 2
;;
esac
#sqlite3 "/4server/data/contracts.db" <<SQL
#DELETE FROM containers WHERE UUID='$UUID';
#SQL
echo "Container $UUID successfully nuked."
+57
View File
@@ -0,0 +1,57 @@
#!/bin/bash
# Usage: ./odoo_passwd.sh <container_name>
CONTAINER=$1
if [ -z "$CONTAINER" ]; then
echo "Usage: $0 <docker_container>"
exit 1
fi
# Step 1: Get users list
echo "Fetching users..."
USERS=$(doas docker exec -i "$CONTAINER" bash -c \
'PGPASSWORD="$PASSWORD" psql -h "$HOST" -U "$USER" -At -F"," -c "SELECT id, login FROM res_users ORDER BY id;"')
# Step 2: Display users
echo "Select a user:"
IFS=$'\n'
select USER_LINE in $USERS; do
if [ -n "$USER_LINE" ]; then
break
fi
done
USER_ID=$(echo "$USER_LINE" | cut -d',' -f1)
USER_LOGIN=$(echo "$USER_LINE" | cut -d',' -f2)
echo "Selected user: $USER_LOGIN (ID: $USER_ID)"
# Step 3: Ask for new password
read -s -p "Enter new password: " NEW_PASS
echo
read -s -p "Confirm password: " CONFIRM_PASS
echo
if [ "$NEW_PASS" != "$CONFIRM_PASS" ]; then
echo "Passwords do not match!"
exit 1
fi
# Step 4: Generate Odoo-compatible hash using Python inside container
HASH=$(doas docker exec -i "$CONTAINER" python3 - <<EOF
from passlib.context import CryptContext
ctx = CryptContext(schemes=['pbkdf2_sha512'])
print(ctx.hash("$NEW_PASS"))
EOF
)
echo "Updating password..."
# Step 5: Update DB
doas docker exec -i "$CONTAINER" bash -c \
"PGPASSWORD=\"$PASSWORD\" psql -h \"$HOST\" -U \"$USER\" -c \"UPDATE res_users SET password='$HASH' WHERE id=$USER_ID;\""
echo "Password updated successfully for $USER_LOGIN"
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
doas docker exec -it $1 bash -c 'PGPASSWORD="$PASSWORD" psql -h "$HOST" -U "$USER"'
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
exec > /4server/data/log/system_pull.log 2>&1
echo "$(date '+%Y-%m-%d %H:%M') Pulling images"
docker ps -a --format '{{.Image}}' \
| grep -vE '^[0-9a-f]{12,}$' \
| sort -u \
| xargs -n1 docker pull
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# Check that a search string was provided
if [ -z "$1" ]; then
echo "Usage: $0 <search_string>"
exit 1
fi
SEARCH="$1"
DB_PATH="/4server/data/contracts.db"
# Get all UUIDs where tags include the search string
uuids=$(sqlite3 "$DB_PATH" "SELECT UUID FROM containers WHERE tags LIKE '%$SEARCH%';")
# Loop through each UUID
for uuid in $uuids; do
# Check if a container with this name is running
if doas docker ps --format '{{.Names}}' | grep -qx "$uuid"; then
echo "Restarting $uuid"
/4server/sbin/startContainer $uuid
fi
done
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
doas docker exec -it $1 bash -c 'PGPASSWORD="$PASSWORD" psql -h "$HOST" -U "$USER"'
+127
View File
@@ -0,0 +1,127 @@
#!/bin/bash
echo "START ODOO 17"
# 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 "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
echo "DBID: $CONTAINERDBID"
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")
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 "[DEBUG] No additional domain file found at $BASEURL/etc/domain"
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 "[DEBUG] Could not resolve $d, skipping"
continue
fi
if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then
RULE+=" || Host(\`$d\`)"
else
echo "[DEBUG] Skipping $d (does not match SERVER_IP $SERVER_IP)"
fi
done
RULE="${RULE# || }"
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE"
echo "[DEBUG] Final Traefik label: $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))
echo "PORT $PORT"
mkdir -p ${ETC_DIR}
echo "GITPATH ${ETC_DIR}gitpath"
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"
docker stop "$UUID" 2>/dev/null
docker rm "$UUID" 2>/dev/null
EXTRA_DOCKER_PARAMETER=""
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
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
check_and_create_db
+127
View File
@@ -0,0 +1,127 @@
#!/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 "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
echo "DBID: $CONTAINERDBID"
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")
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 "[DEBUG] No additional domain file found at $BASEURL/etc/domain"
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 "[DEBUG] Could not resolve $d, skipping"
continue
fi
if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then
RULE+=" || Host(\`$d\`)"
else
echo "[DEBUG] Skipping $d (does not match SERVER_IP $SERVER_IP)"
fi
done
RULE="${RULE# || }"
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE"
echo "[DEBUG] Final Traefik label: $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))
echo "PORT $PORT"
mkdir -p ${ETC_DIR}
echo "GITPATH ${ETC_DIR}gitpath"
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"
docker stop "$UUID" 2>/dev/null
docker rm "$UUID" 2>/dev/null
EXTRA_DOCKER_PARAMETER=""
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
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
check_and_create_db
+125
View File
@@ -0,0 +1,125 @@
#!/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 "***** $ODOO_DB_PASSWORD"
echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
echo "DBID: $CONTAINERDBID"
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")
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 "[DEBUG] No additional domain file found at $BASEURL/etc/domain"
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 "[DEBUG] Could not resolve $d, skipping"
continue
fi
if [ "$RESOLVED_IP" = "$SERVER_IP" ]; then
RULE+=" || Host(\`$d\`)"
else
echo "[DEBUG] Skipping $d (does not match SERVER_IP $SERVER_IP)"
fi
done
RULE="${RULE# || }"
DOMAIN_LABEL="traefik.http.routers.$UUID.rule=$RULE"
echo "[DEBUG] Final Traefik label: $DOMAIN_LABEL"
find "$BASEURL" -type d -exec chmod 777 {} \;
PORT=$((CONTAINERDBID + 2200))
echo "PORT $PORT"
mkdir -p ${ETC_DIR}
chmod 777 ${ETC_DIR}
echo "GITPATH ${ETC_DIR}gitpath"
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}.odoo4projects.com:${PORT}/git-server/repos/odoo.git\"" > "${ETC_DIR}/gitpath"
docker stop "$UUID" 2>/dev/null
docker rm "$UUID" 2>/dev/null
EXTRA_DOCKER_PARAMETER=""
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_19:$BRANCH
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
check_and_create_db
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
#--label "traefik.http.routers.${UUID}.middlewares=cors-headers@file" \
echo "Start N8N container ${UUID}"
# Get the hostname of the machine
HOSTNAME=$(hostname)
mkdir -p /4server/data/${UUID}/n8n
mkdir -p /4server/data/${UUID}/data
mkdir -p /4server/data/${UUID}/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
if docker ps -a --format '{{.Names}}' | grep -q "^${UUID}$"; then
echo "$(date '+%Y-%m-%d %H:%M') - stopping existing container $UUID"
docker stop "$UUID"
docker rm "$UUID"
fi
docker run -d \
--name "$UUID" \
-p 5678 \
--cap-add=SYS_ADMIN \
--security-opt seccomp=unconfined \
--restart=always \
-e N8N_EMAIL_MODE=smtp \
-e N8N_SMTP_HOST="smtp.strato.de" \
-e N8N_SMTP_PORT=587 \
-e N8N_SMTP_USER="n8n@odoo4projects.com" \
-e N8N_SMTP_PASS="Airbus12N@N" \
-e N8N_SMTP_SENDER="n8n <n8n@odoo4projects.com>" \
-e N8N_SMTP_SSL=false \
-e N8N_HOST="${UUID}.odoo4projects.com" \
-e N8N_PORT=5678 \
-e N8N_PROTOCOL=https \
-e N8N_FORMDATA_FILE_SIZE_MAX=3000 \
-e N8N_TRUST_PROXY=true \
-e NODE_ENV=production \
-e EXECUTIONS_PRUNE=true \
-e EXECUTIONS_PRUNE_MAX_AGE=48 \
-e WEBHOOK_URL="https://${UUID}.odoo4projects.com/" \
-e GENERIC_TIMEZONE="UTC-3" \
-e N8N_BLOCK_ENV_ACCESS_AND_PROCESS_INFORMATION=true \
-e N8N_CUSTOM_EXTENSIONS="/usr/local/share/n8n/custom" \
-v "/4server/data/${UUID}/n8n:/home/node/.n8n" \
-v "/4server/data/${UUID}/data:/data" \
-v "/4server/data/${UUID}/backup:/backup" \
--label "traefik.enable=true" \
--label "traefik.http.routers.${UUID}.rule=Host(\`${UUID}.odoo4projects.com\`)" \
--label "traefik.http.routers.${UUID}.entrypoints=web,websecure" \
--label "traefik.http.routers.${UUID}.tls=true" \
--label "traefik.http.routers.${UUID}.tls.certresolver=production" \
--label "traefik.http.services.${UUID}.loadbalancer.server.port=5678" \
--network 4server_4projects \
docker.odoo4projects.com/4projects/n8n:release
echo "Started $1"
+65
View File
@@ -0,0 +1,65 @@
#!/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
echo "$(date '+%Y-%m-%d %H:%M') Start container $1"
source /4server/sbin/helpers
BIN_PATH="/4server/sbin"
UUID="$1"
if [[ -z "$UUID" ]]; then
echo "Usage: $0 <uuid>"
exit 1
fi
if [[ ! "$UUID" =~ ^[0-9a-fA-F-]+$ ]]; then
echo "ERROR: Invalid UUID format: $UUID"
exit 1
fi
DOMAIN_FILE="/4server/data/$UUID/etc/domain"
DB_FILE="/4server/data/contracts.db"
if [ -f "$DOMAIN_FILE" ]; then
DOMAINS=$(paste -sd "," "$DOMAIN_FILE")
# Escape single quotes for SQLite ('' is the standard SQLite escape for ')
DOMAINS_SAFE="${DOMAINS//\'/\'\'}"
UUID_SAFE="${UUID//\'/\'\'}"
sqlite3 "$DB_FILE" "UPDATE containers SET domains='$DOMAINS_SAFE' WHERE UUID='$UUID_SAFE';"
fi
get_contract_info
# Extract the second part of UUID (split by "-")
SECOND_PART=$(echo "$UUID" | cut -d'-' -f2)
# Decide which script to run
case "$SECOND_PART" in
001)
"$BIN_PATH/start/n8n"
;;
002)
"$BIN_PATH/start/ODOO_18"
;;
003)
"$BIN_PATH/start/ODOO_19"
;;
004)
"$BIN_PATH/start/ODOO_17"
;;
*)
echo "Unknown UUID type: $SECOND_PART"
exit 2
;;
esac
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
exec > /4server/data/log/stopContainer.log 2>&1
echo "$(date '+%Y-%m-%d %H:%M') Stop container $1"
docker stop $1
+7
View File
@@ -0,0 +1,7 @@
# ~/.bashrc
clear
echo "Server {{HOSTNAME}} IP: $(ip route get 1.1.1.1 | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}')"
export PS1="\[\e[32m\]\h:\w\$\[\e[0m\] "
df -h .
cd /4server
+1
View File
@@ -0,0 +1 @@
API_KEY={{API_KEY}}
+42
View File
@@ -0,0 +1,42 @@
services:
beedb:
image: postgres:16
restart: always
environment:
- POSTGRES_DB=postgres
- POSTGRES_PASSWORD=ZpSwWNafyy9GhY2gzHw
- POSTGRES_USER=1gtT0sf8klB9lDbYZD9
volumes:
- /4server/data/postgres/data/:/var/lib/postgresql/data/
- /4server/data/postgres/pg_backup/:/BACKUP/
- /4server/data/postgres/etc/:/etc/postgresql/16/main/
networks:
4projects:
ipv4_address: 10.5.0.200
traefik:
image: docker.io/library/traefik:3.1
container_name: traefik
ports:
- 80:80
- 443:443
volumes:
- /run/docker.sock:/run/docker.sock:ro
- /4server/data/traefik/etc:/etc/traefik
- /4server/data/traefik/certs:/certs
- /4server/data/log/traefik-logs:/var/log/traefik
networks:
- 4projects
restart: unless-stopped
networks:
4projects:
driver: bridge
ipam:
config:
- subnet: 10.5.0.0/16
gateway: 10.5.0.1
ip_range: 10.5.0.0/26
+1
View File
@@ -0,0 +1 @@
permit nopass 4server as root
+10
View File
@@ -0,0 +1,10 @@
[DEFAULT]
bantime = 1h
findtime = 30m
maxretry = 1
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
+23
View File
@@ -0,0 +1,23 @@
SERIAL ttyS0 115200
DEFAULT menu.c32
PROMPT 0
MENU TITLE Alpine/Linux Boot Menu
MENU HIDDEN
MENU AUTOBOOT Alpine will be booted automatically in # seconds.
TIMEOUT 100
LABEL virt
MENU LABEL Linux virt
LINUX vmlinuz-virt
INITRD initramfs-virt
APPEND root=UUID=15665bb5-c475-4ac8-9479-d49df97d5ccb modules=sd-mod,usb-storage,ext4 console=ttyS0,115200n8 console=ttyAMA0,115200n8
LABEL lts
MENU DEFAULT
MENU LABEL Linux lts
LINUX vmlinuz-lts
INITRD initramfs-lts
APPEND root=UUID=15665bb5-c475-4ac8-9479-d49df97d5ccb modules=sd-mod,usb-storage,ext4 console=ttyS0,115200n8 console=ttyAMA0,115200n8
MENU SEPARATOR
+23
View File
@@ -0,0 +1,23 @@
SERIAL ttyS0 115200
DEFAULT menu.c32
PROMPT 0
MENU TITLE Alpine/Linux Boot Menu
MENU HIDDEN
MENU AUTOBOOT Alpine will be booted automatically in # seconds.
TIMEOUT 100
LABEL virt
MENU LABEL Linux virt
LINUX vmlinuz-virt
INITRD initramfs-virt
APPEND root=UUID=15665bb5-c475-4ac8-9479-d49df97d5ccb modules=sd-mod,usb-storage,ext4 console=ttyS0,115200n8 console=ttyAMA0,115200n8
LABEL lts
MENU DEFAULT
MENU LABEL Linux lts
LINUX vmlinuz-lts
INITRD initramfs-lts
APPEND root=UUID=15665bb5-c475-4ac8-9479-d49df97d5ccb modules=sd-mod,usb-storage,ext4 console=ttyS0,115200n8 console=ttyAMA0,115200n8
MENU SEPARATOR
+23
View File
@@ -0,0 +1,23 @@
SERIAL ttyS0 115200
DEFAULT menu.c32
PROMPT 0
MENU TITLE Alpine/Linux Boot Menu
MENU HIDDEN
MENU AUTOBOOT Alpine will be booted automatically in # seconds.
TIMEOUT 100
LABEL virt
MENU LABEL Linux virt
LINUX vmlinuz-virt
INITRD initramfs-virt
APPEND root=LABEL=/ modules=sd-mod,usb-storage,ext4 console=ttyS0,115200n8 console=ttyAMA0,115200n8
LABEL lts
MENU DEFAULT
MENU LABEL Linux lts
LINUX vmlinuz-lts
INITRD initramfs-lts
APPEND root=LABEL=/ modules=sd-mod,usb-storage,ext4 console=ttyS0,115200n8 console=ttyAMA0,115200n8
MENU SEPARATOR
+1
View File
@@ -0,0 +1 @@
{{HOSTNAME}}
+2
View File
@@ -0,0 +1,2 @@
127.0.0.1 {{hostname}} localhost
10.5.0.200 beedb
+30
View File
@@ -0,0 +1,30 @@
#!/sbin/openrc-run
name="api"
description="4server API Service"
# Command uses Python inside the venv
command="/4server/sbin/api"
command_args=""
pidfile="/run/${RC_SVCNAME}.pid"
command_background="yes"
respawn_delay=5 # seconds to wait before restart
respawn_max=0 # 0 = unlimited restarts
# Load environment variables if needed
if [ -f /etc/4server ]; then
. /etc/4server
export $(cut -d= -f1 /etc/4server)
fi
# Logs
output_log="/4server/data/log/api.log"
error_log="/4server/data/log/api.log"
depend() {
need net
use logger dns
after firewall
}
+30
View File
@@ -0,0 +1,30 @@
#!/sbin/openrc-run
name="checkCalls"
description="check container service calls"
# Command uses Python inside the venv
command="/4server/sbin/checkCalls"
command_args=""
pidfile="/run/${RC_SVCNAME}.pid"
command_background="yes"
directory="/4server"
command_user="root"
respawn_delay=5 # seconds to wait before restart
respawn_max=0 # 0 = unlimited restarts
# Load environment variables if needed
output_log="/4server/data/log/checkCalls.log"
error_log="/4server/data/log/checkCalls.log"
depend() {
need net
use logger dns
after firewall
}
+17
View File
@@ -0,0 +1,17 @@
#!/sbin/openrc-run
# OpenRC service for /4server/sbin/cleanTmp
name="cleanTmp"
description="Looping /tmp cleaner that removes files older than 2 days"
command="/4server/sbin/cleanTmp"
command_background="yes"
pidfile="/run/${RC_SVCNAME}.pid"
output_log="/4server/data/log/checkCalls.log"
error_log="/4server/data/log/checkCalls.log"
depend() {
need localmount
after bootmisc
}
+17
View File
@@ -0,0 +1,17 @@
#!/sbin/openrc-run
# OpenRC service for /4server/sbin/cpu
name="cpu"
description="Logs cpu usage"
command="/4server/sbin/cpu"
command_background="yes"
pidfile="/run/cpu.pid"
output_log="/4server/data/log/cpu.log"
error_log="/4server/data/log/cpu.log"
depend() {
need localmount
after bootmisc
}
+12
View File
@@ -0,0 +1,12 @@
#!/sbin/openrc-run
command="/usr/bin/nebula"
command_args="-config /etc/nebula/config.yml"
command_background="yes"
pidfile="/run/nebula.pid"
name="nebula"
depend() {
need localmount
after networking
}
+17
View File
@@ -0,0 +1,17 @@
#!/sbin/openrc-run
description="Ping 192.168.9.1 every minute"
pidfile="/run/ping_check.pid"
start() {
ebegin "Starting ping_check service"
start-stop-daemon --start --background --make-pidfile --pidfile $pidfile --exec /bin/sh -- -c "while true; do ping -c 1 192.168.9.1; sleep 60; done"
eend $?
}
stop() {
ebegin "Stopping ping_check service"
start-stop-daemon --stop --pidfile $pidfile
eend $?
}
+1
View File
@@ -0,0 +1 @@
{{NEBULA_CA}}
+65
View File
@@ -0,0 +1,65 @@
pki:
ca: /etc/nebula/ca.crt
cert: /etc/nebula/host.crt
key: /etc/nebula/host.key
static_host_map:
"192.168.9.1": ["167.71.79.60:4242"]
lighthouse:
am_lighthouse: false
interval: 60
hosts:
listen:
host: 0.0.0.0
port: 4242
punchy:
punch: true
relay:
am_relay: false
use_relays: true
tun:
disabled: false
dev: nebula2
drop_local_broadcast: false
drop_multicast: false
tx_queue: 500
mtu: 1300
routes:
#- mtu: 8800
# route: 10.0.0.0/16
unsafe_routes:
logging:
level: info
format: text
firewall:
outbound_action: drop
inbound_action: drop
conntrack:
tcp_timeout: 12m
udp_timeout: 3m
default_timeout: 10m
outbound:
- port: any
proto: any
host: any
inbound:
- port: any #ping
proto: icmp
groups:
- admin
- port: 22 #GIT
proto: tcp
groups:
- admin
+1
View File
@@ -0,0 +1 @@
{{NEBULA_CRT}}
+1
View File
@@ -0,0 +1 @@
{{NEBULA_KEY}}
+3
View File
@@ -0,0 +1,3 @@
# Example: allow your host to connect to all DBs as any user with password
host all all 10.5.0.1/32 md5
+3
View File
@@ -0,0 +1,3 @@
https://dl-cdn.alpinelinux.org/alpine/v3.22/main
https://dl-cdn.alpinelinux.org/alpine/v3.22/community
+1
View File
@@ -0,0 +1 @@
{{SSH_PRIVATE}}
+1
View File
@@ -0,0 +1 @@
{{SSH_PUBLIC}}
+103
View File
@@ -0,0 +1,103 @@
global:
checkNewVersion: false
sendAnonymousUsage: false
accesslog:
filePath: /var/log/traefik/access.log
api:
dashboard: false
disableDashboardAd: true
insecure: false
entryPoints:
web:
address: :80
# -- (Optional) Redirect all HTTP to HTTPS
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
# http:
# middlewares:
# - crowdsec-bouncer@file
address: :443
transport:
respondingTimeouts:
readTimeout: 0
writeTimeout: 0
idleTimeout: 42
# -- Configure your CertificateResolver here...
certificatesResolvers:
# staging:
# acme:
# email: your-email@example.com
# storage: /etc/traefik/certs/acme.json
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
# httpChallenge:
# entryPoint: web
production:
acme:
email: oliver@odoo4projects.com
storage: /certs/acme.json
caServer: "https://acme-v02.api.letsencrypt.org/directory"
httpChallenge:
entryPoint: web
# -- (Optional) Disable TLS Cert verification check
# serversTransport:
# insecureSkipVerify: true
# -- (Optional) Overwrite Default Certificates
# tls:
# stores:
# default:
# defaultCertificate:
# certFile: /etc/traefik/certs/cert.pem
# keyFile: /etc/traefik/certs/cert-key.pem
# -- (Optional) Disable TLS version 1.0 and 1.1
# options:
# default:
# minVersion: VersionTLS12
providers:
docker:
# -- (Optional) Enable this, if you want to expose all containers automatically
exposedByDefault: true
file:
directory: /etc/traefik
watch: true
http:
middlewares:
crowdsec-bouncer:
forwardauth:
address: http://bouncer-traefik:8080/api/v1/forwardAuth
trustForwardHeader: true
cors-headers:
headers:
accessControlAllowCredentials: true
routers:
api-router:
rule: "Host(`{{HOSTNAME}}.odoo4projects.com`)"
service: api-service
entryPoints:
- websecure
tls:
certResolver: production
services:
api-service:
loadBalancer:
servers:
- url: "http://10.5.0.1:8888"
+233
View File
@@ -0,0 +1,233 @@
{
"openapi": "3.0.0",
"info": {
"title": "Container Management API",
"version": "0.0.5",
"description": "API for managing containers and system resources."
},
"servers": [
{ "url": "http://localhost:8888" }
],
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key"
}
},
"schemas": {
"ContainerModel": {
"type": "object",
"properties": {
"UUID": { "type": "string" },
"email": { "type": "string" },
"expires": { "type": "string", "format": "date" },
"tags": { "type": "string" },
"env": { "type": "string" },
"affiliate": { "type": "string" },
"image": { "type": "string" },
"history": { "type": "string" },
"comment": { "type": "string" },
"domains": { "type": "string" },
"status": { "type": "string" },
"created": { "type": "string", "format": "date" },
"bump": { "type": "string", "format": "date" }
},
"required": ["UUID", "email", "expires", "status", "created"]
},
"ContainerIDRequest": {
"type": "object",
"properties": {
"container_id": { "type": "string" }
},
"required": ["container_id"]
},
"InfoContainerRequest": {
"type": "object",
"properties": {
"container_id": { "type": "string" }
}
}
}
},
"security": [{ "ApiKeyAuth": [] }],
"paths": {
"/": {
"get": {
"summary": "Root redirect",
"responses": {
"302": { "description": "Redirect to ODOO4PROJECTS.com" }
}
}
},
"/container/update": {
"post": {
"summary": "Create or update container",
"security": [{ "ApiKeyAuth": [] }],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ContainerModel" }
}
}
},
"responses": {
"200": {
"description": "Container updated or created",
"content": { "application/json": { "example": { "message": "Container updated or created" } } }
}
}
}
},
"/container/start": {
"post": {
"summary": "Start a container",
"security": [{ "ApiKeyAuth": [] }],
"requestBody": {
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContainerIDRequest" } } }
},
"responses": { "200": { "description": "Container started" } }
}
},
"/container/stop": {
"post": {
"summary": "Stop a container",
"security": [{ "ApiKeyAuth": [] }],
"requestBody": {
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContainerIDRequest" } } }
},
"responses": { "200": { "description": "Container stopped" } }
}
},
"/container/nuke": {
"post": {
"summary": "Nuke a container",
"description": "Permanently deletes container if its status is 'nuke'.",
"security": [{ "ApiKeyAuth": [] }],
"requestBody": {
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContainerIDRequest" } } }
},
"responses": {
"200": { "description": "Container nuked" },
"400": { "description": "Container status is not 'nuke'" }
}
}
},
"/container/info": {
"post": {
"summary": "Get container info",
"security": [{ "ApiKeyAuth": [] }],
"requestBody": {
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/InfoContainerRequest" } } }
},
"responses": { "200": { "description": "Container info list" } }
}
},
"/container/bump": {
"post": {
"summary": "Bump a container",
"description": "Updates bump date and runs bump script.",
"security": [{ "ApiKeyAuth": [] }],
"requestBody": {
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContainerIDRequest" } } }
},
"responses": {
"200": { "description": "Bump successful", "content": { "application/json": {} } }
}
}
},
"/container/quota": {
"post": {
"summary": "Get container quota",
"security": [{ "ApiKeyAuth": [] }],
"requestBody": {
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContainerIDRequest" } } }
},
"responses": {
"200": {
"description": "Quota usage",
"content": {
"application/json": {
"example": {
"memory_usage": "50MiB / 2GiB",
"disk_io": "10MB / 200MB"
}
}
}
}
}
}
},
"/system/containers": {
"get": {
"summary": "List running containers",
"security": [{ "ApiKeyAuth": [] }],
"responses": {
"200": {
"description": "Containers in system",
"content": { "application/json": {} }
}
}
}
},
"/system/images": {
"get": {
"summary": "List system images",
"security": [{ "ApiKeyAuth": [] }],
"responses": {
"200": {
"description": "List of docker images",
"content": {
"application/json": {
"example": {
"images": ["repo1:tag", "repo2:tag"]
}
}
}
}
}
}
},
"/system/info": {
"get": {
"summary": "Get system info",
"security": [{ "ApiKeyAuth": [] }],
"responses": {
"200": {
"description": "System resource info",
"content": {
"application/json": {
"example": {
"alpine_version": "3.18.2",
"last_update": "2025-08-01",
"latest_bump": "2025-08-10",
"version": "API: 0.0.5",
"resources": {
"memory": { "total": 16777216, "available": 8388608, "used": 8388608 },
"disk": { "total": 100000000, "used": 50000000, "free": 50000000 },
"cpu_count": 8
}
}
}
}
}
}
}
},
"/system/pull": {
"post": {
"summary": "Pull all images",
"security": [{ "ApiKeyAuth": [] }],
"responses": {
"200": {
"description": "All images pulled",
"content": { "application/json": { "example": { "message": "All images pulled" } } }
}
}
}
}
}
}
+53
View File
@@ -0,0 +1,53 @@
#!/bin/bash
#
# run_all_tests.sh — simple runner for test_api.sh
#
API_SCRIPT="./test_api.sh"
# Make sure test_api.sh is executable
if [ ! -x "$API_SCRIPT" ]; then
echo "Error: $API_SCRIPT not found or not executable"
exit 1
fi
# List of tests to run (must match functions in test_api.sh)
TESTS=(
root_redirect
update_container
start_container
stop_container
nuke_container
info_all
info_one
bump_container
container_quota
system_containers
system_images
system_info
system_pull
)
ok_count=0
fail_count=0
echo "Running API tests..."
echo "===================="
for test in "${TESTS[@]}"; do
# Run test, capture HTTP status code only
status=$($API_SCRIPT $test -s -o /dev/null -w "%{http_code}")
if [[ "$status" == "200" || "$status" == "302" ]]; then
echo "[OK ] $test"
((ok_count++))
else
echo "[NOK] $test (HTTP $status)"
((fail_count++))
fi
done
echo "===================="
echo "Summary: $ok_count OK, $fail_count NOK"
exit $fail_count
Executable
+152
View File
@@ -0,0 +1,152 @@
#!/bin/bash
#
# test_api.sh — cURL test suite for Container Management API
#
# ========= CONFIG =========
API_KEY="your-secret-api-key"
CONTAINER_ID="001-002-123e4567-e89b-12d3-a456-426614174000" # sample UUID
BASE_URL="https://dev.odoo4projects.com"
# ==========================
# --- Functions for each endpoint ---
root_redirect() {
curl -i "$BASE_URL/"
}
update_container() {
curl -k -X POST "$BASE_URL/container/update" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"UUID": "'"$CONTAINER_ID"'",
"email": "user@example.com",
"expires": "2025-12-31",
"status": "active",
"created": "2025-08-16",
"tags": "N8N, SQLITE",
"affiliate": "OLIVER",
"domains": "N8N.local",
"status":"hot",
"env": {
"BRANCH": "release"
},
"secret": {
"psql": "5Sg9gDxYQH44QNSPyOx"
}
}'
}
start_container() {
curl -k -X POST "$BASE_URL/container/start" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
}
stop_container() {
curl -k -X POST "$BASE_URL/container/stop" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
}
nuke_container() {
curl -k -X POST "$BASE_URL/container/nuke" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
}
info_all() {
curl -k -X POST "$BASE_URL/container/info" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{}'
}
info_one() {
curl -k -X POST "$BASE_URL/container/info" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
}
bump_container() {
curl -k -X POST "$BASE_URL/container/bump" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
}
container_quota() {
curl -k -X POST "$BASE_URL/container/quota" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{ "container_id": "'"$CONTAINER_ID"'" }'
}
system_containers() {
curl -k -X GET "$BASE_URL/system/containers" \
-H "X-API-Key: $API_KEY"
}
system_images() {
curl -k -X GET "$BASE_URL/system/images" \
-H "X-API-Key: $API_KEY"
}
system_info() {
curl -k -X GET "$BASE_URL/system/info" \
-H "X-API-Key: $API_KEY"
}
system_pull() {
curl -k -X POST "$BASE_URL/system/pull" \
-H "X-API-Key: $API_KEY"
}
# --- Help message ---
show_help() {
cat <<EOF
Usage: $0 <command>
Available commands:
root_redirect - Test root redirect
update_container - Create or update container
start_container - Start container
stop_container - Stop container
nuke_container - Nuke container (status must be "nuke")
info_all - Get info for all containers
info_one - Get info for one container
bump_container - Bump container
container_quota - Show container quota
system_containers - List running containers
system_images - List docker images
system_info - Show system information
system_pull - Pull all container images
Examples:
$0 update_container
$0 system_info
EOF
}
# --- Main ---
if [ $# -eq 0 ] || [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
show_help
exit 0
fi
cmd="$1"
shift
if declare -f "$cmd" >/dev/null 2>&1; then
"$cmd" "$@"
else
echo "Unknown command: $cmd"
show_help
exit 1
fi
+3
View File
@@ -0,0 +1,3 @@
create user
set bash as default user
Executable
+96
View File
@@ -0,0 +1,96 @@
#!/bin/bash
### SYSTEM SETUP
rex doas mkdir -p /4server
rex doas chmod 777 /4server
rex doas chown 4server:4server /4server
rex mkdir -p /4server/data/log
template templates/hosts /etc/hosts
### BACKUP DIR
rex doas mkdir -p /BACKUP
rex doas chmod 777 /BACKUP
rex doas chown 4server:4server /BACKUP
### TMP DIR
rex doas mkdir -p /4server/tmp
rex doas chmod 777 /4server/tmp
rex doas chown 4server:4server /4server/tmp
template templates/.profile /home/4server/.profile
### PACKAGES
template templates/repositories /etc/apk/repositories
rex "doas apk update && doas apk upgrade"
rex doas apk add sshfs borgbackup iperf linux-lts openssh ufw python3 build-base python3-dev linux-headers py3-pip gcc g++ musl-dev libffi-dev make jq rsync mc vim docker docker-compose htop linux-lts sqlite bash postgresql16-client
rex doas pip install --root-user-action ignore --break-system-packages --no-cache-dir "uvicorn[standard]" fastapi pydantic psutil gdown
### own bins
echo "Running prsync ./sbin"
prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/
### POSTGRESS
rex mkdir -p /4server/data/postgres/etc
template templates/pq_hba.conf /4server/data/postgres/etc/
### API
#INSTALL API KEYS
template templates/4server /etc/4server
rex doas chown root:root /etc/4server
rex doas chmod 600 /etc/4server
#INSTALL API SERVICE
template templates/init.d/api /etc/init.d/api
rex doas chmod 0755 /etc/init.d/api
rex doas chown root:root /etc/init.d/api
rex doas rc-update add api default
rex doas rc-service api restart
#INSTALL checkCalls SERVICE
template templates/init.d/checkCalls /etc/init.d/checkCalls
rex doas chmod 0755 /etc/init.d/checkCalls
rex doas chown root:root /etc/init.d/checkCalls
rex doas rc-update add checkCalls default
rex doas rc-service checkCalls restart
#INSTALL cleanTmp SERVICE
template templates/init.d/cleanTmp /etc/init.d/cleanTmp
rex doas chmod 0755 /etc/init.d/cleanTmp
rex doas chown root:root /etc/init.d/cleanTmp
rex doas rc-update add cleanTmp default
rex doas rc-service cleanTmp restart
#INSTALL cpu service
template templates/init.d/cpu /etc/init.d/cpu
rex doas chmod 0755 /etc/init.d/cpu
rex doas chown root:root /etc/init.d/cpu
rex doas rc-update add cpu default
rex doas rc-service cpu restart
### Infrastructure
##### Docker
rex doas rc-service docker start
rex doas rc-update add docker default
rex doas rc-update del docker boot
#LOGIN ODOO4PROJECTS DOCKER REPO
rex "echo 'Airbus12docker' | doas docker login docker.odoo4projects.com -u admin --password-stdin"
rex mkdir -p /4server/data/traefik/etc
template templates/traefik.yaml /4server/data/traefik/etc/traefik.yaml
rex mkdir -p /4server/data/traefik/etc/certs
echo "prsync traefik certs"
prsync -h "/app/host_vars/hosts" -avz ./etc/traefik/certs/* /4server/data/traefik/etc/certs/
template templates/docker-compose.yml /4server/docker-compose.yml
rex doas docker-compose -f /4server/docker-compose.yml up -d --force-recreate
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
echo "Running prsync ./sbin"
prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/
template templates/.profile /home/4server/.profile
rex doas rc-service api restart
rex doas rc-service checkCalls restart
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 chmod 600 /home/4server/.ssh/borg*
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
set -euo pipefail
MAPPER_NAME="host_vars_crypt"
MOUNT_POINT="/app/host_vars"
# Unmount if mounted
if mountpoint -q "$MOUNT_POINT"; then
echo "Unmounting $MOUNT_POINT..."
umount "$MOUNT_POINT"
else
echo "$MOUNT_POINT is not mounted."
fi
if cryptsetup status "$MAPPER_NAME" >/dev/null 2>&1; then
echo "Closing stale mapping $MAPPER_NAME..."
if ! cryptsetup close "$MAPPER_NAME"; then
echo "cryptsetup close failed, forcing dmsetup remove..."
dmsetup remove --force --retry "$MAPPER_NAME" || true
fi
fi
# Close the LUKS/dm-crypt device if open
if [ -e "/dev/mapper/$MAPPER_NAME" ]; then
echo "Closing /dev/mapper/$MAPPER_NAME..."
cryptsetup close "$MAPPER_NAME"
else
echo "Mapper $MAPPER_NAME is not active."
fi
echo "Vault is now closed."

Some files were not shown because too many files have changed in this diff Show More