Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
## Unreleased

### English
- **Screen on the board (kiosk):** new option in Settings → Farm (`KIOSK=1`). On the images (any systemd + apt system) the dashboard installs cage + Chromium the first time, in the background, and opens `?kiosk` full screen on the HDMI screen at every boot, with no desktop. New service `mineracao-kiosk`, created off by `install.sh`. The option is hidden where it can't work (Docker), and the settings show when it is installing, when it failed or when no screen is plugged in.
- **New theme 🟡 Black & Yellow:** near-black background with exchange-style yellow, green for gains and red for alerts, in Settings → Farm → Visual theme (`THEME=gold`). Applies to the dashboard, kiosk mode and the admin screen.
- **Worker details (last 24 h):** click a worker in the table to open a dialog with its last 24 hours through the proxy: accepted and rejected shares, best share and average hashrate, a chart of shares per hour (rejections in red), a chart of the best difficulty per hour (log scale, so the ESP32 fit too), the 10 best hashes with their time and the latest shares. The proxy keeps this per hour in its stats file (survives restarts) and serves it at `/stats/worker?name=`; the dashboard at `/api/worker?name=`. Not in kiosk mode.
- **Two new achievements:** 🌋 **Trillion hash** (a share with difficulty over 1 trillion) and the 🏆 **Golden prize: block found!**, unlocked when a share passes the network difficulty. The golden prize sits on its own full-width row, shows its trophy even while locked and lights up in gold when won.

### Português
- **Tela ligada na placa (quiosque):** opção nova em Configurações → Fazenda (`KIOSK=1`). Nas imagens (qualquer sistema com systemd e apt), o painel instala o cage e o Chromium na primeira vez, em segundo plano, e abre o `?kiosk` em tela cheia na tela do HDMI em todo boot, sem área de trabalho. Serviço novo `mineracao-kiosk`, criado desligado pelo `install.sh`. A opção some onde não funciona (Docker), e as configurações mostram quando está instalando, quando deu erro ou quando não há tela ligada.
- **Tema novo 🟡 Amarelo e Preto:** fundo quase preto com o amarelo de exchange, verde para ganhos e vermelho para alertas, em Configurações → Fazenda → Tema visual (`THEME=gold`). Vale no painel, no modo quiosque e na tela de administração.
- **Detalhes do worker (últimas 24 h):** clique num worker da tabela para abrir um modal com as últimas 24 horas dele pelo proxy: shares aceitos e recusados, melhor share e hashrate médio, gráfico de shares por hora (recusados em vermelho), gráfico da melhor dificuldade por hora (escala logarítmica, para os ESP32 caberem), os 10 melhores hashes com a hora e os últimos shares. O proxy guarda isso por hora no arquivo de estatísticas (sobrevive ao reinício) e entrega em `/stats/worker?name=`; o painel, em `/api/worker?name=`. Não abre no modo quiosque.
- **Duas conquistas novas:** 🌋 **Um trilhão** (um share com dificuldade acima de 1 trilhão) e o 🏆 **Prêmio de ouro: bloco achado!**, que desbloqueia quando um share passa da dificuldade da rede. O prêmio de ouro fica numa linha inteira só para ele, mostra o troféu mesmo trancado e acende em dourado quando sai.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Bitaxe / NerdMiner / cpuminer ──► proxy (port 3333, 3334, 3335…) ──
- **Almost a block:** your best hash vs. the latest block hash, leading zeros side by side. The 🎟️ button turns today's best hash into a lottery-ticket image ("NOT A WINNER", until the day it is) to share or save.
- **Luck:** each worker's best share compared with what its work should statistically produce (1× is normal, 2× is twice as lucky), a histogram of all share difficulties against the theoretical curve, the **Weekly Championship** (ranked by relative luck so an ESP32/devkit can beat an ASIC), and **Farm Achievements**.

