init
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACAOKT5um7fzviD27odH4GTW5oos0xAeP3DW7OhTqbEyAgAAAJhBIAtnQSAL
|
||||
ZwAAAAtzc2gtZWQyNTUxOQAAACAOKT5um7fzviD27odH4GTW5oos0xAeP3DW7OhTqbEyAg
|
||||
AAAEAk4EqWEVc34Dht1e/vD0ajtgSCo6xq30YPtvnrNuL3PQ4pPm6bt/O+IPbuh0fgZNbm
|
||||
iizTEB4/cNbs6FOpsTICAAAADm9saXZlckBtYXRlLTE2AQIDBAUGBw==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA4pPm6bt/O+IPbuh0fgZNbmiizTEB4/cNbs6FOpsTIC oliver@mate-16
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACD64E32udubuP0PEhxgoxR35jgHFUzlSRONXfGgsxNPswAAAJhgli31YJYt
|
||||
9QAAAAtzc2gtZWQyNTUxOQAAACD64E32udubuP0PEhxgoxR35jgHFUzlSRONXfGgsxNPsw
|
||||
AAAEC+wOOEwsAkPIhD02f+Yw5MYaCZZ5hhU9ZG62CcdH4+XvrgTfa525u4/Q8SHGCjFHfm
|
||||
OAcVTOVJE41d8aCzE0+zAAAADm9saXZlckBtYXRlLTE2AQIDBAUGBw==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPrgTfa525u4/Q8SHGCjFHfmOAcVTOVJE41d8aCzE0+z oliver@mate-16
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACDEN4FtZTrBFBkDDy7hTptxeoyPvpHDbSKuJxEGWusaMgAAAJjCrJXhwqyV
|
||||
4QAAAAtzc2gtZWQyNTUxOQAAACDEN4FtZTrBFBkDDy7hTptxeoyPvpHDbSKuJxEGWusaMg
|
||||
AAAEAUwrtarcT6FifBEUGdm8F32i2lZ3rEeYyKQo5n0wRyxcQ3gW1lOsEUGQMPLuFOm3F6
|
||||
jI++kcNtIq4nEQZa6xoyAAAADm9saXZlckBtYXRlLTE2AQIDBAUGBw==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1,2 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQ3gW1lOsEUGQMPLuFOm3F6jI++kcNtIq4nEQZa6xoy ansible@mumbai
|
||||
|
||||
@@ -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-----
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMBe/ZoUfzHMtYHnO+0kjVKSAOcH84yGaQVBBr+/OGTy oliver@mate-16
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
aaa-bbb-UUID
|
||||
aaa = server
|
||||
001 = manchester
|
||||
002 = boston
|
||||
|
||||
bbb = image
|
||||
001 = n8n
|
||||
002 = ODOO_18
|
||||
003 = ODOO_19
|
||||
Executable
+15
@@ -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"
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
rsync -avz --progress -e ssh mumbai:/4server/sbin/* /app/sbin/
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
Executable
+6
@@ -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
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
-----BEGIN NEBULA CERTIFICATE-----
|
||||
Cj8KDU9ET080cHJvamVjdHMoqNOhvgYwqKTJogg6IDv7w4DxfOvLDJ6WgjE3V8MZ
|
||||
k1I6t5GjmBmnyd0Wf0UqQAESQAzBFnjUsemshOlFCJisKbXdqBR83/Fl5aS0xSQj
|
||||
ZcDIpmgPnslBHTo8oPJLWeuU0Qd9IHNfdQvam2j6YnzVQAE=
|
||||
-----END NEBULA CERTIFICATE-----
|
||||
Executable
+26
@@ -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"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBH5vr90RFgOJrP2Xjr5I5QBxlonCC7pce56JDJFboPXQAAAJh9gvJGfYLy
|
||||
RgAAAAtzc2gtZWQyNTUxOQAAACBH5vr90RFgOJrP2Xjr5I5QBxlonCC7pce56JDJFboPXQ
|
||||
AAAEBSerxP83/u4p/IobVSxko5ZXO+/PPczGW0kopTfLLAykfm+v3REWA4ms/ZeOvkjlAH
|
||||
GWicILulx7nokMkVug9dAAAAEXJvb3RAMmNjNzhkYzBhZDIwAQIDBA==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1,6 @@
|
||||
-----BEGIN NEBULA CERTIFICATE-----
|
||||
CmYKA2RldhIKk5KghQyA/v//DyIDYmVlKI+YrcUGMKekyaIIOiAXY9FKiA1V6ayD
|
||||
Vx9Ce9UK3YcCF93DNP68WPixdl9LZUognXOojuxdSXZ4IG4v3A8HJ/77YSYnV/il
|
||||
ywmZ6V2khEESQHUVytAPARrJ0KxKPolUot6cl+UNMo5HOMqg2kxiRZBIUTp5XIME
|
||||
WfrYcdjlS9af7I34439r6gs4bA2LDGaaMQs=
|
||||
-----END NEBULA CERTIFICATE-----
|
||||
@@ -0,0 +1,2 @@
|
||||
API_KEY=your-secret-api-key
|
||||
HOSTNAME="dev"
|
||||
@@ -0,0 +1,3 @@
|
||||
-----BEGIN NEBULA X25519 PRIVATE KEY-----
|
||||
96/m6SrUsGWzT6atNvnopzygGhIAaXbBCXT8KAvwKp8=
|
||||
-----END NEBULA X25519 PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEfm+v3REWA4ms/ZeOvkjlAHGWicILulx7nokMkVug9d root@2cc78dc0ad20
|
||||
@@ -0,0 +1 @@
|
||||
sydney
|
||||
Executable
+5
@@ -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
@@ -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
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
rex doas restart
|
||||
@@ -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"
|
||||
}
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
/4server/sbin/ODOO_19/listModules $1
|
||||
Executable
+47
@@ -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.")
|
||||
|
||||
Executable
+32
@@ -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>&-
|
||||
|
||||
Executable
+124
@@ -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
|
||||
Executable
+68
@@ -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"
|
||||
Executable
+22
@@ -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"
|
||||
Executable
+60
@@ -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
@@ -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)
|
||||
Executable
+591
@@ -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)
|
||||
Executable
+42
@@ -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
|
||||
Executable
+27
@@ -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"
|
||||
Executable
+41
@@ -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
|
||||
Executable
+8
@@ -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
|
||||
|
||||
Executable
+14
@@ -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
@@ -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
|
||||
|
||||
Executable
+61
@@ -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 "]"
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker exec $1 /gitPull
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker exec $1 /gitRollBack
|
||||
|
||||
Executable
+51
@@ -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
|
||||
}
|
||||
Executable
+87
@@ -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)
|
||||
}
|
||||
Executable
+16
@@ -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
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
+54
@@ -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)."
|
||||
Executable
+68
@@ -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."
|
||||
|
||||
Executable
+57
@@ -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
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
doas docker exec -it $1 bash -c 'PGPASSWORD="$PASSWORD" psql -h "$HOST" -U "$USER"'
|
||||
Executable
+14
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Executable
+23
@@ -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
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
doas docker exec -it $1 bash -c 'PGPASSWORD="$PASSWORD" psql -h "$HOST" -U "$USER"'
|
||||
Executable
+127
@@ -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
|
||||
Executable
+127
@@ -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
|
||||
Executable
+125
@@ -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
|
||||
Executable
+62
@@ -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"
|
||||
Executable
+65
@@ -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
|
||||
Executable
+6
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
API_KEY={{API_KEY}}
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
permit nopass 4server as root
|
||||
@@ -0,0 +1,10 @@
|
||||
[DEFAULT]
|
||||
bantime = 1h
|
||||
findtime = 30m
|
||||
maxretry = 1
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
filter = sshd
|
||||
logpath = /var/log/auth.log
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{HOSTNAME}}
|
||||
@@ -0,0 +1,2 @@
|
||||
127.0.0.1 {{hostname}} localhost
|
||||
10.5.0.200 beedb
|
||||
Executable
+30
@@ -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
|
||||
}
|
||||
|
||||
Executable
+30
@@ -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
|
||||
}
|
||||
|
||||
Executable
+17
@@ -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
|
||||
}
|
||||
|
||||
Executable
+17
@@ -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
|
||||
}
|
||||
|
||||
Executable
+12
@@ -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
|
||||
}
|
||||
|
||||
Executable
+17
@@ -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 $?
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{NEBULA_CA}}
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{NEBULA_CRT}}
|
||||
@@ -0,0 +1 @@
|
||||
{{NEBULA_KEY}}
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
https://dl-cdn.alpinelinux.org/alpine/v3.22/main
|
||||
https://dl-cdn.alpinelinux.org/alpine/v3.22/community
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{SSH_PRIVATE}}
|
||||
@@ -0,0 +1 @@
|
||||
{{SSH_PUBLIC}}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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" } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+53
@@ -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
@@ -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
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
create user
|
||||
set bash as default user
|
||||
|
||||
Executable
+96
@@ -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
|
||||
Executable
+13
@@ -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*
|
||||
Executable
+35
@@ -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
Reference in New Issue
Block a user