# ── Base image ──────────────────────────────────────────────────────────────── FROM node:20-slim # ── System dependencies ─────────────────────────────────────────────────────── RUN apt-get update && \ apt-get install -y --no-install-recommends \ python3 \ python3-pip \ python3-venv \ make \ g++ \ wget \ curl \ git \ neofetch \ mediainfo \ screen \ nano \ rsync \ && rm -rf /var/lib/apt/lists/* # ── Install bore ────────────────────────────────────────────────────────────── RUN wget -q "https://github.com/ekzhang/bore/releases/download/v0.5.0/bore-v0.5.0-x86_64-unknown-linux-musl.tar.gz" \ -O /tmp/bore.tar.gz && \ tar -xzf /tmp/bore.tar.gz -C /usr/local/bin bore && \ chmod +x /usr/local/bin/bore && \ rm /tmp/bore.tar.gz # ── Pin a stable machine-id ─────────────────────────────────────────────────── RUN echo "d8904b4d338adf83688caac869f64c0b" > /etc/machine-id && \ mkdir -p /var/lib/dbus && \ echo "d8904b4d338adf83688caac869f64c0b" > /var/lib/dbus/machine-id USER root # ── Set HOME and PATH ───────────────────────────────────────────────────────── ENV HOME=/root \ PATH="/root/venv/bin:/usr/local/bin:${PATH}" \ VIRTUAL_ENV=/root/venv \ PIP_NO_CACHE_DIR=1 \ HOSTNAME=kanha # ── Install shellular globally ──────────────────────────────────────────────── RUN npm install -g --prefix /usr/local shellular # ── Create Python venv ──────────────────────────────────────────────────────── RUN python3 -m venv /root/venv && \ /root/venv/bin/pip install --upgrade pip && \ /root/venv/bin/pip install huggingface_hub && \ rm -f /root/venv/lib/python*/EXTERNALLY-MANAGED # ── Shell config ────────────────────────────────────────────────────────────── RUN echo 'export PS1="\u@kanha:\w\$ "' >> /root/.bashrc && \ echo 'cd /data' >> /root/.bashrc && \ echo 'cd /data' >> /root/.bash_profile # ── Force pip/python → always use /root/venv ───────────────────────────────── RUN printf '%s\n' \ '#!/bin/sh' \ 'exec /root/venv/bin/pip "$@"' \ > /usr/local/bin/pip && chmod +x /usr/local/bin/pip && \ \ printf '%s\n' \ '#!/bin/sh' \ 'exec /root/venv/bin/pip3 "$@"' \ > /usr/local/bin/pip3 && chmod +x /usr/local/bin/pip3 && \ \ printf '%s\n' \ '#!/bin/sh' \ 'exec /root/venv/bin/python "$@"' \ > /usr/local/bin/python && chmod +x /usr/local/bin/python && \ \ printf '%s\n' \ '#!/bin/sh' \ 'exec /root/venv/bin/python3 "$@"' \ > /usr/local/bin/python3 && chmod +x /usr/local/bin/python3 # ── apt/apt-get wrapper → saves to apt.txt ─────────────────────────────────── RUN printf '%s\n' \ '#!/bin/sh' \ 'APT_FILE="/data/apt.txt"' \ 'REAL_BIN="$1"' \ 'shift' \ 'if [ "$1" = "install" ]; then' \ ' shift' \ ' "$REAL_BIN" install "$@"' \ ' STATUS=$?' \ ' if [ $STATUS -eq 0 ]; then' \ ' touch "$APT_FILE"' \ ' for arg in "$@"; do' \ ' case "$arg" in -*) continue ;; esac' \ ' if ! grep -qx "$arg" "$APT_FILE"; then' \ ' echo "$arg" >> "$APT_FILE"' \ ' echo "✓ $arg saved to apt.txt"' \ ' fi' \ ' done' \ ' fi' \ ' exit $STATUS' \ 'fi' \ '"$REAL_BIN" "$@"' \ > /usr/local/bin/_apt_wrapper && chmod +x /usr/local/bin/_apt_wrapper RUN printf '%s\n' \ '#!/bin/sh' \ 'exec /usr/local/bin/_apt_wrapper /usr/bin/apt "$@"' \ > /usr/local/bin/apt && chmod +x /usr/local/bin/apt RUN printf '%s\n' \ '#!/bin/sh' \ 'exec /usr/local/bin/_apt_wrapper /usr/bin/apt-get "$@"' \ > /usr/local/bin/apt-get && chmod +x /usr/local/bin/apt-get # ── Ensure /data exists ─────────────────────────────────────────────────────── RUN mkdir -p /data # ── App ─────────────────────────────────────────────────────────────────────── COPY package*.json /root/app/ RUN cd /root/app && npm install --omit=dev COPY . /root/app/ # ── Entrypoint ──────────────────────────────────────────────────────────────── RUN printf '%s\n' \ '#!/bin/sh' \ 'set -e' \ '' \ 'mkdir -p /data/persist' \ '' \ '# ── Step 1: RESTORE — /data/persist → /root/ ────────────────────────' \ '# /root/ is always the real live directory' \ '# On restart container is fresh so restore from backup' \ 'if [ -d "/data/persist/venv" ] && [ ! -L "/data/persist/venv" ]; then' \ ' echo "▶ Restoring venv from backup..."' \ ' rsync -a --ignore-existing /data/persist/venv/ /root/venv/ 2>/dev/null || true' \ 'else' \ ' echo "▶ No venv backup found, using image default"' \ 'fi' \ 'if [ -d "/data/persist/cache" ] && [ ! -L "/data/persist/cache" ]; then' \ ' echo "▶ Restoring cache from backup..."' \ ' rsync -a --ignore-existing /data/persist/cache/ /root/.cache/ 2>/dev/null || true' \ 'fi' \ '' \ '# ── Step 2: Fix pip ──────────────────────────────────────────────────' \ 'rm -f /root/venv/lib/python*/EXTERNALLY-MANAGED' \ '' \ '# ── Step 3: WIPE old backup, recreate fresh from /root/ ─────────────' \ '# Deleting old backup is safe because we already restored to /root/' \ '# Fresh copy from /root/ always has correct permissions' \ 'echo "▶ Rebuilding backup from /root/venv..."' \ 'rm -rf /data/persist/venv' \ 'rm -rf /data/persist/cache' \ 'cp -a /root/venv /data/persist/venv' \ 'cp -a /root/.cache /data/persist/cache 2>/dev/null || true' \ 'echo "▶ Backup done"' \ '' \ '# ── Step 4: Background sync every 60s ───────────────────────────────' \ '# Catches any pip installs or changes while server is running' \ '# /root/ → /data/persist/ only, never the other way' \ '(' \ ' while true; do' \ ' sleep 60' \ ' rsync -a --delete /root/venv/ /data/persist/venv/ 2>/dev/null' \ ' rsync -a --delete /root/.cache/ /data/persist/cache/ 2>/dev/null' \ ' done' \ ') &' \ '' \ '# ── Step 5: apt.txt reinstall ────────────────────────────────────────' \ 'APT_FILE="/data/apt.txt"' \ 'if [ ! -f "$APT_FILE" ]; then touch "$APT_FILE"; fi' \ 'if [ -s "$APT_FILE" ]; then' \ ' echo "▶ Installing packages from apt.txt..."' \ ' /usr/bin/apt-get update -qq' \ ' while IFS= read -r pkg || [ -n "$pkg" ]; do' \ ' pkg=$(echo "$pkg" | tr -d "[:space:]")' \ ' [ -z "$pkg" ] && continue' \ ' [ "${pkg#\#}" != "$pkg" ] && continue' \ ' /usr/bin/apt-get install -y --no-install-recommends "$pkg" \' \ ' && echo "✓ $pkg" \' \ ' || echo "WARNING: Failed to install $pkg"' \ ' done < "$APT_FILE"' \ ' rm -rf /var/lib/apt/lists/*' \ 'fi' \ '' \ 'exec "$@"' \ > /usr/local/bin/docker-entrypoint.sh && \ chmod +x /usr/local/bin/docker-entrypoint.sh # ── Runtime ─────────────────────────────────────────────────────────────────── WORKDIR /data EXPOSE 7860 ENV PORT=7860 ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] CMD ["node", "/root/app/app.js"]