For a screen attached to the server, use `?kiosk`: screens rotate on their own and the cursor is hidden.
For a screen attached to the server, use `?kiosk`: screens rotate on their own and the cursor is hidden. On the ready-made images (or any Debian/Armbian install with systemd), ⚙️ Settings → Farm → **Screen on this board (kiosk)** (`KIOSK=1`) does it for you: the first time it installs a minimal full-screen browser (cage + Chromium, about 300 MB, a few minutes online) and from then on opens `?kiosk` on the HDMI screen at every boot, in place of the text login (Ctrl+Alt+F2 still gives you a console). Turning it off gives the screen back.
- **Optional extras:**
- **Telegram bot:** alerts on your phone for farm records, stopped workers, pool failover and money arriving in the wallet (a found block!), a daily summary, and commands such as `/status` and `/luck`. See [Telegram bot](#telegram-bot-optional).
- **Siren:** an Arduino with LEDs and a speaker, see `extras/sirene`.
Expand Down
2 changes: 1 addition & 1 deletion README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Bitaxe / NerdMiner / cpuminer ──► proxy (porta 3333, 3334, 3335…) ──
- **Quase bloco:** nosso melhor hash × hash do último bloco, com os zeros lado a lado. O botão 🎟️ transforma o melhor hash do dia num bilhete de loteria ("NÃO PREMIADO", até o dia em que for) para compartilhar ou salvar.
- **Sorte:** o melhor share de cada worker comparado com o que o trabalho dele deveria render pela estatística (1× é o normal, 2× é o dobro de sorte), um histograma das dificuldades de todos os shares contra a curva teórica, o **Campeonato da semana** (ordenado por sorte relativa para o vovô devkit disputar de igual para igual com um ASIC) e as **Conquistas da fazenda**.

Para uma tela ligada ao servidor, use `?kiosk`: as telas alternam sozinhas e o cursor some.
Para uma tela ligada ao servidor, use `?kiosk`: as telas alternam sozinhas e o cursor some. Nas imagens prontas (ou em qualquer Debian/Armbian com systemd), ⚙️ Configurações → Fazenda → **Tela ligada nesta placa (quiosque)** (`KIOSK=1`) faz isso sozinho: na primeira vez instala um navegador mínimo em tela cheia (cage + Chromium, uns 300 MB, alguns minutos com internet) e daí em diante abre o `?kiosk` na tela do HDMI em todo boot, no lugar do login de texto (Ctrl+Alt+F2 ainda dá um terminal). Desligar devolve a tela.
- **Extras opcionais:**
- **Bot do Telegram:** avisos no celular de recorde da fazenda, worker parado, failover de pool e dinheiro entrando na carteira (um bloco achado!), resumo diário e comandos como `/status` e `/sorte`. Veja [Bot do Telegram](#bot-do-telegram-opcional).
- **Sirene:** Arduino com LEDs e alto-falante, ver `extras/sirene`.
Expand Down
6 changes: 6 additions & 0 deletions config/mineracao.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,9 @@ PORT=3000

# Hide the "Support the project" (donations) item in the dashboard menu
#SHOW_DONATE=0

# Screen plugged into this board's HDMI: open the dashboard full screen on boot (kiosk, no desktop).
# The first time, the dashboard installs cage + Chromium with apt (about 300 MB). Also in Settings → Farm.
#KIOSK=1
# Extra Chromium options for the kiosk, e.g. if the screen flickers or stays black:
#KIOSK_FLAGS=--disable-gpu
34 changes: 33 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ NODE_MAJOR=$("$NODE_BIN" -p 'process.versions.node.split(".")[0]')

m "==> Copiando os arquivos para $APP_DIR" "==> Copying files to $APP_DIR"
mkdir -p "$APP_DIR"
cp "$HERE"/src/painel.js "$HERE"/src/proxy.js "$HERE"/src/stratum.js "$HERE"/src/i18n.js "$HERE"/src/luck.js "$HERE"/src/telegram.js "$HERE"/src/mqtt.js "$HERE"/src/tuya.js "$HERE"/src/sonoff.js "$HERE"/src/settings.js "$HERE"/src/setup.js "$HERE"/src/update.js "$HERE"/src/localminer.js "$HERE"/src/netctl.js "$HERE"/src/clock.js "$HERE"/src/bitaxe.js "$HERE"/src/store.js "$HERE"/src/discover.js "$HERE"/src/hotspot.js "$HERE"/src/cubo.py "$HERE"/src/ticket.py "$APP_DIR/"
cp "$HERE"/src/painel.js "$HERE"/src/proxy.js "$HERE"/src/stratum.js "$HERE"/src/i18n.js "$HERE"/src/luck.js "$HERE"/src/telegram.js "$HERE"/src/mqtt.js "$HERE"/src/tuya.js "$HERE"/src/sonoff.js "$HERE"/src/settings.js "$HERE"/src/setup.js "$HERE"/src/update.js "$HERE"/src/localminer.js "$HERE"/src/netctl.js "$HERE"/src/clock.js "$HERE"/src/bitaxe.js "$HERE"/src/store.js "$HERE"/src/discover.js "$HERE"/src/hotspot.js "$HERE"/src/kiosk.js "$HERE"/src/kiosk.sh "$HERE"/src/cubo.py "$HERE"/src/ticket.py "$APP_DIR/"

m "==> Configuração em $ENV_FILE" "==> Configuration in $ENV_FILE"
if [ ! -f "$ENV_FILE" ]; then
Expand Down Expand Up @@ -110,6 +110,38 @@ if command -v nmcli >/dev/null; then
services="$services mineracao-hotspot"
fi

# Tela ligada na placa (opção KIOSK): serviço criado desligado. Quem liga é o painel, que instala o cage e o
# Chromium pelo apt na primeira vez. Abre o painel em tela cheia na tela 1 (tty1) no lugar do login de texto.
chmod 755 "$APP_DIR/kiosk.sh"
cat > /etc/systemd/system/mineracao-kiosk.service <<UNIT
[Unit]
Description=HomeMinerProxy dashboard on the board's screen (kiosk, managed by the dashboard)
After=mineracao-painel.service systemd-user-sessions.service plymouth-quit-wait.service
Wants=mineracao-painel.service
Conflicts=getty@tty1.service
After=getty@tty1.service

[Service]
EnvironmentFile=$ENV_FILE
Environment=XDG_RUNTIME_DIR=/run/user/0
Environment=WLR_LIBINPUT_NO_DEVICES=1
ExecStart=/bin/sh $APP_DIR/kiosk.sh
User=root
PAMName=login
TTYPath=/dev/tty1
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes
StandardInput=tty-fail
StandardOutput=journal
UtmpIdentifier=tty1
Restart=always
RestartSec=5

[Install]
WantedBy=graphical.target multi-user.target
UNIT

# Minerador local opcional: com o cpuminer instalado (imagens prontas), cria o serviço desligado.
# Quem liga e desliga é o painel, pela opção "Minerar também com este servidor".
MINERD_BIN=$(command -v minerd || true)
Expand Down
87 changes: 87 additions & 0 deletions src/kiosk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Tela ligada na própria placa (opção KIOSK): abre o painel em tela cheia no boot, sem área de trabalho.
// Usa o cage (um "quiosque" Wayland mínimo) com o Chromium apontado para http://127.0.0.1:PORTA/?kiosk.
// O install.sh cria o serviço mineracao-kiosk desligado (onde há systemd). Ao ligar a opção, o painel
// instala o cage e o Chromium pelo apt na primeira vez (alguns minutos, em segundo plano) e liga o serviço;
// ao desligar, desliga o serviço e a placa volta a ser só servidor. Nada disso existe no Docker.

const fs = require('fs');
const path = require('path');
const { execFile, spawn } = require('child_process');

const UNIT = 'mineracao-kiosk';
const UNIT_FILE = () => process.env.KIOSK_UNIT_FILE || `/etc/systemd/system/${UNIT}.service`;
const PACKAGES = ['cage', 'chromium', 'fonts-noto-color-emoji'];
const CAGE = ['/usr/bin/cage', '/usr/local/bin/cage'];
const CHROMIUM = ['/usr/bin/chromium', '/usr/bin/chromium-browser'];
const which = (list) => list.find((b) => fs.existsSync(b)) || null;

// Dá para ligar por aqui? Precisa do serviço criado pelo install.sh (ou seja, systemd) e do apt para instalar.
function available() {
return fs.existsSync(UNIT_FILE()) && (installed() || fs.existsSync(process.env.KIOSK_APT || '/usr/bin/apt-get'));
}
const installed = () => !!which(CAGE) && !!which(CHROMIUM);

// Tela ligada? O kernel diz em /sys/class/drm/card*-*/status ("connected"). Sem a pasta, não dá para saber.
function screen(dir = process.env.KIOSK_DRM_DIR || '/sys/class/drm') {
let names;
try { names = fs.readdirSync(dir); } catch (_) { return null; }
const outs = names.filter((n) => /^card\d+-/.test(n));
if (!outs.length) return null;
return outs.some((n) => { try { return fs.readFileSync(path.join(dir, n, 'status'), 'utf8').trim() === 'connected'; } catch (_) { return false; } });
}

function run(cmd, args, timeout = 20000) {
return new Promise((resolve) => {
execFile(cmd, args, { timeout }, (err, out, errOut) => resolve({ ok: !err, out: String(out || '') + String(errOut || '') }));
});
}
const systemctl = (args) => run(process.env.KIOSK_SYSTEMCTL || 'systemctl', args);

// Estado da instalação em segundo plano (para a tela de configurações mostrar)
const stateFile = (dataDir) => path.join(dataDir, 'kiosk.json');
function readState(dataDir) { try { return JSON.parse(fs.readFileSync(stateFile(dataDir), 'utf8')); } catch (_) { return {}; } }
function writeState(dataDir, s) { try { fs.writeFileSync(stateFile(dataDir), JSON.stringify(s)); } catch (_) { /* segue */ } }

// Instala os pacotes sem travar o painel; quando terminar, liga o serviço. Um log fica em DATA_DIR/kiosk-install.log.
function installThenStart(dataDir) {
const st = readState(dataDir);
if (st.installing && Date.now() - st.installing < 30 * 60e3) return Promise.resolve({ installing: true });
writeState(dataDir, { installing: Date.now(), error: null });
const log = fs.openSync(path.join(dataDir, 'kiosk-install.log'), 'w');
const apt = process.env.KIOSK_APT || 'apt-get';
const sh = `${apt} update -q && DEBIAN_FRONTEND=noninteractive ${apt} install -y -q --no-install-recommends ${PACKAGES.join(' ')}`;
return new Promise((resolve) => {
const child = spawn('/bin/sh', ['-c', sh], { stdio: ['ignore', log, log] });
child.on('error', (e) => { writeState(dataDir, { installing: null, error: e.message }); resolve({ ok: false }); });
child.on('close', async (code) => {
fs.closeSync(log);
if (code !== 0 || !installed()) { writeState(dataDir, { installing: null, error: 'apt ' + code }); return resolve({ ok: false }); }
writeState(dataDir, { installing: null, error: null, installedAt: Date.now() });
await systemctl(['enable', '--now', UNIT]);
resolve({ ok: true });
});
});
}

// Aplica a opção ao subir o painel
async function sync({ enabled, dataDir }) {
if (!fs.existsSync(UNIT_FILE())) return { ok: false, reason: 'nounit' };
if (enabled) {
if (!installed()) { installThenStart(dataDir); return { ok: true, installing: true }; }
await systemctl(['enable', UNIT]);
const active = await systemctl(['is-active', '--quiet', UNIT]);
if (!active.ok) await systemctl(['start', UNIT]);
return { ok: true };
}
const active = await systemctl(['is-active', '--quiet', UNIT]);
const en = await systemctl(['is-enabled', '--quiet', UNIT]);
if (active.ok || en.ok) await systemctl(['disable', '--now', UNIT]);
return { ok: true, off: true };
}

function status(dataDir) {
const st = readState(dataDir);
return { available: available(), installed: installed(), screen: screen(), installing: !!(st.installing && Date.now() - st.installing < 30 * 60e3), error: st.error || null };
}

module.exports = { UNIT, PACKAGES, available, installed, screen, sync, status };
21 changes: 21 additions & 0 deletions src/kiosk.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh
# Tela ligada na placa (opção KIOSK, serviço mineracao-kiosk): o painel em tela cheia, sem área de trabalho.
# KIOSK_FLAGS (no .env) acrescenta opções ao Chromium, ex.: KIOSK_FLAGS=--disable-gpu se a tela piscar ou ficar preta.
# Espera o painel responder, depois abre o Chromium dentro do cage (quiosque Wayland mínimo).
PORT=${PORT:-3000}
URL="http://127.0.0.1:${PORT}/?kiosk"
i=0
while [ $i -lt 90 ]; do
if command -v node >/dev/null && node -e "require('http').get('http://127.0.0.1:${PORT}/',r=>process.exit(r.statusCode<500?0:1)).on('error',()=>process.exit(1))" 2>/dev/null; then break; fi
i=$((i + 1)); sleep 2
done
CHROME=$(command -v chromium || command -v chromium-browser)
PROFILE=${KIOSK_PROFILE:-/var/lib/mineracao-kiosk}
mkdir -p "$PROFILE"
# perfil limpo a cada boot evita o aviso "o Chromium não foi fechado corretamente"
rm -f "$PROFILE/SingletonLock" "$PROFILE/SingletonSocket" "$PROFILE/SingletonCookie"
sed -i 's/"exited_cleanly":false/"exited_cleanly":true/; s/"exit_type":"Crashed"/"exit_type":"Normal"/' "$PROFILE/Default/Preferences" 2>/dev/null || true
exec cage -s -- "$CHROME" --no-sandbox --kiosk --noerrdialogs --disable-infobars --no-first-run \
--password-store=basic --disable-features=Translate,MediaRouter --check-for-update-interval=31536000 \
--autoplay-policy=no-user-gesture-required --disable-pinch --overscroll-history-navigation=0 \
--ozone-platform=wayland --user-data-dir="$PROFILE" ${KIOSK_FLAGS:-} "$URL"
2 changes: 2 additions & 0 deletions src/painel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2875,6 +2875,8 @@ function start(port = PORT, host = '0.0.0.0') {
enabled: process.env.LOCAL_MINER === '1', address: ADDRESS, worker: process.env.LOCAL_WORKER,
threads: process.env.MINER_THREADS, appDir: process.env.DATA_DIR || __dirname,
}).then((w) => { if (w) LOCAL_WORKER = w; }).catch(() => {});
// tela ligada na placa (opção KIOSK): instala o navegador na primeira vez e liga/desliga o serviço
require('./kiosk').sync({ enabled: process.env.KIOSK === '1', dataDir: process.env.DATA_DIR || __dirname }).catch(() => {});
const tgBot = startTelegram();
const mqttClient = startMqtt();
resolve({
Expand Down
Loading
Loading