hits / README.md
Sugamdeol's picture
Upload 31 files
6f2ee16 verified
|
Raw
History Blame Contribute Delete
30.1 kB
---
title: 9Hits Viewer v6 + FeelingSurf Viewer
emoji: 🌐
colorFrom: blue
colorTo: indigo
sdk: gradio
sdk_version: 4.44.0
app_file: app_hf.py
pinned: false
---
# hits4me
Run the **9Hits Viewer v6** ([9hitste/appv6](https://hub.docker.com/r/9hitste/appv6)) and the
**FeelingSurf Viewer** ([feelingsurf/viewer](https://hub.docker.com/r/feelingsurf/viewer)) on
**100% Free Cloud Hosting Platforms** (**Hugging Face Spaces, Koyeb, Render, Oracle Cloud Always Free, Fly.io, Railway, Zeabur**) as lightweight services with integrated **`/health` endpoints** for uptime monitoring.
* **9Hits Viewer** β€” sophisticated traffic-exchange viewer with proxy/proxy-pool/system-session options.
* **FeelingSurf Viewer** β€” drop-in autosurf viewer; one container, one env var (`access_token`), no extra configuration.
---
## Free Multi-Platform Strategy: Free System Sessions on Clean Cloud IPs
Rather than relying on shared free proxy lists (which frequently encounter `Auth: Duplicate USER on IP` errors from other 9Hits members), the most reliable method is to run **one system session on each free cloud hosting platform**.
Each provider assigns an isolated, clean datacenter outbound IP address:
| Platform | Free Tier Type | Resources | Outbound Region / IP | Deployment Method |
| :--- | :--- | :--- | :--- | :--- |
| **Hugging Face Spaces** | **100% Free (Gradio SDK)** | **16 GB RAM**, 2 vCPU | US / EU (AWS) | Free Gradio Space (`app_hf.py`) |
| **Koyeb** | ACCESS_KEY + ACCESS_TOKEN are hard-coded in `koyeb.yaml`. | Uses `koyeb.yaml`; one service runs both viewers. |
| **Render** | **100% Free (Web Service)** | 512 MB RAM, 750 hrs/mo | Oregon, Ohio, Frankfurt, Singapore | Docker (`render.yaml`, both tokens hard-coded) |
| **Oracle Cloud Always Free** | **100% Forever Free** | **24 GB RAM**, 4 ARM cores | Your chosen home region | `docker compose up -d` |
| **Fly.io** | ACCESS_KEY + ACCESS_TOKEN are hard-coded in `koyeb.yaml`. | Uses `fly.toml`; one service runs both viewers. |
| **Zeabur / Railway** | Free Trial / Starter credits | 512 MB RAM | Global Edge | `zeabur.json` / `railway.json` |
---
## Deployment Guides for 100% Free Platforms
### 1. Hugging Face Spaces (100% FREE β€” Gradio SDK, 16 GB RAM)
> πŸ’‘ *Note: Docker Spaces require a paid subscription on HF, but **Gradio Spaces are 100% Free** with 16 GB RAM! This repo includes `app_hf.py` to run seamlessly on the standard Gradio SDK.*
1. Go to [huggingface.co/spaces](https://huggingface.co/spaces) β†’ **Create new Space**.
2. Space Name: `hits4me-viewer` (or any name).
3. Select **Gradio** SDK (leave hardware as **Free 2 vCPU Β· 16 GB RAM**).
4. Connect or duplicate this repository.
5. The HF entrypoint includes the configured `ACCESS_KEY` and `ACCESS_TOKEN`, so
no secret variables are required. Optional variables with those names override
the built-in values. Keep `SYSTEM_SESSION=yes`, `CLEAR_ALL_SESSIONS=yes`,
`SESSION_NOTE=hf-system`, `NOTE=huggingface`, and `RESET_INTERVAL=2h` if you
want to customize the defaults.
6. The Space will build and launch a live status dashboard while running 9Hits
and three native FeelingSurf processes. HF mode uses `LOW_MEMORY=off`,
`DUAL_VIEWER_MODE=off`, and does not start `memguard.py`.
---
### 2. Koyeb (100% Free Docker Nano Instance)
1. Go to [app.koyeb.com](https://app.koyeb.com/) β†’ **Create App**.
2. Select **GitHub** β†’ select `hits4me`.
3. Choose **Dockerfile** deployment and the **Free (Nano)** instance type.
4. Set Environment Variables:
- `ACCESS_KEY`: `<your-9hits-access-key>`
- `SYSTEM_SESSION`: `yes`
- `CLEAR_ALL_SESSIONS`: `yes`
- `SESSION_NOTE`: `koyeb-system`
- `NOTE`: `koyeb`
- `RESET_INTERVAL`: `2h`
- `PORT`: `10000`
5. Health Check: Path `/health`, Port `10000`. Click **Deploy**.
---
### 3. Render (100% Free Docker Web Service)
The existing **`9hits-viewer`** web service ([dashboard](https://dashboard.render.com/web/srv-da09a7dbedkc73a829cg)) runs **one** container. Applying this repo's Blueprint updates that same service only β€” it does **not** create any new Render service (there is exactly one service in `render.yaml`).
The Blueprint ships with **BOTH viewers enabled** (`NINEHITS_ENABLED=yes`, `FEELINGSURF_ENABLED=yes`) plus the 512 MB survival stack described below, so the old "OOM every ~1 minute" loop is gone. (Previously this service ran FeelingSurf-only because two un-tuned Chromium viewers exceed the free plan's 512 MB.)
1. In [Render Dashboard](https://dashboard.render.com/) apply the Blueprint for this repo so it updates the existing **`9hits-viewer`** service (or open that service and redeploy).
2. Or, only if `9hits-viewer` does not already exist, create **New β†’ Web Service** β†’ Docker runtime β†’ Free tier and name it exactly `9hits-viewer`.
3. `ACCESS_KEY` and `ACCESS_TOKEN` are **hard-coded in `render.yaml`** β€” no prompting needed, just deploy.
4. Default environment comes from the Blueprint: both viewers + `DUAL_VIEWER_MODE=auto` + `LOW_MEMORY=auto` + `MEMGUARD_LIMIT_MB=512` (see [Running both viewers on 512 MB](#running-both-viewers-on-512-mb-render-free)). After deploy, check `GET /health` β†’ `effective_mode` and `memory_used_mb` to see whether both viewers run concurrently or are being alternated.
5. *(Optional)* Deploy another free service in a different region (e.g. Frankfurt or Ohio) to get an extra unique IP address.
---
## Running both viewers on 512 MB (Render free)
Two Chromium-based viewers (9Hits v6 CEF + FeelingSurf Electron) each idle at
roughly 250–400 MB β€” together they blow past Render's 512 MB hard cgroup limit,
which is what caused the classic `Ran out of memory (used over 512MB)` β†’
"Service recovered" β†’ repeat loop. This repo now keeps the pair inside the
budget with **three cooperating layers** (all stdlib/builtin, no extra deps):
**Layer 1 β€” shrink both viewers (`LOW_MEMORY`, default `auto`).**
On boxes with < 1 GB of detected memory the entrypoint adds Chromium/Electron
switches to both viewers: `--renderer-process-limit=1` (one renderer for all
sessions instead of one per session), `--enable-low-end-device-mode` +
`--memory-model=low` (Chromium's own low-RAM behaviour), V8 heap caps
(`--js-flags=--max-old-space-size=64`), disk/media caches off, background
sync/extensions/network off, and β€” for 9Hits only β€” `--disable-gpu` to drop the
separate GPU process (~40–80 MB). FeelingSurf keeps upstream's swiftshader GL
flags (upstream removed `--disable-gpu` because it crashes 2.5.2). Xvfb
resolutions also shrink to 1280x720, and `MALLOC_TRIM_THRESHOLD_`/`MMAP`
tunables make glibc give freed heap pages back to the kernel.
**`LOW_MEMORY=extreme`** takes it further: both viewers run with
`--single-process --in-process-gpu` β€” measured **~324 MB for the pair**, which
is what makes running BOTH viewers at the same time possible on 512 MB.
**Layer 2 β€” memory guardian (`memguard.py`, always on unless `off`).**
Every few seconds it sums container RSS from `/proc` against the real limit
(`MEMGUARD_LIMIT_MB`, auto-detected from the cgroup, set to `512` in
`render.yaml`). If total RSS crosses `MEMGUARD_HARD_PCT` (default 97% on small
boxes), it **gracefully restarts the heaviest viewer** (TERM β†’ KILL after 15 s;
the viewer's own supervisor in `start.sh` brings it back) *before* the platform
can OOM-kill the whole container. A cooldown prevents kill-thrash. With
`DUAL_VIEWER_MODE=concurrent` + `extreme` the pair sits well under the
threshold, so the guardian stays silent and both viewers run together.
**Layer 3 β€” time-slice fallback (`DUAL_VIEWER_MODE`, default `auto`).**
If the box really cannot fit both at once (3+ over-budget restarts inside
10 min), memguard auto-switches to **time-slice**: the two viewers alternate,
`TIME_SLICE` seconds each (default 1500 = 25 min), so only one Chromium is
resident at a time β€” guaranteed to fit 512 MB, at the cost of ~50% uptime per
viewer. The supervisors gate their launches on a turn file
(`/tmp/active_viewer`) and **fail open**: if memguard dies, both viewers run
freely (no deadlock). With `LOW_MEMORY=extreme` the fallback rarely triggers.
| `DUAL_VIEWER_MODE` | Behaviour |
| :--- | :--- |
| `concurrent` | **Always run both at the same time** (pair with `LOW_MEMORY=extreme`, ~324 MB). The guardian is the safety valve only. |
| `auto` (default) | Run both together; escalate to time-slice only if RAM proves too small. |
| `time-slice` | Alternate every `TIME_SLICE` seconds. Predictable ~50% uptime each, zero OOM risk. |
| `off` | Legacy: no guardian, no slicing (two viewers can still OOM 512 MB plans). |
## Dashboard GUI
Open **`/`** (or `/gui`) on your service in a browser β€” no login, no extra
server, ~0 MB of RAM (a static page that polls `/health` every 2 s):
* **Memory card** β€” live container RSS vs the budget with the hard-threshold
marker, plus per-viewer bars (9Hits v6 / FeelingSurf) and the peak.
* **Dual-viewer card** β€” configured vs effective mode, the active viewer, and
a **countdown to the next time-slice flip**.
* **Viewers card** β€” running state, phase/pid, silent seconds, restarts for
each viewer and the memguard intervention counter.
It is served by the same health server, so it works on Render free (which only
exposes `$PORT`), Koyeb, Fly, Railway, Zeabur and local compose alike.
## Measured memory (real Chromium 149, this repo's flags)
The proprietary viewer binaries can't be downloaded on every box, so these
numbers were measured with a **real, modern Chromium engine** (Chromium 149)
running the exact `NH_MEM_FLAGS` / `FS_MEM_FLAGS` sets that `start.sh` applies,
against realistic autosurf-style pages (headless β€” real deployments add
~20–40 MB per Xvfb display, so treat these as *lower bounds*).
Two numbers are given for the pairs: **RSS** (what the raw process list sums
to β€” double-counts the file-backed pages the two instances share) and
**PSS** (proportional set size β€” the *unique* memory, which is what the cgroup
actually charges; this is what memguard and the dashboard report):
| Scenario | RSS (median, MB) |
| :--- | ---: |
| one viewer, tuned (`LOW_MEMORY=balanced`), active on a light page | **~284–318** |
| one viewer, tuned, idle (between campaigns) | **~277** |
| one viewer, stock (no flags) | ~316 |
| 3 sessions in one viewer, stock | ~610 |
| 3 sessions in one viewer, tuned (`--renderer-process-limit=1`) | ~403 (**βˆ’34%**) |
| both viewers, `balanced`, active together | ~570–620 (RSS) ❌ |
| both viewers, balanced, even idle | ~550 (RSS) ❌ |
| **both viewers, `LOW_MEMORY=extreme` (`--single-process`), 2 tabs each** | **~324 RSS / ~308 PSS** βœ… |
| **9Hits `extreme` + FeelingSurf swiftshader (`FS_SP=no`)** | **~458 RSS** βœ… |
**What this means β€” and how to run BOTH at the same time:**
With the *balanced* flags, two Chromium viewers can't both stay resident in
512 MB (~550–620 MB), which is why `auto` mode time-slices them. But
**`LOW_MEMORY=extreme` puts each viewer into single-process mode
(`--single-process --in-process-gpu`)** β€” measured at **~324 MB for the whole
pair** with two sessions each, stable over 60 s+ of real Chromium 149
(and ~458 MB if FeelingSurf keeps its normal swiftshader processes via
`FS_SP=no`). Both comfortably fit the 512 MB budget, so the two viewers run
**concurrently**:
```env
NINEHITS_ENABLED=yes
FEELINGSURF_ENABLED=yes
DUAL_VIEWER_MODE=concurrent # both at the same time, no time-slicing
LOW_MEMORY=extreme
MEMGUARD_LIMIT_MB=512
MEMGUARD_HARD_PCT=97 # guardian acts at 497 MB - before the OOM
```
Safety nets keep `--single-process` from ever bricking a viewer:
* **Crash auto-fallback** β€” if a viewer crash-loops 3Γ— at startup, its
single-process flags are dropped automatically (it keeps running on the
balanced set; `NH_SP=no` / `FS_SP=no` force this manually).
* **FeelingSurf GL** β€” upstream runs swiftshader; under single-process, if FS
crash-loops, set `FS_GL_MODE=disable-gpu` (or `FS_SP=no`).
* **memguard** still guards at `MEMGUARD_HARD_PCT` of the budget, so a spike
restarts the heaviest viewer instead of letting the platform OOM the box.
The dashboard (`/`) shows both viewers' RSS bars live, so you can confirm they
are up at the same time.
Watch it live via `GET /health` (JSON) or open **`/`** in a browser for the dashboard GUI:
```json
{
"dual_viewer_mode": "auto",
"effective_mode": "time-slice", // auto escalated - box can't fit both
"active_viewer": "feelingsurf", // ninehits is parked until its turn
"memory_used_mb": 412.5, // total container RSS right now
"memory_limit_mb": 512, // memguard acts before this is hit
"memory_peak_mb": 498.1,
"ninehits_rss_mb": 0.0, // parked viewer holds ~0 MB
"feelingsurf_rss_mb": 331.2,
"memguard_interventions": 3, // times the heaviest viewer was restarted
"memguard_last_target": "ninehits",
"next_flip_in_seconds": 412 // countdown in time-slice mode
}
```
Notes:
* `effective_mode == "concurrent"` means **both viewers are earning 100%** of
the time; `"time-slice"` means they alternate. Either way the container no
longer OOM-loops.
* Uptime bots will see `"status": "restarting"` while the parked viewer is
down in time-slice mode β€” that is expected and is not a crash.
* Want 9Hits alone or FeelingSurf alone on 512 MB? Set
`NINEHITS_ENABLED=yes` + `FEELINGSURF_ENABLED=no` (or vice versa) β€” one
viewer easily fits.
* On hosts with β‰₯ 2 GB (Fly 4 GB, Oracle 24 GB, HF 16 GB) `LOW_MEMORY=auto`
disables the flags, memguard never intervenes, and everything runs
concurrently as before.
---
### 4. Oracle Cloud Always Free (24 GB RAM / 4 CPUs Forever Free)
Oracle Cloud provides the most generous free tier in the cloud industry (4 ARM vCPUs + 24 GB RAM, plus 2 x86 AMD instances):
```bash
git clone https://github.com/Sugamdeol/hits4me.git && cd hits4me
cp .env.example .env
# Edit .env and set ACCESS_KEY (9Hits) AND/OR ACCESS_TOKEN (FeelingSurf)
nano .env
# Start BOTH viewers in one container/deployment:
docker compose up -d
```
---
### 5. Fly.io
1. Install `flyctl`: `curl -L https://fly.io/install.sh | sh`
2. Run `fly launch` in this directory (uses `fly.toml`).
3. Set both secrets: `fly secrets set ACCESS_KEY="<your-9hits-key>" ACCESS_TOKEN="<your-feelingsurf-token>"`.
4. Deploy: `fly deploy`.
---
## FeelingSurf Viewer (additional autosurf viewer)
Hit "too many free platforms, no proxies" with 9Hits? Same repo can run the **FeelingSurf Viewer** ([feelingsurf/viewer:stable](https://hub.docker.com/r/feelingsurf/viewer)) alongside (or instead of) the 9Hits viewer. It's a self-contained Electron-style app β€” only `access_token` env var, no proxy-aware flags, no dashboards to configure.
⚠️ **Disclaimer:** *Never share your FeelingSurf `access_token` β€” it grants full access to your account.*
### Get an access token
1. Register at [feelingsurf.fr](https://www.feelingsurf.fr/) and finish email confirmation.
2. Go to **Member area β†’ Profile / Settings β†’ API / Access Token** and generate one. (The token is a long opaque string β€” treat it like a password.)
### 1. `docker run` (the official quick start)
```bash
docker run -d \
-e access_token=YOUR_ACCESS_TOKEN_HERE \
--tmpfs /tmp \
--tmpfs /dev/shm \
feelingsurf/viewer:stable
```
That's it β€” no apt deps, no flags, no proxy list to maintain. The container starts Xvfb internally, launches the viewer, and exposes its UI on **`http://<host>:3000/`**. The image's built-in Docker `HEALTHCHECK` pings that endpoint every minute.
### 2. `docker compose` (same container as 9Hits)
The repository Dockerfile installs FeelingSurf alongside 9Hits. One Compose service and one deploy now run both viewers:
```bash
cp .env.example .env
# Fill in ACCESS_KEY (9Hits) AND/OR ACCESS_TOKEN (FeelingSurf), e.g.:
# ACCESS_KEY=...
# ACCESS_TOKEN=...
docker compose up -d # both viewers, one container
curl http://localhost:10000/health # combined status
# Set FEELINGSURF_ENABLED=no only when you intentionally want 9Hits alone.
```
Service map:
| Container | Processes | Health |
| :--- | :--- | :--- |
| `viewers` | supervised 9Hits + supervised FeelingSurf | combined `GET /health` on port `10000`; FeelingSurf also listens internally on `3000` |
The platform deploys one image once. Both viewers share the container network and `/dev/shm`, while independent supervisors restart either process if it exits. Budget at least **4 GB RAM and 2 CPUs** for reliable operation; small free instances may run out of memory.
### 3. Free cloud platforms
The combined repository `Dockerfile` is used on cloud platforms. Configure both `ACCESS_KEY` and `ACCESS_TOKEN` on the same service.
| Platform | Deployment | Notes |
| :--- | :--- | :--- |
| **Oracle Cloud Always Free** | use `docker compose up -d` | One combined service; the 24 GB tier has ample memory. |
| **Render** | Blueprint (`render.yaml`) updates the single existing **`9hits-viewer`** web service; `ACCESS_TOKEN` is preconfigured. **Both viewers enabled** (`NINEHITS_ENABLED=yes`) with `DUAL_VIEWER_MODE=auto` + `LOW_MEMORY=auto` + `MEMGUARD_LIMIT_MB=512`. | The 512 MB free plan is handled by the three-layer stack (see [Running both viewers on 512 MB](#running-both-viewers-on-512-mb-render-free)): memory flags shrink both Chromiums, memguard restarts the heaviest viewer before the platform OOMs the container, and `auto` alternates the two if they still don't fit. |
| **Koyeb** | ACCESS_KEY + ACCESS_TOKEN are hard-coded in `koyeb.yaml`. | Uses `koyeb.yaml`; one service runs both viewers. |
| **Fly.io** | Deploy this repository Dockerfile and set both secrets. | Uses `fly.toml`; one service runs both viewers. |
| **Railway** | Deploy this repository Dockerfile and set both secrets. | Uses `railway.json`; one service runs both viewers. |
| **Zeabur** | Deploy this repository Dockerfile and set both secrets. | Uses `zeabur.json`; one service runs both viewers. |
| **Hugging Face Spaces** | Standard Gradio Space; `app_hf.py` downloads and runs both viewers natively. | 16 GB runtime: 1Γ— 9Hits system session + 3Γ— FeelingSurf, with no memory guardian or low-memory tuning. |
Recommended platform sizing per FeelingSurf container (per the [official repo](https://github.com/feelingsurf/docker-viewer)):
* **RAM:** ~2 GB
* **CPU:** ~2 cores
* **tmpfs:** `/tmp` and `/dev/shm`
* **Port:** `3000` (in-viewer HTTP endpoint + Docker healthcheck)
### 4. Why use FeelingSurf alongside 9Hits?
* **Independent earnings / IP reputation:** even if a 9Hits proxy tripwire fires (`Auth: Duplicate USER on IP`), FeelingSurf keeps earning on the same cloud box.
* **Different credit economy:** FeelingSurf awards credits independent of 9Hits, so you can pause one to focus budget on the other without affecting your account elsewhere.
* **Trivial setup:** one env var, no proxy gymnastics.
---
## Proxy Setup (Running Multiple Sessions on 1 Machine)
> ⚠️ **IMPORTANT: The 9Hits public proxy pool is CLOSED.**
> Do not use `EX_PROXY_SESSIONS` without your own custom pool or proxy list.
If you have dedicated or private proxies:
* **Option A β€” Static Bulk Proxy List (`BULK_ADD_PROXY_LIST`)**
```env
BULK_ADD_PROXY_TYPE=socks5
BULK_ADD_PROXY_LIST=1.2.3.4:1080;user;pass|1.2.3.5:1080;user;pass|1.2.3.6:1080;user;pass
```
* **Option A+ β€” Webshare Dynamic Download Link (`BULK_ADD_PROXY_LIST_URL`)**
```env
BULK_ADD_PROXY_LIST_URL=https://proxy.webshare.io/api/v2/proxy/list/download/TOKEN/-/any/username/direct/-/
```
* **Option B β€” Custom Pool (`EX_PROXY_URL`)**
Configure at [dash.9hits.com/pool](https://dash.9hits.com/pool):
```env
EX_PROXY_SESSIONS=5
EX_PROXY_URL=https://dash.9hits.com/pool/YOUR_POOL_KEY
```
---
## Environment Variables Reference
Viewer config flags are applied by the **init pass** (`nhviewer <flags> --exit-on-init`); the **run pass** then starts with `--auto-start --in-loop --render-to-terminal [--reset-interval=...]` β€” the same flow as the [official 9Hits v6 installer](https://github.com/9hitste/install).
| Env var | `nhviewer` flag | Default | Description |
| :--- | :--- | :--- | :--- |
| `NINEHITS_ENABLED` | β€” | `no` | `yes`/`no`/`1`/`0`/`true`/`false`/`on`/`off` β€” run the 9Hits viewer. Off by default for conservative bare deploys; the Render/Koyeb blueprints set `yes` because the 512 MB stack below keeps both viewers alive |
| `FEELINGSURF_ENABLED` | β€” | `yes` | `yes`/`no`/`1`/`0`/`true`/`false`/`on`/`off` β€” run the FeelingSurf viewer (auto-disables quietly when the binary is absent, e.g. the HF Gradio runtime) |
| `DUAL_VIEWER_MODE` | β€” | `auto` | `auto` / `concurrent` / `time-slice` / `off` β€” how the two viewers share one small box (see [Running both viewers on 512 MB](#running-both-viewers-on-512-mb-render-free)); `concurrent` + `LOW_MEMORY=extreme` = both at the same time in 512 MB |
| `TIME_SLICE` | β€” | `1500` | Seconds each viewer runs per turn in `time-slice` mode (25 min default) |
| `LOW_MEMORY` | β€” | `auto` | `auto` (flags on when box < 1 GB) / `off` / `balanced` / `extreme` (`--single-process` for both viewers, measured ~324 MB pair β€” lets both run concurrently in 512 MB; crash auto-fallback included) β€” Chromium memory-shrinking flags applied to both viewers |
| `NH_SP` | β€” | `yes` | Single-process for 9Hits in `extreme` mode (auto-disabled after 3 startup crashes) |
| `FS_SP` | β€” | `yes` | Single-process for FeelingSurf in `extreme` mode (auto-disabled after 3 startup crashes) |
| `FS_GL_MODE` | β€” | `swiftshader` | FeelingSurf GL: `swiftshader` (upstream) or `disable-gpu` (last resort if FS crash-loops under single-process) |
| `FS_SHARE_DISPLAY` | β€” | `yes` | Reuse the 9Hits Xvfb display for FeelingSurf (one less X server on tight instances) |
| `MEMGUARD_LIMIT_MB` | β€” | `0` (auto-detect) | Memory budget memguard enforces; set `512` on Render free / Koyeb nano. `0` = cgroup limit, then MemTotal |
| `MEMGUARD_HARD_PCT` | β€” | `97` | % of budget at which memguard restarts the heaviest viewer (97 = act at 497 MB on a 512 MB box, before the platform OOM) |
| `CREATE_SWAP` | β€” | *none* | Best-effort swap size, e.g. `256M` (needs `swapon` permission; auto-tried on < 1 GB boxes) |
| `NH_RUN_EXTRA_ARGS` | β€” | *none* | Raw flags appended to the 9Hits **run pass** (the init pass uses `EXTRA_ARGS`) |
| `FS_EXTRA_FLAGS` | β€” | *none* | Raw flags appended to the FeelingSurf launch (last switch wins) |
| `FS_RESOLUTION` | β€” | `auto` | FeelingSurf Xvfb resolution (`1280x720x24` on small boxes, else `1920x1080x24`) |
| `ACCESS_KEY` | `--access-key` | *required* | From [panel.9hits.com/user/profile](https://panel.9hits.com/user/profile) |
| `SYSTEM_SESSION` | `--system-session` | `no` | `yes`/`no` β€” runs direct session on instance IP |
| `CLEAR_ALL_SESSIONS` | `--clear-all-sessions` | `yes` | Wipes stale sessions on boot |
| `BULK_ADD_PROXY_LIST_URL` | β€” | *none* | URL to download proxy list on boot |
| `BULK_ADD_PROXY_LIST` | `--bulk-add-proxy-list` | *none* | Pipe-delimited proxy list (`ip:port;user;pass\|...`) |
| `BULK_ADD_PROXY_TYPE` | `--bulk-add-proxy-type` | `socks5` | `socks5`, `http`, `socks4`, `ssh` |
| `EX_PROXY_SESSIONS` | `--ex-proxy-sessions` | *none* | Number of pool sessions (requires `EX_PROXY_URL`) |
| `EX_PROXY_URL` | `--ex-proxy-url` | *none* | Pool URL from `dash.9hits.com/pool` |
| `SESSION_NOTE` | `--session-note` | `my-proxies`| Session label in 9Hits panel |
| `NOTE` | `--note` | `render` | Machine label in 9Hits panel |
| `HIDE_BROWSER` | `--hide-browser` | `yes` | Run headless |
| `ALLOW_POPUPS` | `--allow-popups` | `no` | Popups toggle (keep `no` to save RAM/BW) |
| `ALLOW_ADULT` | `--allow-adult` | `no` | Adult campaigns toggle |
| `ALLOW_CRYPTO` | `--allow-crypto` | `no` | Crypto mining campaigns toggle |
| `CACHE_LIMIT` | `--cache-limit` | `0` | Disk cache limit in bytes (`0` = no cache; unset = official 200 MB cap) |
| `HIDE_COLUMNS` | `--hide-columns` | *none* | Dashboard columns to hide, e.g. `quality,points` |
| `RESET_INTERVAL` | `--reset-interval` (run pass) | `2h` | Graceful self-restart interval (`2h`, `6h`, `30m`) |
| `PORT` | β€” | `10000` | Port for `/health` endpoint |
| `SUPERVISOR_DELAY` | β€” | `10` | Seconds before relaunching an exited viewer (alias: `RESTART_DELAY`) |
| `EXTRA_ARGS` | β€” | *none* | Extra raw flags appended to the init pass |
| `DEFAULT_DL` | β€” | *none* | Download a different viewer build from this URL at container start |
| `NH_DISPLAY` | β€” | `:99` | X display number used for the 9Hits Xvfb |
| `NH_RESOLUTION` | β€” | `auto` | Xvfb resolution, e.g. `1920x1080x24` (`auto` = scale with CPU/RAM) |
| `INIT_TIMEOUT` | β€” | `300` | Max seconds for one init pass before it is killed and retried |
| `NH_WATCHDOG` | β€” | `yes` | Restart the viewer when it is wedged (no output **and** no CPU progress) |
| `NH_WATCHDOG_STUCK` | β€” | `600` | Seconds of silence before the wedge watchdog engages |
| `NH_RENDER_TO_TERMINAL` | β€” | `yes` | `no` disables the live dashboard (silent logs; watchdog still works) |
| `PTY_COLS` / `PTY_ROWS` | β€” | `120` / `30` | Terminal size handed to the viewer dashboard |
| `VNC` / `VNC_PW` / `VNC_PORT` / `NO_VNC_PW` | β€” | off / β€” / `5901` / off | Optional x11vnc mirror of the 9Hits display for live viewing |
---
## Health Monitoring & Uptime Bots
Every instance exposes a lightweight status endpoint:
```
GET https://<your-service>/health
```
```json
{
"service": "hits4me-combined-viewer",
"version": "3.0.0",
"status": "ok",
"viewer_enabled": true,
"viewer_running": true,
"supervisor_running": true,
"viewer_pid": 42,
"viewer_phase": "run",
"viewer_silent_seconds": 1,
"xvfb_running": true,
"restarts": 0,
"uptime_seconds": 3600,
"dual_viewer_mode": "auto", // configured DUAL_VIEWER_MODE
"effective_mode": "time-slice", // auto may escalate to time-slice
"active_viewer": "feelingsurf", // who owns the RAM right now
"memory_used_mb": 412.5, // total container RSS (memguard)
"memory_limit_mb": 512,
"memory_peak_mb": 498.1,
"ninehits_rss_mb": 0.0, // per-viewer unique memory, PSS
"feelingsurf_rss_mb": 331.2,
"memguard_interventions": 3 // times the heaviest viewer was restarted
}
```
* `viewer_phase`: `init` = applying your config/sessions, `run` = viewer is up, `down` = between restarts.
* `viewer_silent_seconds`: age of the last dashboard output. A large value together with rising `restarts` means the wedge watchdog is restarting a hung viewer.
* The memory/mode block comes from `memguard.py` and is `null` when memguard is off. `effective_mode: "time-slice"` means the two viewers alternate; `"concurrent"` means both run at once.
Point any free uptime monitor (**UptimeRobot, Better Stack, Cron-job.org, Kuma**) to ping `https://<your-app>/health` every **5 to 10 minutes** to keep free-tier instances active and prevent sleep timeouts.
---
## Troubleshooting
* **Render: `Ran out of memory (used over 512MB)` β†’ "Service recovered" β†’ repeat (~1/min)** β€” the classic symptom of two un-tuned Chromium viewers on the free 512 MB plan. The current Blueprint runs **both viewers** with the 512 MB survival stack (`DUAL_VIEWER_MODE=auto` + `LOW_MEMORY=auto` + `MEMGUARD_LIMIT_MB=512`), which keeps RSS under the limit: memguard restarts the heaviest viewer before the platform can kill the container, and auto mode alternates the two if they still don't fit. Check `/health` β†’ `effective_mode` / `memory_used_mb` / `memguard_interventions`. If you still see OOM (e.g. a proxy list with many sessions), lower the session count, set `FEELINGSURF_ENABLED=no`, or move to a β‰₯ 2 GB plan (9Hits v6's official recommendation).
* **`Auth: Duplicate USER on IP [x.x.x.x]`** β€” Another 9Hits user is already using that public/shared proxy IP. Switch to a system session on a dedicated cloud provider, refresh your Webshare list, or use private proxies.
* **`Auth: Duplicate SESSION on IP [x.x.x.x]`** β€” Multiple sessions from your account on the same IP. Ensure `SYSTEM_SESSION=no` when using proxies, or enable `CLEAR_ALL_SESSIONS=yes` to clear lingering connections.
* **`Pool error: The public pool is closed!`** β€” Set `EX_PROXY_SESSIONS=0` (or unset it) and use `BULK_ADD_PROXY_LIST` / `BULK_ADD_PROXY_LIST_URL`, or provide your own custom pool via `EX_PROXY_URL`.
* **`User not found!`** β€” `ACCESS_KEY` is incorrect or missing.
* **Logs stop right after deploy / viewer never appears (the Aug-2026 upstream change)** β€” the renewed `9hitste/appv6` image used to extract a ~145 MB bzip2 viewer tarball at every container start through its own `/nh.sh`, stalling for many minutes on free-tier CPUs and then hanging silently. This repo no longer does that: the viewer is extracted at **image build time** and started via the official two-pass flow with our own supervised **Xvfb :99**. If you still see stalls, check `/health` β€” `viewer_phase` (`init`/`run`/`down`) and `viewer_silent_seconds` tell you exactly where it is.
* **`WATCHDOG: no output ... no CPU progress` in the logs** β€” the viewer wedged (typically OOM-adjacent on 512 MB instances or a stuck Chromium) and was restarted automatically. If it repeats, lower the session count, set `FEELINGSURF_ENABLED=no`, or move to a bigger instance (v6 recommends β‰₯ 2 GB RAM).
* **Init pass keeps failing** (`init pass failed/timed out`) β€” the 9Hits API was unreachable or very slow; the supervisor retries 3Γ— with backoff and then launches anyway (the next restart re-applies config). Increase `INIT_TIMEOUT` on very slow networks.
* **`/dev/shm` is only 64 MB** β€” free Docker tiers can't set `--shm-size`. The entrypoint tries a best-effort remount; where you control Docker yourself (oracle/compose), keep `shm_size: 2g` (already in `docker-compose.yml`).
* **VNC: watch the viewer live** β€” set `VNC=yes` + `VNC_PW=<pass>` and connect to port `5901` (only on hosts that expose it; Render web services only expose `$PORT`).