Compare commits
28 Commits
b14d2f4fc1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ce01dd64f7 | |||
| eb4d83e9e9 | |||
| 5c6dbe416a | |||
| 72243d8b18 | |||
| 11a1c2635f | |||
| 835165289b | |||
| 08b9c38d0f | |||
| eb8c770506 | |||
| 3c33bd86e5 | |||
| 04f665a20d | |||
| 1d2ddd85e1 | |||
| 0251f06861 | |||
| 4328750ac8 | |||
| 22e49faf46 | |||
| c94bac2db3 | |||
| 1f08c66c78 | |||
| ba5ad005a6 | |||
| 1b64bdc08d | |||
| 7e3f4be906 | |||
| d354d989e7 | |||
| 902c43ec89 | |||
| 717045baa6 | |||
| e672b1d2c6 | |||
| ad59e8f0f1 | |||
| 943d67045e | |||
| b525fdaeaf | |||
| 2549419ef1 | |||
| 73090ddaa1 |
@@ -1,2 +1,4 @@
|
|||||||
|
alpine.qcow2
|
||||||
tmp/
|
tmp/
|
||||||
exchange/
|
exchange/
|
||||||
|
backup/
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Build output
|
||||||
|
output/
|
||||||
|
data/
|
||||||
|
|
||||||
|
# Nebula certificates and keys (user-provided secrets)
|
||||||
|
nebula/*.crt
|
||||||
|
nebula/*.key
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
FROM alpine:3.22
|
||||||
|
|
||||||
|
# Base tools
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
bash \
|
||||||
|
coreutils \
|
||||||
|
util-linux \
|
||||||
|
tar \
|
||||||
|
gzip \
|
||||||
|
xz \
|
||||||
|
cpio \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
ca-certificates \
|
||||||
|
rsync \
|
||||||
|
file \
|
||||||
|
grep \
|
||||||
|
gawk \
|
||||||
|
sed \
|
||||||
|
vim
|
||||||
|
|
||||||
|
# Filesystem + disk tools (needed by chroot-setup.sh and for Alpine install)
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
dosfstools \
|
||||||
|
e2fsprogs \
|
||||||
|
xfsprogs \
|
||||||
|
btrfs-progs \
|
||||||
|
parted \
|
||||||
|
sfdisk \
|
||||||
|
blkid \
|
||||||
|
lsblk \
|
||||||
|
wipefs \
|
||||||
|
util-linux-misc \
|
||||||
|
findmnt
|
||||||
|
|
||||||
|
# ISO + boot tools
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
xorriso \
|
||||||
|
syslinux \
|
||||||
|
mtools \
|
||||||
|
squashfs-tools \
|
||||||
|
libarchive-tools \
|
||||||
|
busybox-static
|
||||||
|
|
||||||
|
# GRUB (BIOS + EFI) — available if needed for alternative builds
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
grub \
|
||||||
|
grub-bios \
|
||||||
|
grub-efi \
|
||||||
|
efibootmgr
|
||||||
|
|
||||||
|
WORKDIR /work
|
||||||
|
|
||||||
|
CMD ["/bin/bash"]
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
# Alpine Linux USB Rescue ISO
|
||||||
|
|
||||||
|
A bootable hybrid ISO that:
|
||||||
|
|
||||||
|
- Gets an IP via **DHCP on `eth0`**
|
||||||
|
- Automatically opens a **Nebula overlay tunnel**
|
||||||
|
- Runs **OpenSSH** (login: `root` / `alpine`)
|
||||||
|
- Carries all tools needed to **install Alpine onto the host machine** (`setup-alpine`, `setup-disk`, `parted`, …)
|
||||||
|
|
||||||
|
The ISO is a hybrid image — it can be written to a USB stick with `dd` *or* burned to a CD/DVD.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Docker + Docker Compose
|
||||||
|
- A Nebula certificate set for this node (see below)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Place Nebula certificates
|
||||||
|
|
||||||
|
Put the following files in `nebula/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
nebula/
|
||||||
|
nebula ← binary (already included)
|
||||||
|
config.yml ← already included, edit as needed
|
||||||
|
ca.crt ← your Nebula CA certificate ← YOU PROVIDE
|
||||||
|
host.crt ← this node's certificate ← YOU PROVIDE
|
||||||
|
host.key ← this node's private key ← YOU PROVIDE
|
||||||
|
```
|
||||||
|
|
||||||
|
`ca.crt`, `host.crt`, and `host.key` are gitignored.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Build the ISO
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run --rm alpine bash /scripts/create.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The ISO is written to `output/rescue-alpine.iso`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Write to USB
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dd if=output/rescue-alpine.iso of=/dev/sdX bs=4M status=progress && sync
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `/dev/sdX` with your USB device (check with `lsblk`). **All data on the device will be erased.**
|
||||||
|
|
||||||
|
Or burn to CD/DVD:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cdrecord dev=/dev/sr0 output/rescue-alpine.iso
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Boot and connect
|
||||||
|
|
||||||
|
1. Insert USB and boot the target machine from it.
|
||||||
|
2. The system comes up fully in RAM — no writes to the host disk.
|
||||||
|
3. On boot, `eth0` gets an IP via DHCP, then the Nebula tunnel comes up automatically.
|
||||||
|
4. SSH in via the Nebula overlay IP (see `nebula/config.yml`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@<nebula-ip>
|
||||||
|
# password: alpine
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Install Alpine onto the host machine
|
||||||
|
|
||||||
|
Once SSH'd in (or at the local console):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Interactive guided installer — partitions disk, installs Alpine
|
||||||
|
setup-alpine
|
||||||
|
|
||||||
|
# Or use setup-disk for more control
|
||||||
|
setup-disk -m sys /dev/sda
|
||||||
|
```
|
||||||
|
|
||||||
|
All required tools (`parted`, `e2fsprogs`, `dosfstools`, `btrfs-progs`, `alpine-conf`, …) are pre-installed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Default credentials
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|----------|----------|
|
||||||
|
| Username | `root` |
|
||||||
|
| Password | `alpine` |
|
||||||
|
|
||||||
|
Change this in `scripts/chroot-setup.sh` before building if needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
ISO (hybrid — bootable on USB or CD)
|
||||||
|
└── isolinux (BIOS boot)
|
||||||
|
├── vmlinuz-lts
|
||||||
|
└── initramfs.gz ← entire Alpine system packed as cpio+gzip
|
||||||
|
├── eth0 DHCP (openrc networking)
|
||||||
|
├── nebula → /usr/local/bin/nebula
|
||||||
|
├── /etc/nebula/ (config + certs)
|
||||||
|
├── OpenSSH (sshd)
|
||||||
|
└── Alpine install tools
|
||||||
|
```
|
||||||
|
|
||||||
|
The system runs **entirely in RAM** — the host disk is never touched until you explicitly run the installer.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
docker compose run --rm alpine bash /scripts/create.sh
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
alpine:
|
||||||
|
build: .
|
||||||
|
devices:
|
||||||
|
- /dev/loop-control:/dev/loop-control
|
||||||
|
privileged: true
|
||||||
|
volumes:
|
||||||
|
- ./data:/work
|
||||||
|
- ./scripts:/scripts:ro
|
||||||
|
- ./nebula:/nebula:ro
|
||||||
|
- ./output:/output
|
||||||
|
- /dev:/dev
|
||||||
|
working_dir: /work
|
||||||
|
tty: true
|
||||||
|
stdin_open: true
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
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:
|
||||||
|
- "192.168.9.1"
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Executable
BIN
Binary file not shown.
Executable
+135
@@ -0,0 +1,135 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# chroot-setup.sh — Runs inside the Alpine chroot to configure the system
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. APK repositories
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Configuring APK repositories"
|
||||||
|
cat > /etc/apk/repositories <<'EOF'
|
||||||
|
https://dl-cdn.alpinelinux.org/alpine/latest-stable/main
|
||||||
|
https://dl-cdn.alpinelinux.org/alpine/latest-stable/community
|
||||||
|
EOF
|
||||||
|
|
||||||
|
apk update
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Install packages
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Installing packages"
|
||||||
|
apk add --no-cache \
|
||||||
|
linux-lts \
|
||||||
|
mkinitfs \
|
||||||
|
openrc \
|
||||||
|
busybox \
|
||||||
|
musl \
|
||||||
|
libc-utils \
|
||||||
|
util-linux \
|
||||||
|
e2fsprogs \
|
||||||
|
dosfstools \
|
||||||
|
btrfs-progs \
|
||||||
|
parted \
|
||||||
|
sfdisk \
|
||||||
|
openssh \
|
||||||
|
dhcpcd \
|
||||||
|
iproute2 \
|
||||||
|
iputils \
|
||||||
|
net-tools \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
ca-certificates \
|
||||||
|
vim \
|
||||||
|
mc \
|
||||||
|
bind-tools \
|
||||||
|
nmap \
|
||||||
|
rsync \
|
||||||
|
alpine-conf
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. Generate initramfs
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Generating initramfs with mkinitfs"
|
||||||
|
mkinitfs $(ls /lib/modules/ | tail -1)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4. Network configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Configuring network interfaces"
|
||||||
|
mkdir -p /etc/network
|
||||||
|
cat > /etc/network/interfaces <<'EOF'
|
||||||
|
auto lo
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
|
auto eth0
|
||||||
|
iface eth0 inet dhcp
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 5. SSH configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Configuring sshd"
|
||||||
|
sed -i \
|
||||||
|
-e 's/^#*\s*PermitRootLogin.*/PermitRootLogin yes/' \
|
||||||
|
-e 's/^#*\s*PasswordAuthentication.*/PasswordAuthentication yes/' \
|
||||||
|
/etc/ssh/sshd_config
|
||||||
|
|
||||||
|
# Ensure the directives exist if sed found nothing to replace
|
||||||
|
grep -q '^PermitRootLogin' /etc/ssh/sshd_config || echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config
|
||||||
|
grep -q '^PasswordAuthentication' /etc/ssh/sshd_config || echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
echo "==> Generating SSH host keys"
|
||||||
|
ssh-keygen -A
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 6. Root password
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Setting root password"
|
||||||
|
echo "root:alpine" | chpasswd
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 7. Nebula OpenRC service
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Creating /etc/init.d/nebula"
|
||||||
|
cat > /etc/init.d/nebula <<'EOF'
|
||||||
|
#!/sbin/openrc-run
|
||||||
|
description="Nebula overlay network"
|
||||||
|
command="/usr/local/bin/nebula"
|
||||||
|
command_args="-config /etc/nebula/config.yml"
|
||||||
|
command_background=true
|
||||||
|
pidfile="/run/nebula.pid"
|
||||||
|
|
||||||
|
depend() {
|
||||||
|
need net
|
||||||
|
after networking
|
||||||
|
}
|
||||||
|
|
||||||
|
start_pre() {
|
||||||
|
checkpath -f -m 0600 -o root:root /run/nebula.pid
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
chmod +x /etc/init.d/nebula
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 8. OpenRC service enablement
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Enabling services"
|
||||||
|
rc-update add sshd default
|
||||||
|
rc-update add networking default
|
||||||
|
rc-update add nebula default
|
||||||
|
|
||||||
|
# Also ensure basic boot services are present
|
||||||
|
rc-update add devfs sysinit || true
|
||||||
|
rc-update add dmesg sysinit || true
|
||||||
|
rc-update add mdev sysinit || true
|
||||||
|
rc-update add hwdrivers sysinit || true
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 9. Hostname
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Setting hostname to 'rescue'"
|
||||||
|
echo "rescue" > /etc/hostname
|
||||||
|
|
||||||
|
echo "==> chroot-setup.sh complete"
|
||||||
Executable
+211
@@ -0,0 +1,211 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# create.sh — Build a bootable Alpine Linux rescue ISO
|
||||||
|
#
|
||||||
|
# Run inside the Docker build container:
|
||||||
|
# docker compose run --rm alpine bash /scripts/create.sh
|
||||||
|
#
|
||||||
|
# The resulting ISO is a hybrid image: it can be burned to a CD/DVD or
|
||||||
|
# written directly to a USB stick with dd.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ALPINE_MIRROR="https://dl-cdn.alpinelinux.org/alpine"
|
||||||
|
ALPINE_BRANCH="latest-stable"
|
||||||
|
ALPINE_ARCH="x86_64"
|
||||||
|
MINIROOTFS_BASE="${ALPINE_MIRROR}/${ALPINE_BRANCH}/releases/${ALPINE_ARCH}"
|
||||||
|
|
||||||
|
ISO="/output/rescue-alpine.iso"
|
||||||
|
CHROOT="/mnt/alpine-chroot"
|
||||||
|
ISOROOT="/tmp/isoroot"
|
||||||
|
|
||||||
|
mkdir -p /output "$CHROOT" "$ISOROOT"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. Fetch latest Alpine minirootfs
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Fetching latest Alpine minirootfs index"
|
||||||
|
TARBALL=$(curl -fsSL "${MINIROOTFS_BASE}/" \
|
||||||
|
| grep -oP 'alpine-minirootfs-[0-9.]+-x86_64\.tar\.gz' \
|
||||||
|
| sort -V | tail -1)
|
||||||
|
|
||||||
|
[[ -z "$TARBALL" ]] && { echo "ERROR: could not determine minirootfs tarball" >&2; exit 1; }
|
||||||
|
|
||||||
|
TARBALL_LOCAL="/tmp/${TARBALL}"
|
||||||
|
if [[ ! -f "$TARBALL_LOCAL" ]]; then
|
||||||
|
echo "==> Downloading ${MINIROOTFS_BASE}/${TARBALL}"
|
||||||
|
curl -fsSL -o "$TARBALL_LOCAL" "${MINIROOTFS_BASE}/${TARBALL}"
|
||||||
|
else
|
||||||
|
echo "==> Using cached ${TARBALL_LOCAL}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Extract minirootfs into chroot
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Extracting minirootfs into ${CHROOT}"
|
||||||
|
tar -xzf "$TARBALL_LOCAL" -C "$CHROOT"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. Copy Nebula binary and config into chroot
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Installing Nebula binary"
|
||||||
|
mkdir -p "${CHROOT}/usr/local/bin"
|
||||||
|
cp /nebula/nebula "${CHROOT}/usr/local/bin/nebula"
|
||||||
|
chmod +x "${CHROOT}/usr/local/bin/nebula"
|
||||||
|
|
||||||
|
echo "==> Installing Nebula config and certificates"
|
||||||
|
mkdir -p "${CHROOT}/etc/nebula"
|
||||||
|
cp /nebula/config.yml "${CHROOT}/etc/nebula/config.yml"
|
||||||
|
for f in /nebula/*.crt /nebula/*.key; do
|
||||||
|
[[ -f "$f" ]] && cp "$f" "${CHROOT}/etc/nebula/"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4. Bind-mount virtual filesystems for chroot
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Bind-mounting virtual filesystems"
|
||||||
|
mount --bind /dev "${CHROOT}/dev"
|
||||||
|
mount --bind /proc "${CHROOT}/proc"
|
||||||
|
mount --bind /sys "${CHROOT}/sys"
|
||||||
|
mkdir -p "${CHROOT}/run"
|
||||||
|
mount --bind /run "${CHROOT}/run"
|
||||||
|
cp /etc/resolv.conf "${CHROOT}/etc/resolv.conf"
|
||||||
|
|
||||||
|
# Make scripts available inside chroot
|
||||||
|
mkdir -p "${CHROOT}/scripts"
|
||||||
|
mount --bind /scripts "${CHROOT}/scripts"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 5. Run in-chroot configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Running chroot-setup.sh inside chroot"
|
||||||
|
chroot "$CHROOT" /bin/sh /scripts/chroot-setup.sh
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 6. Unmount virtual filesystems
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Unmounting virtual filesystems"
|
||||||
|
umount "${CHROOT}/scripts" || true
|
||||||
|
umount "${CHROOT}/run" || true
|
||||||
|
umount "${CHROOT}/sys" || true
|
||||||
|
umount "${CHROOT}/proc" || true
|
||||||
|
umount "${CHROOT}/dev" || true
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 7. Pack the full system as a squashfs (xz compressed)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Creating squashfs from chroot (xz — this may take a while)"
|
||||||
|
mksquashfs "$CHROOT" "${ISOROOT}/rootfs.squashfs" \
|
||||||
|
-comp xz -Xdict-size 100% -noappend \
|
||||||
|
-e boot/initramfs-lts 2>/dev/null || \
|
||||||
|
mksquashfs "$CHROOT" "${ISOROOT}/rootfs.squashfs" \
|
||||||
|
-comp xz -noappend
|
||||||
|
echo " squashfs size: $(du -sh "${ISOROOT}/rootfs.squashfs" | cut -f1)"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 8. Copy kernel into ISO root
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Copying kernel"
|
||||||
|
cp "${CHROOT}/boot/vmlinuz-lts" "${ISOROOT}/vmlinuz"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 9. Build tiny initramfs: busybox + init script that mounts the squashfs
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Building tiny initramfs"
|
||||||
|
INITRD="/tmp/initrd"
|
||||||
|
rm -rf "$INITRD"
|
||||||
|
mkdir -p "${INITRD}"/{bin,dev,proc,sys,mnt/boot,mnt/squash,mnt/upper,mnt/work,mnt/newroot}
|
||||||
|
|
||||||
|
# Static busybox — no external library deps
|
||||||
|
BBOX=$(find /bin /usr/bin /usr/sbin -name 'busybox.static' -o -name 'busybox' 2>/dev/null \
|
||||||
|
| xargs -r file 2>/dev/null | grep -i 'statically linked' | cut -d: -f1 | head -1)
|
||||||
|
[[ -z "$BBOX" ]] && { echo "ERROR: cannot find a statically linked busybox" >&2; exit 1; }
|
||||||
|
echo " using static busybox: $BBOX"
|
||||||
|
cp "$BBOX" "${INITRD}/bin/busybox"
|
||||||
|
chmod +x "${INITRD}/bin/busybox"
|
||||||
|
for cmd in sh mount umount mkdir mknod switch_root mdev sleep; do
|
||||||
|
ln -sf busybox "${INITRD}/bin/${cmd}"
|
||||||
|
done
|
||||||
|
|
||||||
|
cat > "${INITRD}/init" <<'INIT_EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
mount -t proc none /proc
|
||||||
|
mount -t sysfs none /sys
|
||||||
|
mount -t devtmpfs none /dev 2>/dev/null || mdev -s
|
||||||
|
|
||||||
|
# Scan block devices for the ISO containing rootfs.squashfs
|
||||||
|
found=0
|
||||||
|
for try in 1 2 3 4 5; do
|
||||||
|
for dev in /dev/sr0 /dev/sda /dev/sda1 /dev/sdb /dev/sdb1 /dev/sdc /dev/sdc1; do
|
||||||
|
[ -b "$dev" ] || continue
|
||||||
|
mount -t iso9660 -o ro "$dev" /mnt/boot 2>/dev/null || \
|
||||||
|
mount -o ro "$dev" /mnt/boot 2>/dev/null || continue
|
||||||
|
if [ -f /mnt/boot/rootfs.squashfs ]; then
|
||||||
|
found=1
|
||||||
|
break 2
|
||||||
|
fi
|
||||||
|
umount /mnt/boot 2>/dev/null
|
||||||
|
done
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$found" != "1" ]; then
|
||||||
|
echo "ERROR: rootfs.squashfs not found — dropping to shell"
|
||||||
|
exec sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
mount -t squashfs -o ro /mnt/boot/rootfs.squashfs /mnt/squash
|
||||||
|
|
||||||
|
mount -t tmpfs tmpfs /mnt/upper
|
||||||
|
mkdir -p /mnt/upper/rw /mnt/upper/work
|
||||||
|
mount -t overlay overlay \
|
||||||
|
-o lowerdir=/mnt/squash,upperdir=/mnt/upper/rw,workdir=/mnt/upper/work \
|
||||||
|
/mnt/newroot
|
||||||
|
|
||||||
|
exec switch_root /mnt/newroot /sbin/init
|
||||||
|
INIT_EOF
|
||||||
|
chmod +x "${INITRD}/init"
|
||||||
|
|
||||||
|
( cd "$INITRD" && find . | cpio --quiet -oH newc | gzip -9 ) > "${ISOROOT}/initramfs.gz"
|
||||||
|
echo " initramfs size: $(du -sh "${ISOROOT}/initramfs.gz" | cut -f1)"
|
||||||
|
echo " initramfs contents:"
|
||||||
|
zcat "${ISOROOT}/initramfs.gz" | cpio -t 2>/dev/null
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 10. Build ISO with grub-mkrescue (BIOS + UEFI hybrid)
|
||||||
|
# grub-mkrescue uses its own file loader — no BIOS INT 13h size limits.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "==> Writing GRUB config"
|
||||||
|
mkdir -p "${ISOROOT}/boot/grub"
|
||||||
|
cat > "${ISOROOT}/boot/grub/grub.cfg" <<'EOF'
|
||||||
|
set timeout=5
|
||||||
|
set default=0
|
||||||
|
|
||||||
|
menuentry "Alpine Linux USB Rescue" {
|
||||||
|
linux /vmlinuz quiet init=/sbin/init
|
||||||
|
initrd /initramfs.gz
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "==> Building hybrid ISO with grub-mkrescue: ${ISO}"
|
||||||
|
grub-mkrescue -o "$ISO" "$ISOROOT" -- -V ALPINE_RESCUE
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Done
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
ISO_SIZE=$(du -sh "$ISO" | cut -f1)
|
||||||
|
echo ""
|
||||||
|
echo "================================================================"
|
||||||
|
echo " Build complete!"
|
||||||
|
echo " ISO : ${ISO} (${ISO_SIZE})"
|
||||||
|
echo ""
|
||||||
|
echo " Burn to USB:"
|
||||||
|
echo " dd if=${ISO} of=/dev/sdX bs=4M status=progress && sync"
|
||||||
|
echo ""
|
||||||
|
echo " Burn to CD/DVD:"
|
||||||
|
echo " cdrecord dev=/dev/sr0 ${ISO}"
|
||||||
|
echo ""
|
||||||
|
echo " Default login : root / alpine"
|
||||||
|
echo " Nebula tunnel : auto-starts on boot (needs certs in nebula/)"
|
||||||
|
echo "================================================================"
|
||||||
+3
-1
@@ -15,7 +15,9 @@ RUN apk add --no-cache \
|
|||||||
e2fsprogs \
|
e2fsprogs \
|
||||||
screen \
|
screen \
|
||||||
rsync \
|
rsync \
|
||||||
device-mapper
|
sshfs \
|
||||||
|
device-mapper \
|
||||||
|
borgbackup
|
||||||
|
|
||||||
|
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ Host meppel
|
|||||||
User 4server
|
User 4server
|
||||||
IdentityFile /app/host_vars/meppel/meppel
|
IdentityFile /app/host_vars/meppel/meppel
|
||||||
|
|
||||||
|
Host sydney
|
||||||
|
Hostname 192.168.9.22
|
||||||
|
User 4server
|
||||||
|
IdentityFile /app/host_vars/sydney/sydney
|
||||||
|
|
||||||
Host saopaulo
|
Host saopaulo
|
||||||
Hostname 192.168.9.11
|
Hostname 192.168.9.11
|
||||||
User 4server
|
User 4server
|
||||||
|
|||||||
@@ -13,4 +13,5 @@
|
|||||||
192.168.9.11 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDc6J024++J25z2ZMXQ3Vim+mLVIVs+cZngv8DmtEDfrT/Ptl7+F2IqXRNcZuq1YTjEZO8eQg27iIemPoPT3xa8=
|
192.168.9.11 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDc6J024++J25z2ZMXQ3Vim+mLVIVs+cZngv8DmtEDfrT/Ptl7+F2IqXRNcZuq1YTjEZO8eQg27iIemPoPT3xa8=
|
||||||
192.168.9.21 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMFuf87ngDaF8D0kzVzzB953ji6ptg/V9t+WPac+DAjO
|
192.168.9.21 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMFuf87ngDaF8D0kzVzzB953ji6ptg/V9t+WPac+DAjO
|
||||||
192.168.9.21 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtexcutExMflSRPJOwGPuZx49c+ZUMoElNPNntXONOuVZ8jM+zzA/4s+HCj+RWdHMnarLcNJjwRZRq7JWruqmr1EN9eNmvu13cLCKZr6Ape1gt8VmMhvQ9nA7eMx3UdvnWgXjLCfJy+BKh2XVAneM5lOSPMcyQlcACrLQVkse9EKhxhV5nEjXKYxSNetIPK9PxglXznpC8IpuWlXnDH2R8vDhdvBmBFTKOjN5Wa+GrMfElWeQOjyCpNcMVYohvV87VN6FxHQTADFTm2CKy/W7pnM8rI55ZbBIMfvLyhpM+vtFh1sJlIZSFnn+ytMUImNEAgBDI3fCpwZ99zeLViGhQJlBkdCUI+JJK9XJjpcOMNydeWh142RGU6juPuSLOZPqKNc3BpZYgPY0vMDgDWgT9AcYup3rAJ/UnUnhsxiT2h/gx7+t/j9xg01BQF5S6mezaueuavHCh9MvAzXiJXR8zanhl/Hh9BKgIAFrZh71CzP/PYG33BKCe9DXA09aJrf8=
|
192.168.9.21 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtexcutExMflSRPJOwGPuZx49c+ZUMoElNPNntXONOuVZ8jM+zzA/4s+HCj+RWdHMnarLcNJjwRZRq7JWruqmr1EN9eNmvu13cLCKZr6Ape1gt8VmMhvQ9nA7eMx3UdvnWgXjLCfJy+BKh2XVAneM5lOSPMcyQlcACrLQVkse9EKhxhV5nEjXKYxSNetIPK9PxglXznpC8IpuWlXnDH2R8vDhdvBmBFTKOjN5Wa+GrMfElWeQOjyCpNcMVYohvV87VN6FxHQTADFTm2CKy/W7pnM8rI55ZbBIMfvLyhpM+vtFh1sJlIZSFnn+ytMUImNEAgBDI3fCpwZ99zeLViGhQJlBkdCUI+JJK9XJjpcOMNydeWh142RGU6juPuSLOZPqKNc3BpZYgPY0vMDgDWgT9AcYup3rAJ/UnUnhsxiT2h/gx7+t/j9xg01BQF5S6mezaueuavHCh9MvAzXiJXR8zanhl/Hh9BKgIAFrZh71CzP/PYG33BKCe9DXA09aJrf8=
|
||||||
|
192.168.9.22 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMO5VhveQe76kEOdVqrdoTwW1fNzmgDzZNBznFEC4ohtXes0VMtJR02lhc+55XTIUX+EDxBtAh8U+kJpFeLetMU=
|
||||||
192.168.9.21 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGlzLZWNjSaDuDd4e66LoBizY5j+QCsifIIxkvX4CrzP/AqAgWDEEgT+pAXBFkNJlBR6TDFJ0bIdwZTbcq8/72E=
|
192.168.9.21 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGlzLZWNjSaDuDd4e66LoBizY5j+QCsifIIxkvX4CrzP/AqAgWDEEgT+pAXBFkNJlBR6TDFJ0bIdwZTbcq8/72E=
|
||||||
|
|||||||
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"
|
||||||
Regular → Executable
@@ -13,7 +13,7 @@ source /4server/sbin/helpers
|
|||||||
get_contract_info
|
get_contract_info
|
||||||
|
|
||||||
|
|
||||||
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
||||||
|
|
||||||
echo "PASSWORD $ODOO_DB_PASSWORD"
|
echo "PASSWORD $ODOO_DB_PASSWORD"
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,10 @@ source /4server/sbin/helpers
|
|||||||
get_contract_info
|
get_contract_info
|
||||||
|
|
||||||
|
|
||||||
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
||||||
|
|
||||||
echo "PASSWORD $ODOO_DB_PASSWORD"
|
echo "PASSWORD $ODOO_DB_PASSWORD"
|
||||||
|
|
||||||
echo "POSTGRES HOST: $POSTGRES_HOST"
|
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"
|
doas docker exec -it "$UUID" odoo shell --db_host beedb --db_password="$ODOO_DB_PASSWORD" -d "$UUID" --db_user="$UUID"
|
||||||
|
|
||||||
|
|||||||
+176
-69
@@ -1,30 +1,27 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from fastapi import FastAPI, HTTPException, Depends, Response
|
import json
|
||||||
from fastapi.security.api_key import APIKeyHeader
|
import os
|
||||||
from fastapi.responses import RedirectResponse, PlainTextResponse
|
import re
|
||||||
from pydantic import BaseModel
|
|
||||||
import psutil
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
|
||||||
import uvicorn
|
|
||||||
from typing import Dict, Any, Optional
|
|
||||||
from datetime import datetime
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
from collections import deque
|
|
||||||
import time
|
import time
|
||||||
|
from collections import deque
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
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 ----------------------
|
# ---------------------- Constants ----------------------
|
||||||
DB_PATH = "/4server/data/contracts.db"
|
DB_PATH = "/4server/data/contracts.db"
|
||||||
BIN_PATH = "/4server/sbin"
|
BIN_PATH = "/4server/sbin"
|
||||||
API_KEY = os.getenv("API_KEY", "your-secret-api-key")
|
API_KEY = os.getenv("API_KEY", "your-secret-api-key")
|
||||||
VERSION = "API: 0.0.8"
|
VERSION = "API: 0.0.9"
|
||||||
|
|
||||||
# ---------------------- FastAPI App ----------------------
|
# ---------------------- FastAPI App ----------------------
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
@@ -57,7 +54,7 @@ def init_db():
|
|||||||
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
||||||
conn = sqlite3.connect(DB_PATH)
|
conn = sqlite3.connect(DB_PATH)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('''
|
cursor.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS containers (
|
CREATE TABLE IF NOT EXISTS containers (
|
||||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
UUID CHAR(50) UNIQUE,
|
UUID CHAR(50) UNIQUE,
|
||||||
@@ -74,9 +71,19 @@ def init_db():
|
|||||||
created DATE,
|
created DATE,
|
||||||
bump DATE,
|
bump DATE,
|
||||||
secret TEXT,
|
secret TEXT,
|
||||||
contract 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.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -101,13 +108,12 @@ class ContractItem(BaseModel):
|
|||||||
product_id: int
|
product_id: int
|
||||||
features: Dict[str, Any]
|
features: Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
class ContainerModel(BaseModel):
|
class ContainerModel(BaseModel):
|
||||||
UUID: str
|
UUID: str
|
||||||
email: Optional[str] = None
|
email: Optional[str] = None
|
||||||
expires: Optional[str] = None
|
expires: Optional[str] = None
|
||||||
tags: Optional[str] = None
|
tags: Optional[str] = None
|
||||||
env: Optional[Dict[str, Any]] = None
|
|
||||||
affiliate: Optional[str] = None
|
|
||||||
image: Optional[str] = None
|
image: Optional[str] = None
|
||||||
history: Optional[str] = None
|
history: Optional[str] = None
|
||||||
comment: Optional[str] = None
|
comment: Optional[str] = None
|
||||||
@@ -115,37 +121,57 @@ class ContainerModel(BaseModel):
|
|||||||
status: Optional[str] = None
|
status: Optional[str] = None
|
||||||
created: Optional[str] = None
|
created: Optional[str] = None
|
||||||
bump: Optional[str] = None
|
bump: Optional[str] = None
|
||||||
secret: Optional[Dict[str, Any]] = None
|
git: Optional[int] = None
|
||||||
contract: Optional[str] = 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):
|
class UUIDRequest(BaseModel):
|
||||||
UUID: str
|
UUID: str
|
||||||
|
|
||||||
|
|
||||||
class CommandRequest(BaseModel):
|
class CommandRequest(BaseModel):
|
||||||
uuid: str
|
uuid: str
|
||||||
method: int
|
method: int
|
||||||
|
|
||||||
|
|
||||||
class ImportRequest(BaseModel):
|
class ImportRequest(BaseModel):
|
||||||
filename: str
|
filename: str
|
||||||
|
|
||||||
|
|
||||||
class MoveRequest(BaseModel):
|
class MoveRequest(BaseModel):
|
||||||
source: str
|
source: str
|
||||||
destination: str
|
destination: str
|
||||||
|
|
||||||
|
|
||||||
class AddKeyRequest(BaseModel):
|
class AddKeyRequest(BaseModel):
|
||||||
key: str
|
key: str
|
||||||
uuid: str
|
uuid: str
|
||||||
|
|
||||||
|
|
||||||
|
class ImageRequest(BaseModel):
|
||||||
|
image: str
|
||||||
|
|
||||||
|
|
||||||
# ---------------------- Routes ----------------------
|
# ---------------------- Routes ----------------------
|
||||||
@app.get("/", include_in_schema=False)
|
@app.get("/", include_in_schema=False)
|
||||||
def redirect_to_odoo():
|
def redirect_to_odoo():
|
||||||
return RedirectResponse(url="https://ODOO4PROJECTS.com")
|
return RedirectResponse(url="https://ODOO4PROJECTS.com")
|
||||||
|
|
||||||
from fastapi import FastAPI, Depends
|
|
||||||
|
import json
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from fastapi import Depends, FastAPI
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import Optional, Dict, Any
|
|
||||||
import json
|
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
@@ -158,25 +184,43 @@ def redirect_to_odoo():
|
|||||||
|
|
||||||
@app.post("/container/update", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/update", dependencies=[Depends(verify_api_key)])
|
||||||
def update_container(request: ContainerModel):
|
def update_container(request: ContainerModel):
|
||||||
# Convert dict fields to JSON strings
|
|
||||||
env_str = json.dumps(request.env) if isinstance(request.env, dict) else None
|
|
||||||
secret_str = json.dumps(request.secret) if isinstance(request.secret, dict) else None
|
|
||||||
contract_str = json.dumps(request.contract) if isinstance(request.contract, dict) else None
|
|
||||||
|
|
||||||
|
|
||||||
# Fetch existing record
|
# Fetch existing record
|
||||||
existing = execute_db("SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True)
|
existing = execute_db(
|
||||||
|
"SELECT * FROM containers WHERE UUID = ?", (request.UUID,), fetch=True
|
||||||
|
)
|
||||||
if not existing:
|
if not existing:
|
||||||
# If record does not exist, insert a new one with all given fields
|
# If record does not exist, insert a new one with all given fields
|
||||||
execute_db("""
|
execute_db(
|
||||||
INSERT INTO containers (UUID, email, expires, tags, env, affiliate, image, history,
|
"""
|
||||||
comment, domains, status, created, bump, secret, contract)
|
INSERT INTO containers (UUID, email, expires, tags, image, history,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
comment, domains, status, created, bump, git, backup_slots, hdd, workers,
|
||||||
""", (
|
utm_source, utm_medium, utm_campaign, domains_slots, secret_list, env_list)
|
||||||
request.UUID, request.email, request.expires, request.tags, env_str,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
request.affiliate, request.image, request.history, request.comment,
|
""",
|
||||||
request.domains, request.status, request.created, request.bump, secret_str, contract_str
|
(
|
||||||
))
|
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"}
|
return {"UUID": request.UUID, "status": "created"}
|
||||||
|
|
||||||
# Existing record found, do partial update
|
# Existing record found, do partial update
|
||||||
@@ -190,9 +234,6 @@ def update_container(request: ContainerModel):
|
|||||||
continue
|
continue
|
||||||
value = getattr(request, field)
|
value = getattr(request, field)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
if field in ["env", "secret", "contract"]:
|
|
||||||
value = json.dumps(value)
|
|
||||||
|
|
||||||
updates[field] = value
|
updates[field] = value
|
||||||
params.append(value)
|
params.append(value)
|
||||||
|
|
||||||
@@ -201,7 +242,11 @@ def update_container(request: ContainerModel):
|
|||||||
set_clause = ", ".join(f"{k}=?" for k in updates.keys())
|
set_clause = ", ".join(f"{k}=?" for k in updates.keys())
|
||||||
params.append(request.UUID) # UUID for WHERE clause
|
params.append(request.UUID) # UUID for WHERE clause
|
||||||
execute_db(f"UPDATE containers SET {set_clause} WHERE UUID=?", tuple(params))
|
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": "updated",
|
||||||
|
"fields_updated": list(updates.keys()),
|
||||||
|
}
|
||||||
|
|
||||||
return {"UUID": request.UUID, "status": "no_change"}
|
return {"UUID": request.UUID, "status": "no_change"}
|
||||||
|
|
||||||
@@ -211,6 +256,11 @@ def start_container(request: UUIDRequest):
|
|||||||
return {"message": run_command([f"{BIN_PATH}/startContainer", request.UUID])}
|
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)])
|
@app.post("/container/stop", dependencies=[Depends(verify_api_key)])
|
||||||
def stop_container(request: UUIDRequest):
|
def stop_container(request: UUIDRequest):
|
||||||
try:
|
try:
|
||||||
@@ -222,7 +272,9 @@ def stop_container(request: UUIDRequest):
|
|||||||
|
|
||||||
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/nuke", dependencies=[Depends(verify_api_key)])
|
||||||
def nuke_container(request: UUIDRequest):
|
def nuke_container(request: UUIDRequest):
|
||||||
status = execute_db("SELECT status FROM containers WHERE UUID=?", (request.UUID,), fetch=True)
|
status = execute_db(
|
||||||
|
"SELECT status FROM containers WHERE UUID=?", (request.UUID,), fetch=True
|
||||||
|
)
|
||||||
if not status or status[0]["status"] != "nuke":
|
if not status or status[0]["status"] != "nuke":
|
||||||
raise HTTPException(400, "Container status is not 'nuke'")
|
raise HTTPException(400, "Container status is not 'nuke'")
|
||||||
return {"message": run_command([f"{BIN_PATH}/nukeContainer", request.UUID])}
|
return {"message": run_command([f"{BIN_PATH}/nukeContainer", request.UUID])}
|
||||||
@@ -232,8 +284,26 @@ def nuke_container(request: UUIDRequest):
|
|||||||
def info_container(request: Optional[UUIDRequest] = None):
|
def info_container(request: Optional[UUIDRequest] = None):
|
||||||
# Fields to select
|
# Fields to select
|
||||||
fields = [
|
fields = [
|
||||||
"ID", "UUID", "email", "expires", "tags", "env", "affiliate",
|
"ID",
|
||||||
"image", "history", "comment", "domains", "status", "created", "contract"
|
"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)
|
field_str = ", ".join(fields)
|
||||||
|
|
||||||
@@ -242,13 +312,10 @@ def info_container(request: Optional[UUIDRequest] = None):
|
|||||||
rows = execute_db(
|
rows = execute_db(
|
||||||
f"SELECT {field_str} FROM containers WHERE UUID=?",
|
f"SELECT {field_str} FROM containers WHERE UUID=?",
|
||||||
(str(request.UUID),),
|
(str(request.UUID),),
|
||||||
fetch=True
|
fetch=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
rows = execute_db(
|
rows = execute_db(f"SELECT {field_str} FROM containers", fetch=True)
|
||||||
f"SELECT {field_str} FROM containers",
|
|
||||||
fetch=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Map rows to dicts safely
|
# Map rows to dicts safely
|
||||||
containers = []
|
containers = []
|
||||||
@@ -265,6 +332,7 @@ def info_container(request: Optional[UUIDRequest] = None):
|
|||||||
|
|
||||||
return n8n_items
|
return n8n_items
|
||||||
|
|
||||||
|
|
||||||
@app.post("/container/bump", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/bump", dependencies=[Depends(verify_api_key)])
|
||||||
def bump_container(request: UUIDRequest):
|
def bump_container(request: UUIDRequest):
|
||||||
today = datetime.utcnow().strftime("%Y-%m-%d")
|
today = datetime.utcnow().strftime("%Y-%m-%d")
|
||||||
@@ -275,10 +343,16 @@ def bump_container(request: UUIDRequest):
|
|||||||
|
|
||||||
@app.post("/container/quota", dependencies=[Depends(verify_api_key)])
|
@app.post("/container/quota", dependencies=[Depends(verify_api_key)])
|
||||||
def container_quota(request: UUIDRequest):
|
def container_quota(request: UUIDRequest):
|
||||||
output = run_command([
|
output = run_command(
|
||||||
"docker", "stats", request.UUID, "--no-stream",
|
[
|
||||||
"--format", "{{.MemUsage}},{{.BlockIO}}"
|
"docker",
|
||||||
])
|
"stats",
|
||||||
|
request.UUID,
|
||||||
|
"--no-stream",
|
||||||
|
"--format",
|
||||||
|
"{{.MemUsage}},{{.BlockIO}}",
|
||||||
|
]
|
||||||
|
)
|
||||||
mem_usage, disk_usage = output.split(",")
|
mem_usage, disk_usage = output.split(",")
|
||||||
return {"memory_usage": mem_usage, "disk_io": disk_usage}
|
return {"memory_usage": mem_usage, "disk_io": disk_usage}
|
||||||
|
|
||||||
@@ -286,7 +360,10 @@ def container_quota(request: UUIDRequest):
|
|||||||
# ---------------------- SYSTEM ----------------------
|
# ---------------------- SYSTEM ----------------------
|
||||||
@app.get("/system/containers", dependencies=[Depends(verify_api_key)])
|
@app.get("/system/containers", dependencies=[Depends(verify_api_key)])
|
||||||
def get_containers():
|
def get_containers():
|
||||||
return Response(content=run_command([f"{BIN_PATH}/getContainers"]), media_type="application/json")
|
return Response(
|
||||||
|
content=run_command([f"{BIN_PATH}/getContainers"]),
|
||||||
|
media_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/system/images", dependencies=[Depends(verify_api_key)])
|
@app.get("/system/images", dependencies=[Depends(verify_api_key)])
|
||||||
@@ -294,6 +371,7 @@ def list_images():
|
|||||||
images = run_command(["docker", "images", "--format", "{{.Repository}}:{{.Tag}}"])
|
images = run_command(["docker", "images", "--format", "{{.Repository}}:{{.Tag}}"])
|
||||||
return {"images": images.split("\n")}
|
return {"images": images.split("\n")}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/system/cpu", dependencies=[Depends(verify_api_key)])
|
@app.get("/system/cpu", dependencies=[Depends(verify_api_key)])
|
||||||
def get_cpu_log():
|
def get_cpu_log():
|
||||||
CPU_LOG_PATH = Path("/4server/data/log/cpu.log")
|
CPU_LOG_PATH = Path("/4server/data/log/cpu.log")
|
||||||
@@ -307,12 +385,15 @@ def get_cpu_log():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"Error reading CPU log: {e}")
|
raise HTTPException(status_code=500, detail=f"Error reading CPU log: {e}")
|
||||||
|
|
||||||
|
|
||||||
@app.get("/system/info", dependencies=[Depends(verify_api_key)])
|
@app.get("/system/info", dependencies=[Depends(verify_api_key)])
|
||||||
def get_system_info():
|
def get_system_info():
|
||||||
try:
|
try:
|
||||||
alpine_version = None
|
alpine_version = None
|
||||||
last_update = None
|
last_update = None
|
||||||
bump_dates = execute_db("SELECT MAX(bump) AS latest_bump FROM containers", fetch=True)[0]["latest_bump"]
|
bump_dates = execute_db(
|
||||||
|
"SELECT MAX(bump) AS latest_bump FROM containers", fetch=True
|
||||||
|
)[0]["latest_bump"]
|
||||||
if os.path.exists("/4server/data/update"):
|
if os.path.exists("/4server/data/update"):
|
||||||
with open("/4server/data/update") as f:
|
with open("/4server/data/update") as f:
|
||||||
last_update = f.read().strip()
|
last_update = f.read().strip()
|
||||||
@@ -328,10 +409,14 @@ def get_system_info():
|
|||||||
"latest_bump": bump_dates,
|
"latest_bump": bump_dates,
|
||||||
"version": VERSION,
|
"version": VERSION,
|
||||||
"resources": {
|
"resources": {
|
||||||
"memory": {"total": mem.total, "available": mem.available, "used": mem.used},
|
"memory": {
|
||||||
|
"total": mem.total,
|
||||||
|
"available": mem.available,
|
||||||
|
"used": mem.used,
|
||||||
|
},
|
||||||
"disk": {"total": disk.total, "used": disk.used, "free": disk.free},
|
"disk": {"total": disk.total, "used": disk.used, "free": disk.free},
|
||||||
"cpu_count": cpu_count
|
"cpu_count": cpu_count,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
@@ -342,6 +427,13 @@ def pull_all_images():
|
|||||||
return {"message": run_command([f"{BIN_PATH}/pullAllContainers"])}
|
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)])
|
@app.post("/client/git", dependencies=[Depends(verify_api_key)])
|
||||||
def git_tool(request: CommandRequest):
|
def git_tool(request: CommandRequest):
|
||||||
if request.method == 1:
|
if request.method == 1:
|
||||||
@@ -382,7 +474,10 @@ def audit_odoo(uuid: str):
|
|||||||
@app.get("/client/logs/{uuid}", dependencies=[Depends(verify_api_key)])
|
@app.get("/client/logs/{uuid}", dependencies=[Depends(verify_api_key)])
|
||||||
async def get_odoo_log_summary(uuid: str):
|
async def get_odoo_log_summary(uuid: str):
|
||||||
if not re.fullmatch(r"[0-9a-fA-F\-]+", uuid):
|
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.")
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="Invalid UUID format. Only numbers, letters a-f, and '-' are allowed.",
|
||||||
|
)
|
||||||
|
|
||||||
BASE_LOG_DIR = "/4server/data"
|
BASE_LOG_DIR = "/4server/data"
|
||||||
|
|
||||||
@@ -424,7 +519,9 @@ async def get_odoo_log_summary(uuid: str):
|
|||||||
try:
|
try:
|
||||||
# Last 500 lines from Odoo log
|
# Last 500 lines from Odoo log
|
||||||
last_500_lines = read_last_lines(odoo_log_file, 500)
|
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)]
|
important_odoo_lines = [
|
||||||
|
line for line in last_500_lines if is_important_line(line)
|
||||||
|
]
|
||||||
|
|
||||||
# Last 50 lines from git.log
|
# Last 50 lines from git.log
|
||||||
last_50_git_lines = read_last_lines(git_log_file, 50)
|
last_50_git_lines = read_last_lines(git_log_file, 50)
|
||||||
@@ -437,6 +534,7 @@ async def get_odoo_log_summary(uuid: str):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"Error reading log files: {e}")
|
raise HTTPException(status_code=500, detail=f"Error reading log files: {e}")
|
||||||
|
|
||||||
|
|
||||||
# ------------------------ BACKUP HANDLING -------------------------------------
|
# ------------------------ BACKUP HANDLING -------------------------------------
|
||||||
@app.post("/backup/import", dependencies=[Depends(verify_api_key)])
|
@app.post("/backup/import", dependencies=[Depends(verify_api_key)])
|
||||||
def backup_import(request: ImportRequest):
|
def backup_import(request: ImportRequest):
|
||||||
@@ -447,10 +545,13 @@ def backup_import(request: ImportRequest):
|
|||||||
output = run_command(command)
|
output = run_command(command)
|
||||||
return {"message": output}
|
return {"message": output}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/backup/move", dependencies=[Depends(verify_api_key)])
|
@app.post("/backup/move", dependencies=[Depends(verify_api_key)])
|
||||||
def backup_move(request: MoveRequest):
|
def backup_move(request: MoveRequest):
|
||||||
if not request.source or not request.destination:
|
if not request.source or not request.destination:
|
||||||
raise HTTPException(status_code=400, detail="Source and destination are required")
|
raise HTTPException(
|
||||||
|
status_code=400, detail="Source and destination are required"
|
||||||
|
)
|
||||||
|
|
||||||
if not os.path.exists(request.source):
|
if not os.path.exists(request.source):
|
||||||
raise HTTPException(status_code=404, detail="Source file does not exist")
|
raise HTTPException(status_code=404, detail="Source file does not exist")
|
||||||
@@ -460,7 +561,15 @@ def backup_move(request: MoveRequest):
|
|||||||
# For Windows, use: command = ["move", request.source, request.destination]
|
# For Windows, use: command = ["move", request.source, request.destination]
|
||||||
|
|
||||||
output = run_command(command)
|
output = run_command(command)
|
||||||
return {"message": f"Moved {request.source} to {request.destination}", "output": output}
|
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 ----------------------
|
# ---------------------- Entry Point ----------------------
|
||||||
@@ -469,5 +578,3 @@ if __name__ == "__main__":
|
|||||||
init_db()
|
init_db()
|
||||||
time.sleep(25)
|
time.sleep(25)
|
||||||
uvicorn.run(app, host="10.5.0.1", port=8888)
|
uvicorn.run(app, host="10.5.0.1", port=8888)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Backup Odoo database script
|
|
||||||
# Author: Your Name
|
|
||||||
# Description: Dumps Odoo DB, manages backups, and sets permissions
|
|
||||||
|
|
||||||
set -euo pipefail # Fail on error, undefined variables, and pipe errors
|
|
||||||
|
|
||||||
# Load helper functions
|
|
||||||
source /4server/sbin/helpers
|
|
||||||
|
|
||||||
# Get contract info
|
|
||||||
get_contract_info
|
|
||||||
|
|
||||||
# Export Odoo database password
|
|
||||||
export ODOO_DB_PASSWORD
|
|
||||||
ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
|
||||||
|
|
||||||
# Display basic info
|
|
||||||
echo "UUID: $UUID"
|
|
||||||
echo "Backup slots: $BACKUP_SLOTS"
|
|
||||||
|
|
||||||
# Create backup filename
|
|
||||||
FILENAME="$(date +"%Y%m%d_%H%M").zip"
|
|
||||||
BACKUP_DIR="/BACKUP/$UUID"
|
|
||||||
|
|
||||||
# Ensure backup directory exists
|
|
||||||
mkdir -p "$BACKUP_DIR"
|
|
||||||
|
|
||||||
# Perform database dump using docker
|
|
||||||
doas docker exec "$UUID" odoo db \
|
|
||||||
--db_host beedb \
|
|
||||||
-r "$UUID" \
|
|
||||||
-w "$ODOO_DB_PASSWORD" \
|
|
||||||
--data-dir /home/odoo/.local/share/Odoo/ \
|
|
||||||
dump "$UUID" "/mnt/backup/$FILENAME"
|
|
||||||
|
|
||||||
# Set permissions for backup files
|
|
||||||
doas chmod 600 "$BACKUP_DIR"/*
|
|
||||||
doas docker exec "$UUID" chown odoo:odoo -R /mnt/backup
|
|
||||||
|
|
||||||
|
|
||||||
# Remove old backups beyond the configured slots
|
|
||||||
ls -t "$BACKUP_DIR"/[0-9]*.zip 2>/dev/null | tail -n +$((BACKUP_SLOTS + 1)) | while read -r file; do
|
|
||||||
echo "Deleting old backup: $file"
|
|
||||||
doas rm -f "$file"
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Backup Odoo database script
|
|
||||||
# Author: Your Name
|
|
||||||
# Description: Dumps Odoo DB, manages backups, and sets permissions
|
|
||||||
|
|
||||||
set -euo pipefail # Fail on error, undefined variables, and pipe errors
|
|
||||||
|
|
||||||
# Load helper functions
|
|
||||||
source /4server/sbin/helpers
|
|
||||||
|
|
||||||
# Get contract info
|
|
||||||
get_contract_info
|
|
||||||
|
|
||||||
# Export Odoo database password
|
|
||||||
export ODOO_DB_PASSWORD
|
|
||||||
ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
|
||||||
|
|
||||||
# Display basic info
|
|
||||||
echo "UUID: $UUID"
|
|
||||||
echo "Backup slots: $BACKUP_SLOTS"
|
|
||||||
|
|
||||||
# Create backup filename
|
|
||||||
FILENAME="$(date +"%Y%m%d_%H%M").zip"
|
|
||||||
BACKUP_DIR="/BACKUP/$UUID"
|
|
||||||
|
|
||||||
# Ensure backup directory exists
|
|
||||||
mkdir -p "$BACKUP_DIR"
|
|
||||||
|
|
||||||
# Perform database dump using docker
|
|
||||||
doas docker exec "$UUID" odoo db \
|
|
||||||
--db_host beedb \
|
|
||||||
-r "$UUID" \
|
|
||||||
-w "$ODOO_DB_PASSWORD" \
|
|
||||||
--data-dir /home/odoo/.local/share/Odoo/ \
|
|
||||||
dump "$UUID" "/mnt/backup/$FILENAME"
|
|
||||||
|
|
||||||
# Set permissions for backup files
|
|
||||||
doas chmod 600 "$BACKUP_DIR"/*
|
|
||||||
doas docker exec "$UUID" chown odoo:odoo -R /mnt/backup
|
|
||||||
|
|
||||||
|
|
||||||
# Remove old backups beyond the configured slots
|
|
||||||
ls -t "$BACKUP_DIR"/[0-9]*.zip 2>/dev/null | tail -n +$((BACKUP_SLOTS + 1)) | while read -r file; do
|
|
||||||
echo "Deleting old backup: $file"
|
|
||||||
doas rm -f "$file"
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Backup Odoo database script
|
|
||||||
# Author: Your Name
|
|
||||||
# Description: Dumps Odoo DB, manages backups, and sets permissions
|
|
||||||
|
|
||||||
set -euo pipefail # Fail on error, undefined variables, and pipe errors
|
|
||||||
|
|
||||||
# Load helper functions
|
|
||||||
source /4server/sbin/helpers
|
|
||||||
|
|
||||||
# Get contract info
|
|
||||||
get_contract_info
|
|
||||||
|
|
||||||
# Export Odoo database password
|
|
||||||
export ODOO_DB_PASSWORD
|
|
||||||
ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
|
||||||
|
|
||||||
# Display basic info
|
|
||||||
echo "UUID: $UUID"
|
|
||||||
echo "Backup slots: $BACKUP_SLOTS"
|
|
||||||
|
|
||||||
# Create backup filename
|
|
||||||
FILENAME="$(date +"%Y%m%d_%H%M").zip"
|
|
||||||
BACKUP_DIR="/BACKUP/$UUID"
|
|
||||||
|
|
||||||
# Ensure backup directory exists
|
|
||||||
mkdir -p "$BACKUP_DIR"
|
|
||||||
|
|
||||||
# Perform database dump using docker
|
|
||||||
doas docker exec "$UUID" odoo db \
|
|
||||||
--db_host beedb \
|
|
||||||
-r "$UUID" \
|
|
||||||
-w "$ODOO_DB_PASSWORD" \
|
|
||||||
--data-dir /home/odoo/.local/share/Odoo/ \
|
|
||||||
dump "$UUID" "/mnt/backup/$FILENAME"
|
|
||||||
|
|
||||||
# Set permissions for backup files
|
|
||||||
doas chmod 600 "$BACKUP_DIR"/*
|
|
||||||
doas docker exec "$UUID" chown odoo:odoo -R /mnt/backup
|
|
||||||
|
|
||||||
|
|
||||||
# Remove old backups beyond the configured slots
|
|
||||||
ls -t "$BACKUP_DIR"/[0-9]*.zip 2>/dev/null | tail -n +$((BACKUP_SLOTS + 1)) | while read -r file; do
|
|
||||||
echo "Deleting old backup: $file"
|
|
||||||
doas rm -f "$file"
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
@@ -26,13 +26,13 @@ case "$SECOND_PART" in
|
|||||||
"$BIN_PATH/backup/N8N"
|
"$BIN_PATH/backup/N8N"
|
||||||
;;
|
;;
|
||||||
002)
|
002)
|
||||||
"$BIN_PATH/backup/ODOO_18"
|
"$BIN_PATH/backup/ODOO"
|
||||||
;;
|
;;
|
||||||
003)
|
003)
|
||||||
"$BIN_PATH/backup/ODOO_19"
|
"$BIN_PATH/backup/ODOO"
|
||||||
;;
|
;;
|
||||||
004)
|
004)
|
||||||
"$BIN_PATH/backup/ODOO_17"
|
"$BIN_PATH/backup/ODOO"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
|
|||||||
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"
|
||||||
+30
-17
@@ -5,34 +5,47 @@ POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
|||||||
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
|
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
|
||||||
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
|
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
|
||||||
|
|
||||||
|
DB_PATH="/4server/data/contracts.db"
|
||||||
|
|
||||||
|
|
||||||
get_contract_info() {
|
get_contract_info() {
|
||||||
|
|
||||||
DB_PATH="/4server/data/contracts.db"
|
|
||||||
|
|
||||||
echo "get_contract_info $UUID"
|
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
|
while IFS="=" read -r key value; do
|
||||||
if [ -n "$key" ]; then
|
if [ -n "$key" ]; then
|
||||||
export "$key=$value"
|
export "$key=$value"
|
||||||
fi
|
fi
|
||||||
done < <(sqlite3 "$DB_PATH" "
|
done < <(sqlite3 "$DB_PATH" "
|
||||||
SELECT 'UUID=' || UUID FROM containers WHERE UUID='$UUID'
|
WITH c AS (SELECT * FROM containers WHERE UUID='$UUID' LIMIT 1)
|
||||||
UNION ALL SELECT 'EMAIL=' || email FROM containers WHERE UUID='$UUID'
|
SELECT 'UUID=' || COALESCE(UUID, '') FROM c UNION ALL
|
||||||
UNION ALL SELECT 'EXPIRES=' || expires FROM containers WHERE UUID='$UUID'
|
SELECT 'EXPIRES=' || COALESCE(expires, '') FROM c UNION ALL
|
||||||
UNION ALL SELECT 'TAGS=' || replace(replace(tags, char(10), ''), char(13), '') FROM containers WHERE UUID='$UUID'
|
SELECT 'IMAGE=' || COALESCE(image, '') FROM c UNION ALL
|
||||||
UNION ALL SELECT 'ENV=' || replace(replace(env, char(10), ''), char(13), '') FROM containers WHERE UUID='$UUID'
|
SELECT 'STATUS=' || COALESCE(status, '') FROM c UNION ALL
|
||||||
UNION ALL SELECT 'IMAGE=' || image FROM containers WHERE UUID='$UUID'
|
SELECT 'CREATED=' || COALESCE(created, '') FROM c UNION ALL
|
||||||
UNION ALL SELECT 'DOMAINS=' || domains FROM containers WHERE UUID='$UUID'
|
SELECT 'SECRET_LIST=' || COALESCE(secret_list, '') FROM c UNION ALL
|
||||||
UNION ALL SELECT 'STATUS=' || status FROM containers WHERE UUID='$UUID'
|
SELECT 'CONTAINERDBID=' || COALESCE(id, '') FROM c UNION ALL
|
||||||
UNION ALL SELECT 'CREATED=' || created FROM containers WHERE UUID='$UUID'
|
SELECT 'GIT=' || COALESCE(git, '') FROM c UNION ALL
|
||||||
UNION ALL SELECT 'SECRET=' || secret FROM containers WHERE UUID='$UUID'
|
SELECT 'BACKUP_SLOTS=' || COALESCE(backup_slots, '') FROM c UNION ALL
|
||||||
UNION ALL SELECT 'CONTAINERDBID=' || id FROM containers WHERE UUID='$UUID'
|
SELECT 'HDD=' || COALESCE(hdd, '') FROM c UNION ALL
|
||||||
UNION ALL SELECT 'BUMP=' || bump FROM containers WHERE UUID='$UUID';
|
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)
|
||||||
eval $(echo "$ENV" | jq -r 'to_entries | .[] | "export \(.key | ascii_upcase)=\(.value)"')
|
# - 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
+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
|
||||||
@@ -12,7 +12,7 @@ POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
|||||||
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
|
POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-1gtT0sf8klB9lDbYZD9}"
|
||||||
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
|
POSTGRES_ADMIN_PASSWORD="${POSTGRES_ADMIN_PASSWORD:-ZpSwWNafyy9GhY2gzHw}"
|
||||||
ODOO_DB_USER="${UUID}"
|
ODOO_DB_USER="${UUID}"
|
||||||
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
||||||
|
|
||||||
BASEURL="${BASEURL:-/4server/data/$UUID}"
|
BASEURL="${BASEURL:-/4server/data/$UUID}"
|
||||||
BACKUPURL="/4backup/$UUID"
|
BACKUPURL="/4backup/$UUID"
|
||||||
@@ -52,5 +52,3 @@ END
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "✅ Database '$UUID' and user '$ODOO_DB_USER' removed (if they existed)."
|
echo "✅ Database '$UUID' and user '$ODOO_DB_USER' removed (if they existed)."
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
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
+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"'
|
||||||
@@ -10,10 +10,10 @@ UUID="${UUID:-default}"
|
|||||||
BRANCH="${BRANCH:-release}"
|
BRANCH="${BRANCH:-release}"
|
||||||
|
|
||||||
ODOO_DB_USER="${UUID}"
|
ODOO_DB_USER="${UUID}"
|
||||||
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
||||||
|
|
||||||
|
|
||||||
echo "ENV: $HDD $DOMAIN_COUNT $BACKUP_SLOTS $CONTAINERDBID"
|
echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
|
||||||
echo "DBID: $CONTAINERDBID"
|
echo "DBID: $CONTAINERDBID"
|
||||||
|
|
||||||
BASEURL="${BASEURL:-/4server/data/$UUID}"
|
BASEURL="${BASEURL:-/4server/data/$UUID}"
|
||||||
@@ -101,7 +101,7 @@ docker run -d --name "$UUID" \
|
|||||||
-e PASSWORD="$ODOO_DB_PASSWORD" \
|
-e PASSWORD="$ODOO_DB_PASSWORD" \
|
||||||
-e UUID="$UUID" \
|
-e UUID="$UUID" \
|
||||||
-e HDD="$HDD" \
|
-e HDD="$HDD" \
|
||||||
-e DOMAIN_COUNT="$DOMAIN_COUNT" \
|
-e DOMAIN_SLOTS="$DOMAIN_SLOTS" \
|
||||||
-e BACKUP_SLOTS="$BACKUP_SLOTS" \
|
-e BACKUP_SLOTS="$BACKUP_SLOTS" \
|
||||||
-e WORKER="$WORKER" \
|
-e WORKER="$WORKER" \
|
||||||
-e GIT="$GIT" \
|
-e GIT="$GIT" \
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ UUID="${UUID:-default}"
|
|||||||
BRANCH="${BRANCH:-release}"
|
BRANCH="${BRANCH:-release}"
|
||||||
|
|
||||||
ODOO_DB_USER="${UUID}"
|
ODOO_DB_USER="${UUID}"
|
||||||
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
||||||
|
|
||||||
|
|
||||||
echo "ENV: $HDD $DOMAIN_COUNT $BACKUP_SLOTS $CONTAINERDBID"
|
echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
|
||||||
echo "DBID: $CONTAINERDBID"
|
echo "DBID: $CONTAINERDBID"
|
||||||
|
|
||||||
BASEURL="${BASEURL:-/4server/data/$UUID}"
|
BASEURL="${BASEURL:-/4server/data/$UUID}"
|
||||||
@@ -101,7 +101,7 @@ docker run -d --name "$UUID" \
|
|||||||
-e PASSWORD="$ODOO_DB_PASSWORD" \
|
-e PASSWORD="$ODOO_DB_PASSWORD" \
|
||||||
-e UUID="$UUID" \
|
-e UUID="$UUID" \
|
||||||
-e HDD="$HDD" \
|
-e HDD="$HDD" \
|
||||||
-e DOMAIN_COUNT="$DOMAIN_COUNT" \
|
-e DOMAIN_SLOTS="$DOMAIN_SLOTS" \
|
||||||
-e BACKUP_SLOTS="$BACKUP_SLOTS" \
|
-e BACKUP_SLOTS="$BACKUP_SLOTS" \
|
||||||
-e WORKER="$WORKER" \
|
-e WORKER="$WORKER" \
|
||||||
-e GIT="$GIT" \
|
-e GIT="$GIT" \
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ UUID="${UUID:-default}"
|
|||||||
BRANCH="${BRANCH:-release}"
|
BRANCH="${BRANCH:-release}"
|
||||||
|
|
||||||
ODOO_DB_USER="${UUID}"
|
ODOO_DB_USER="${UUID}"
|
||||||
export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
#export ODOO_DB_PASSWORD=$(echo "$SECRET" | jq -r '.psql')
|
||||||
|
echo "***** $ODOO_DB_PASSWORD"
|
||||||
|
|
||||||
|
echo "ENV: $HDD $DOMAIN_SLOTS $BACKUP_SLOTS $CONTAINERDBID"
|
||||||
echo "ENV: $HDD $DOMAIN_COUNT $BACKUP_SLOTS $CONTAINERDBID"
|
|
||||||
echo "DBID: $CONTAINERDBID"
|
echo "DBID: $CONTAINERDBID"
|
||||||
|
|
||||||
BASEURL="${BASEURL:-/4server/data/$UUID}"
|
BASEURL="${BASEURL:-/4server/data/$UUID}"
|
||||||
@@ -104,7 +104,7 @@ docker run -d --name "$UUID" \
|
|||||||
-e PASSWORD="$ODOO_DB_PASSWORD" \
|
-e PASSWORD="$ODOO_DB_PASSWORD" \
|
||||||
-e UUID="$UUID" \
|
-e UUID="$UUID" \
|
||||||
-e HDD="$HDD" \
|
-e HDD="$HDD" \
|
||||||
-e DOMAIN_COUNT="$DOMAIN_COUNT" \
|
-e DOMAIN_SLOTS="$DOMAIN_SLOTS" \
|
||||||
-e BACKUP_SLOTS="$BACKUP_SLOTS" \
|
-e BACKUP_SLOTS="$BACKUP_SLOTS" \
|
||||||
-e WORKER="$WORKER" \
|
-e WORKER="$WORKER" \
|
||||||
-e GIT="$GIT" \
|
-e GIT="$GIT" \
|
||||||
|
|||||||
@@ -15,16 +15,20 @@ if [[ -z "$UUID" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ ! "$UUID" =~ ^[0-9a-fA-F-]+$ ]]; then
|
||||||
|
echo "ERROR: Invalid UUID format: $UUID"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
DOMAIN_FILE="/4server/data/$UUID/etc/domain"
|
DOMAIN_FILE="/4server/data/$UUID/etc/domain"
|
||||||
DB_FILE="/4server/data/contracts.db"
|
DB_FILE="/4server/data/contracts.db"
|
||||||
if [ -f "$DOMAIN_FILE" ]; then
|
if [ -f "$DOMAIN_FILE" ]; then
|
||||||
DOMAINS=$(paste -sd "," "$DOMAIN_FILE")
|
DOMAINS=$(paste -sd "," "$DOMAIN_FILE")
|
||||||
sqlite3 "$DB_FILE" <<SQL
|
# Escape single quotes for SQLite ('' is the standard SQLite escape for ')
|
||||||
UPDATE containers
|
DOMAINS_SAFE="${DOMAINS//\'/\'\'}"
|
||||||
SET domains='$DOMAINS'
|
UUID_SAFE="${UUID//\'/\'\'}"
|
||||||
WHERE UUID='$UUID';
|
sqlite3 "$DB_FILE" "UPDATE containers SET domains='$DOMAINS_SAFE' WHERE UUID='$UUID_SAFE';"
|
||||||
SQL
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
@@ -59,4 +63,3 @@ case "$SECOND_PART" in
|
|||||||
exit 2
|
exit 2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# ~/.bashrc
|
# ~/.bashrc
|
||||||
clear
|
clear
|
||||||
echo "Server {{HOSTNAME}}"
|
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\] "
|
export PS1="\[\e[32m\]\h:\w\$\[\e[0m\] "
|
||||||
df -h .
|
df -h .
|
||||||
|
|||||||
+1
-2
@@ -25,7 +25,7 @@ template templates/.profile /home/4server/.profile
|
|||||||
### PACKAGES
|
### PACKAGES
|
||||||
template templates/repositories /etc/apk/repositories
|
template templates/repositories /etc/apk/repositories
|
||||||
rex "doas apk update && doas apk upgrade"
|
rex "doas apk update && doas apk upgrade"
|
||||||
rex doas apk add 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 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
|
rex doas pip install --root-user-action ignore --break-system-packages --no-cache-dir "uvicorn[standard]" fastapi pydantic psutil gdown
|
||||||
|
|
||||||
@@ -94,4 +94,3 @@ prsync -h "/app/host_vars/hosts" -avz ./etc/traefik/certs/* /4server/data/traefi
|
|||||||
|
|
||||||
template templates/docker-compose.yml /4server/docker-compose.yml
|
template templates/docker-compose.yml /4server/docker-compose.yml
|
||||||
rex doas docker-compose -f /4server/docker-compose.yml up -d --force-recreate
|
rex doas docker-compose -f /4server/docker-compose.yml up -d --force-recreate
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
|
|
||||||
echo "Running prsync ./sbin"
|
echo "Running prsync ./sbin"
|
||||||
prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/
|
prsync -h "/app/host_vars/hosts" -avz ./sbin/ /4server/sbin/
|
||||||
|
template templates/.profile /home/4server/.profile
|
||||||
|
|
||||||
rex doas rc-service api restart
|
rex doas rc-service api restart
|
||||||
|
|
||||||
rex doas rc-service checkCalls 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*
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user