File size: 5,582 Bytes
44b5a51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# =============================================================================
# 9Hits Viewer v6 + FeelingSurf Viewer + /health endpoint - ready for Render.com
#
# Based on the official 9Hits image: https://hub.docker.com/r/9hitste/appv6
# (it carries the correct system libraries plus the viewer tarball baked in).
#
# Why this image exists:
#   * The upstream image extracts its 145 MB bzip2 viewer tarball on EVERY
#     container start and runs the viewer via an opaque /nh.sh. On free-tier
#     CPUs (Render 0.1 vCPU) that runtime extraction stalls for minutes and a
#     wedged viewer produces no output and never exits ("Extracting
#     9hitsv6-linux64 ..." then silence forever). We instead extract the
#     viewer at BUILD time into /opt/9hits and drive it directly, mirroring
#     the official install.sh flow:
#       Xvfb :99  ->  nhviewer <flags> --exit-on-init  (apply config)
#                 ->  nhviewer --auto-start --in-loop --render-to-terminal
#   * The v6 viewer requires a TTY (Render doesn't allocate one), so the
#     entrypoint runs it under a pseudo-TTY (run_pty.py, with a wedge
#     watchdog and output heartbeat).
#   * Render web services must listen on 0.0.0.0:$PORT and pass an HTTP
#     health check, so the entrypoint also runs a tiny /health HTTP server
#     (health_server.py) - perfect target for an uptime ping bot.
# =============================================================================

FROM 9hitste/appv6

ARG FSVIEWER_VERSION=2.5.2

# Add the official FeelingSurf application to the 9Hits image. Both upstream
# viewers are Debian/Ubuntu applications, so they can safely share this image
# and its Chromium/X11 libraries. Pinning the release keeps builds repeatable.
#
# Also extract the 9Hits v6 viewer (baked into the base image at
# /etc/9hitsv6-linux64.tar.bz2) into /opt/9hits NOW, at build time, so the
# container starts instantly instead of decompressing 145 MB of bzip2 on a
# throttled CPU at every boot. The tarball is then removed (whiteout) to save
# runtime disk.
RUN set -eux; \
    command -v apt-get >/dev/null 2>&1 || { \
        echo "ERROR: the 9Hits base image must provide apt-get" >&2; exit 1; \
    }; \
    test -f /etc/9hitsv6-linux64.tar.bz2 || { \
        echo "ERROR: /etc/9hitsv6-linux64.tar.bz2 missing - base image layout changed?" >&2; exit 1; \
    }; \
    apt-get update; \
    apt-get install -y --no-install-recommends \
        python3 ca-certificates wget bzip2 util-linux psmisc bc \
        libasound2 libgbm1 libgtk-3-0 libnotify4 libnss3 libsecret-1-0 \
        libxss1 libxtst6 libatspi2.0-0 libatomic1 libcanberra-gtk-module \
        xdg-utils xvfb x11-utils x11vnc; \
    getent group audio >/dev/null || groupadd -r audio; \
    getent group fsviewer >/dev/null || groupadd -r fsviewer; \
    id fsviewer >/dev/null 2>&1 || useradd -r -m -g fsviewer -G audio fsviewer; \
    echo 'pcm.!default {\n  type plug\n  slave.pcm "null"\n}' > /etc/asound.conf; \
    arch="$(dpkg --print-architecture)"; \
    wget -q -O /tmp/feelingsurf.deb \
      "https://github.com/feelingsurf/viewer/releases/download/${FSVIEWER_VERSION}/FeelingSurfViewer-linux-${arch}-${FSVIEWER_VERSION}.deb"; \
    dpkg -i /tmp/feelingsurf.deb || apt-get install -f -y --no-install-recommends; \
    test -x /usr/bin/FeelingSurfViewer; \
    rm -f /tmp/feelingsurf.deb; \
    # --- 9Hits viewer: extract at build time, mirroring official install.sh ---
    mkdir -p /opt/9hits /tmp/nhextract; \
    tar -xjf /etc/9hitsv6-linux64.tar.bz2 -C /tmp/nhextract; \
    if [ -f /tmp/nhextract/nhviewer ]; then \
        cp -a /tmp/nhextract/. /opt/9hits/; \
    else \
        sub="$(find /tmp/nhextract -mindepth 1 -maxdepth 1 -type d | head -n 1)"; \
        test -n "$sub" && test -f "$sub/nhviewer" || { \
            echo "ERROR: nhviewer not found in the baked tarball" >&2; exit 1; \
        }; \
        cp -a "$sub"/. /opt/9hits/; \
    fi; \
    rm -rf /tmp/nhextract; \
    chmod -R a+rwX /opt/9hits; \
    chmod +x /opt/9hits/nhviewer; \
    test -x /opt/9hits/nhviewer; \
    rm -f /etc/9hitsv6-linux64.tar.bz2; \
    rm -rf /var/lib/apt/lists/* /usr/share/doc/* /usr/share/man/* /tmp/* /var/tmp/*

WORKDIR /

COPY start.sh        /start.sh
COPY run_pty.py      /run_pty.py
COPY health_server.py /health_server.py
COPY memguard.py     /memguard.py
COPY fetch_proxy_list.py /fetch_proxy_list.py
COPY feelingsurf-run.sh /feelingsurf-run.sh

RUN chmod +x /start.sh /run_pty.py /health_server.py /memguard.py /fetch_proxy_list.py /feelingsurf-run.sh

# 512MB dual-viewer survival (see start.sh): LOW_MEMORY shrinks both
# Chromiums, memguard.py restarts the heaviest viewer before the platform can
# OOM the container, and auto mode alternates the viewers when they still
# don't fit simultaneously (Render free = 512 MB).
ENV PORT=10000 \
    FEELINGSURF_PORT=3000 \
    FEELINGSURF_ENABLED=yes \
    SUPERVISOR_DELAY=10 \
    NH_DIR=/opt/9hits \
    NH_DISPLAY=:99 \
    INIT_TIMEOUT=300 \
    NH_WATCHDOG=yes \
    NH_WATCHDOG_STUCK=600 \
    DUAL_VIEWER_MODE=auto \
    TIME_SLICE=1500 \
    LOW_MEMORY=auto

EXPOSE 10000 3000 5901

HEALTHCHECK --interval=30s --timeout=5s --start-period=45s --retries=3 \
    CMD wget -q --spider "http://localhost:${PORT}/health" || exit 1

# /start.sh launches BOTH viewers (supervised + auto-restarted) plus the
# Xvfb display for 9Hits, the combined /health HTTP server, and memguard.py
# (memory guardian / time-slice scheduler for 512 MB plans).
# Extra docker/start-command arguments are forwarded to the nhviewer init pass.
ENTRYPOINT ["/start.sh"]
CMD []