Update
This commit is contained in:
143
installer
143
installer
@ -4,11 +4,11 @@
|
|||||||
# An Open Source Linux Appliance from Robbie Ferguson
|
# An Open Source Linux Appliance from Robbie Ferguson
|
||||||
# (c) 2025 Robbie Ferguson
|
# (c) 2025 Robbie Ferguson
|
||||||
|
|
||||||
# Version 1.0.2
|
# Version 1.0.3
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
BTC_VER="30.0"
|
BTC_VER="29.2"
|
||||||
|
|
||||||
APP_ROOT="/opt/btc-solo"
|
APP_ROOT="/opt/btc-solo"
|
||||||
WWW_ROOT="/var/www/btc-solo"
|
WWW_ROOT="/var/www/btc-solo"
|
||||||
@ -109,7 +109,7 @@ curl -fsSL "$BASE_URL/$TARBALL" -o "$TMPD/$TARBALL"
|
|||||||
curl -fsSL "$BASE_URL/SHA256SUMS" -o "$TMPD/SHA256SUMS"
|
curl -fsSL "$BASE_URL/SHA256SUMS" -o "$TMPD/SHA256SUMS"
|
||||||
if command -v gpg >/dev/null 2>&1; then
|
if command -v gpg >/dev/null 2>&1; then
|
||||||
curl -fsSL "$BASE_URL/SHA256SUMS.asc" -o "$TMPD/SHA256SUMS.asc" || true
|
curl -fsSL "$BASE_URL/SHA256SUMS.asc" -o "$TMPD/SHA256SUMS.asc" || true
|
||||||
# If you’ve already imported the Bitcoin Core release keys, this will verify.
|
# If you've already imported the Bitcoin Core release keys, this will verify.
|
||||||
# If not, we still proceed after SHA256 check below.
|
# If not, we still proceed after SHA256 check below.
|
||||||
gpg --verify "$TMPD/SHA256SUMS.asc" "$TMPD/SHA256SUMS" || echo "[WARN] Could not verify SHA256SUMS signature (no keys?). Continuing with SHA256 check."
|
gpg --verify "$TMPD/SHA256SUMS.asc" "$TMPD/SHA256SUMS" || echo "[WARN] Could not verify SHA256SUMS signature (no keys?). Continuing with SHA256 check."
|
||||||
fi
|
fi
|
||||||
@ -1166,7 +1166,7 @@ function drawLineGraph(containerId, points, opts = {}) {
|
|||||||
<span class="graph-legend-label">Range</span>
|
<span class="graph-legend-label">Range</span>
|
||||||
<span class="graph-legend-value">${
|
<span class="graph-legend-value">${
|
||||||
isFinite(minVal) && isFinite(maxVal)
|
isFinite(minVal) && isFinite(maxVal)
|
||||||
? minVal.toFixed(2) + '–' + maxVal.toFixed(2) + ' ' + unit
|
? minVal.toFixed(2) + '-' + maxVal.toFixed(2) + ' ' + unit
|
||||||
: 'n/a'
|
: 'n/a'
|
||||||
}</span>
|
}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -1425,7 +1425,7 @@ async function loadStatus() {
|
|||||||
const rawIbd = data.initialblockdownload;
|
const rawIbd = data.initialblockdownload;
|
||||||
const hasRealIbd = (typeof rawIbd === 'boolean');
|
const hasRealIbd = (typeof rawIbd === 'boolean');
|
||||||
const ibd = hasRealIbd ? rawIbd : true;
|
const ibd = hasRealIbd ? rawIbd : true;
|
||||||
const blocks = data.blocks ?? '–';
|
const blocks = data.blocks ?? '-';
|
||||||
const pruned = data.pruned ? '(pruned)' : '';
|
const pruned = data.pruned ? '(pruned)' : '';
|
||||||
let progress = 0;
|
let progress = 0;
|
||||||
if (typeof data.verificationprogress === 'number') {
|
if (typeof data.verificationprogress === 'number') {
|
||||||
@ -1591,7 +1591,7 @@ function parseHashrate(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatHashrateHps(value) {
|
function formatHashrateHps(value) {
|
||||||
if (value == null || !isFinite(value) || value <= 0) return '–';
|
if (value == null || !isFinite(value) || value <= 0) return '-';
|
||||||
|
|
||||||
let v = value;
|
let v = value;
|
||||||
let unit = 'H/s';
|
let unit = 'H/s';
|
||||||
@ -1627,9 +1627,9 @@ const HIDE_SEC = 3 * 24 * 3600; // 3 days -> "inactive" list
|
|||||||
const PURGE_SEC = 30 * 24 * 3600; // 30 days -> purge from UI
|
const PURGE_SEC = 30 * 24 * 3600; // 30 days -> purge from UI
|
||||||
|
|
||||||
function formatShare(value) {
|
function formatShare(value) {
|
||||||
if (value == null) return '–';
|
if (value == null) return '-';
|
||||||
const num = typeof value === 'number' ? value : parseFloat(value);
|
const num = typeof value === 'number' ? value : parseFloat(value);
|
||||||
if (!isFinite(num)) return '–';
|
if (!isFinite(num)) return '-';
|
||||||
const units = ['', 'k', 'M', 'G', 'T', 'P'];
|
const units = ['', 'k', 'M', 'G', 'T', 'P'];
|
||||||
let v = num;
|
let v = num;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
@ -1675,13 +1675,13 @@ function buildWorkerDetailHtml(worker, animate, graphId) {
|
|||||||
detail.push('<div>Last share: ' + lastRel + ' (' + lastAbs + ')</div>');
|
detail.push('<div>Last share: ' + lastRel + ' (' + lastAbs + ')</div>');
|
||||||
detail.push('<div style="margin-top:.25rem;">Hashrate (averages):</div>');
|
detail.push('<div style="margin-top:.25rem;">Hashrate (averages):</div>');
|
||||||
detail.push('<ul style="margin:.2rem 0 .3rem .9rem;font-size:.84rem;">' +
|
detail.push('<ul style="margin:.2rem 0 .3rem .9rem;font-size:.84rem;">' +
|
||||||
'<li>1 minute: ' + (worker.hashrate1m || '–') + '</li>' +
|
'<li>1 minute: ' + (worker.hashrate1m || '-') + '</li>' +
|
||||||
'<li>5 minutes: ' + (worker.hashrate5m || '–') + '</li>' +
|
'<li>5 minutes: ' + (worker.hashrate5m || '-') + '</li>' +
|
||||||
'<li>1 hour: ' + (worker.hashrate1hr || '–') + '</li>' +
|
'<li>1 hour: ' + (worker.hashrate1hr || '-') + '</li>' +
|
||||||
'<li>1 day: ' + (worker.hashrate1d || '–') + '</li>' +
|
'<li>1 day: ' + (worker.hashrate1d || '-') + '</li>' +
|
||||||
'<li>7 days: ' + (worker.hashrate7d || '–') + '</li>' +
|
'<li>7 days: ' + (worker.hashrate7d || '-') + '</li>' +
|
||||||
'</ul>');
|
'</ul>');
|
||||||
detail.push('<div>Shares: ' + (worker.shares ?? '–') +
|
detail.push('<div>Shares: ' + (worker.shares ?? '-') +
|
||||||
' • Best share: ' + formatShare(worker.bestshare) +
|
' • Best share: ' + formatShare(worker.bestshare) +
|
||||||
' • Best ever: ' + formatShare(worker.bestever) + '</div>');
|
' • Best ever: ' + formatShare(worker.bestever) + '</div>');
|
||||||
|
|
||||||
@ -1816,17 +1816,17 @@ async function loadPool() {
|
|||||||
|
|
||||||
const acceptedText = shares.accepted != null
|
const acceptedText = shares.accepted != null
|
||||||
? acceptedNum.toLocaleString()
|
? acceptedNum.toLocaleString()
|
||||||
: '–';
|
: '-';
|
||||||
const rejectedText = shares.rejected != null
|
const rejectedText = shares.rejected != null
|
||||||
? rejectedNum.toLocaleString()
|
? rejectedNum.toLocaleString()
|
||||||
: '–';
|
: '-';
|
||||||
|
|
||||||
let rejectPct = null;
|
let rejectPct = null;
|
||||||
if (totalShares > 0 && rejectedNum >= 0) {
|
if (totalShares > 0 && rejectedNum >= 0) {
|
||||||
rejectPct = ((rejectedNum / totalShares) * 100).toFixed(2);
|
rejectPct = ((rejectedNum / totalShares) * 100).toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bestHashText = bestPoolH1m ? formatHashrateHps(bestPoolH1m) : '–';
|
const bestHashText = bestPoolH1m ? formatHashrateHps(bestPoolH1m) : '-';
|
||||||
|
|
||||||
summaryHtml += '<div class="pool-metrics-label" style="margin-top:1rem;">SHARES</div>';
|
summaryHtml += '<div class="pool-metrics-label" style="margin-top:1rem;">SHARES</div>';
|
||||||
summaryHtml += '<div class="pool-shares-line">';
|
summaryHtml += '<div class="pool-shares-line">';
|
||||||
@ -1932,12 +1932,12 @@ async function loadPool() {
|
|||||||
tableHtml += '<tr class="' + rowClass + openClass + '" data-worker="' +
|
tableHtml += '<tr class="' + rowClass + openClass + '" data-worker="' +
|
||||||
encodeURIComponent(w.workername) + '">' +
|
encodeURIComponent(w.workername) + '">' +
|
||||||
'<td>' + displayName + '</td>' +
|
'<td>' + displayName + '</td>' +
|
||||||
'<td>' + (w.hashrate1m || '–') + '</td>' +
|
'<td>' + (w.hashrate1m || '-') + '</td>' +
|
||||||
'<td>' + (w.hashrate5m || '–') + '</td>' +
|
'<td>' + (w.hashrate5m || '-') + '</td>' +
|
||||||
'<td>' + (w.hashrate1hr || '–') + '</td>' +
|
'<td>' + (w.hashrate1hr || '-') + '</td>' +
|
||||||
'<td>' + (w.hashrate1d || '–') + '</td>' +
|
'<td>' + (w.hashrate1d || '-') + '</td>' +
|
||||||
'<td>' + (w.hashrate7d || '–') + '</td>' +
|
'<td>' + (w.hashrate7d || '-') + '</td>' +
|
||||||
'<td>' + (w.shares ?? '–') + '</td>' +
|
'<td>' + (w.shares ?? '-') + '</td>' +
|
||||||
'<td>' + formatShare(w.bestshare) + '</td>' +
|
'<td>' + formatShare(w.bestshare) + '</td>' +
|
||||||
'<td>' + lastCol + '</td>' +
|
'<td>' + lastCol + '</td>' +
|
||||||
'</tr>';
|
'</tr>';
|
||||||
@ -2349,7 +2349,7 @@ if (isset($_GET['addr'])) {
|
|||||||
$addr = trim($_GET['addr']);
|
$addr = trim($_GET['addr']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// light sanity check (not strict – CKPool does the real validation)
|
// light sanity check (not strict - CKPool does the real validation)
|
||||||
if ($addr !== '' && !preg_match('/^[13bc][a-km-zA-HJ-NP-Z1-9]{25,}$/', $addr)) {
|
if ($addr !== '' && !preg_match('/^[13bc][a-km-zA-HJ-NP-Z1-9]{25,}$/', $addr)) {
|
||||||
$addr = '';
|
$addr = '';
|
||||||
}
|
}
|
||||||
@ -2418,7 +2418,7 @@ if ($addr !== '') {
|
|||||||
}
|
}
|
||||||
$resp['worker_count'] = count($resp['workers']);
|
$resp['worker_count'] = count($resp['workers']);
|
||||||
} else {
|
} else {
|
||||||
// Not a fatal error – just "no data yet" for that address
|
// Not a fatal error - just "no data yet" for that address
|
||||||
if ($resp['error'] === null) {
|
if ($resp['error'] === null) {
|
||||||
$resp['error'] = 'No worker stats yet for this address';
|
$resp['error'] = 'No worker stats yet for this address';
|
||||||
}
|
}
|
||||||
@ -2483,7 +2483,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
<form method="post">
|
<form method="post">
|
||||||
<h2>Create New Admin User</h2>
|
<h2>Create New Admin User</h2>
|
||||||
<p><small>
|
<p><small>
|
||||||
This sets the Bitcoin node’s administrative (RPC) credentials used to control the server itself.<br /><br />
|
This sets the Bitcoin node's administrative (RPC) credentials used to control the server itself.<br /><br />
|
||||||
Keep these private — they grant full access to your node.<br /><br />
|
Keep these private — they grant full access to your node.<br /><br />
|
||||||
These credentials are <b>not</b> used by miners to connect; miners only need your Bitcoin address and can use any password.
|
These credentials are <b>not</b> used by miners to connect; miners only need your Bitcoin address and can use any password.
|
||||||
</small></p>
|
</small></p>
|
||||||
@ -2742,10 +2742,10 @@ draw_dashboard() {
|
|||||||
tput cup 13 0; echo -e "Memory: ${mem}\033[K"
|
tput cup 13 0; echo -e "Memory: ${mem}\033[K"
|
||||||
tput cup 14 0; echo -e "Disk (${BTC_DIR}): ${disk}\033[K"
|
tput cup 14 0; echo -e "Disk (${BTC_DIR}): ${disk}\033[K"
|
||||||
|
|
||||||
tput cup 16 0; echo -e "Workers:\033[K"
|
tput cup 16 0; echo -e "Mining:\033[K"
|
||||||
tput cup 17 2; echo -e "${workers}\033[K"
|
tput cup 17 2; echo -e "${workers}\033[K"
|
||||||
|
|
||||||
tput cup 19 0; echo -e "(Appliance console view — use the web UI for full details.)\033[K"
|
# tput cup 19 0; echo -e "(Appliance console view — use the web UI for full details.)\033[K"
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_header
|
draw_header
|
||||||
@ -2768,7 +2768,84 @@ while true; do
|
|||||||
PROGRESS_LINE=""
|
PROGRESS_LINE=""
|
||||||
BAR_LINE="[--------------] 0.0%"
|
BAR_LINE="[--------------] 0.0%"
|
||||||
SERVICES_LINE="Bitcoin: ${BTC_STATE} CKPool: ${CKP_STATE}"
|
SERVICES_LINE="Bitcoin: ${BTC_STATE} CKPool: ${CKP_STATE}"
|
||||||
WORKERS_LINE="No workers detected (point your ASICs at this node)"
|
|
||||||
|
# --- CKPool worker summary (console-safe, no per-worker listing) ---
|
||||||
|
POOL_STATUS="/opt/btc-solo/logs/pool/pool.status"
|
||||||
|
|
||||||
|
# Humanize hashrate (expects H/s as a number; falls back to raw if unknown)
|
||||||
|
human_hashrate() {
|
||||||
|
local v="$1"
|
||||||
|
if [[ -z "$v" || "$v" == "null" ]]; then echo "-"; return; fi
|
||||||
|
if ! [[ "$v" =~ ^[0-9]+([.][0-9]+)?$ ]]; then echo "$v"; return; fi
|
||||||
|
|
||||||
|
awk -v x="$v" 'BEGIN{
|
||||||
|
u[0]="H/s"; u[1]="kH/s"; u[2]="MH/s"; u[3]="GH/s"; u[4]="TH/s"; u[5]="PH/s"; u[6]="EH/s";
|
||||||
|
i=0;
|
||||||
|
while (x>=1000 && i<6) { x/=1000; i++ }
|
||||||
|
if (x<10) printf("%.2f %s", x, u[i]);
|
||||||
|
else if (x<100)printf("%.1f %s", x, u[i]);
|
||||||
|
else printf("%.0f %s", x, u[i]);
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add commas to large integers (e.g. 1000000 -> 1,000,000)
|
||||||
|
fmt_int() {
|
||||||
|
[[ "$1" =~ ^[0-9]+$ ]] && printf "%'d" "$1" || printf "%s" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
WORKERS_LINE="No workers detected (point your ASICs at this node)"
|
||||||
|
else
|
||||||
|
WORKERS_LINE="Install jq for worker stats."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -r "$POOL_STATUS" ]]; then
|
||||||
|
# pool.status is 3 JSON objects, one per line: counts, hashrate, shares
|
||||||
|
# Slurp into array so we can index [0],[1],[2]
|
||||||
|
POOL_JSON="$(jq -s '.' "$POOL_STATUS" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ -n "$POOL_JSON" && "$POOL_JSON" != "null" ]]; then
|
||||||
|
workers="$(jq -r '.[0].Workers // empty' <<<"$POOL_JSON")"
|
||||||
|
idle="$(jq -r '.[0].idle // empty' <<<"$POOL_JSON")"
|
||||||
|
disc="$(jq -r '.[0].disconnected // empty' <<<"$POOL_JSON")"
|
||||||
|
users="$(jq -r '.[0].users // empty' <<<"$POOL_JSON")"
|
||||||
|
|
||||||
|
hr1m="$(jq -r '.[1].hashrate1m // empty' <<<"$POOL_JSON")"
|
||||||
|
hr5m="$(jq -r '.[1].hashrate5m // empty' <<<"$POOL_JSON")"
|
||||||
|
hr15m="$(jq -r '.[1].hashrate15m // empty' <<<"$POOL_JSON")"
|
||||||
|
|
||||||
|
acc="$(jq -r '.[2].accepted // empty' <<<"$POOL_JSON")"
|
||||||
|
rej="$(jq -r '.[2].rejected // empty' <<<"$POOL_JSON")"
|
||||||
|
stale="$(jq -r '.[2].stale // empty' <<<"$POOL_JSON")"
|
||||||
|
dup="$(jq -r '.[2].dup // empty' <<<"$POOL_JSON")"
|
||||||
|
sps="$(jq -r '.[2].SPS1m // empty' <<<"$POOL_JSON")"
|
||||||
|
bestshare="$(jq -r '.[2].bestshare // empty' <<<"$POOL_JSON")"
|
||||||
|
|
||||||
|
if [[ -n "$workers" && "$workers" != "0" ]]; then
|
||||||
|
# Compact worker line (single line, console-safe)
|
||||||
|
# Example: Workers: 3 (idle 1, disc 0) | HR: 1m 1.2 TH/s / 5m 1.1 TH/s / 15m 1.0 TH/s | Shares: acc 120 rej 0 stale 1 dup 0 | SPS 0.03
|
||||||
|
WORKERS_LINE="Workers: ${workers}"
|
||||||
|
[[ -n "$idle" || -n "$disc" ]] && WORKERS_LINE+=" (idle ${idle:-0}, disc ${disc:-0})"
|
||||||
|
WORKERS_LINE+=" " # Extend a little beyond the end of the current text to blank if new output is shorter
|
||||||
|
[[ -n "$users" ]] && WORKERS_LINE+="\n Users: ${users}"
|
||||||
|
WORKERS_LINE+=" " # Extend a little beyond the end of the current text to blank if new output is shorter
|
||||||
|
|
||||||
|
WORKERS_LINE+="\n Hashrate: 1m $(human_hashrate "$hr1m") / 5m $(human_hashrate "$hr5m") / 15m $(human_hashrate "$hr15m")"
|
||||||
|
WORKERS_LINE+=" " # Extend a little beyond the end of the current text to blank if new output is shorter
|
||||||
|
|
||||||
|
# Shares (show only fields that exist; keep it tight)
|
||||||
|
WORKERS_LINE+="\n Shares:"
|
||||||
|
WORKERS_LINE+="\n Accepted: $(fmt_int "${acc:-0}") "
|
||||||
|
[[ -n "$rej" ]] && WORKERS_LINE+="\n Rejected: $(fmt_int "$rej") "
|
||||||
|
[[ -n "$stale" ]] && WORKERS_LINE+="\n Stale: $(fmt_int "$stale") "
|
||||||
|
[[ -n "$dup" ]] && WORKERS_LINE+="\n Duplicate:$(fmt_int "$dup") "
|
||||||
|
[[ -n "$sps" ]] && WORKERS_LINE+="\n Shares Per Second: $sps "
|
||||||
|
[[ -n "$bestshare" ]] && WORKERS_LINE+="\n Best Share: $(fmt_int "$bestshare") "
|
||||||
|
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# --- /CKPool worker summary ---
|
||||||
|
|
||||||
# cpu load
|
# cpu load
|
||||||
if [ -r /proc/loadavg ]; then
|
if [ -r /proc/loadavg ]; then
|
||||||
@ -2812,7 +2889,7 @@ while true; do
|
|||||||
INFO="$LAST_INFO"
|
INFO="$LAST_INFO"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# success – store and reset fail counter
|
# success - store and reset fail counter
|
||||||
LAST_INFO="$INFO"
|
LAST_INFO="$INFO"
|
||||||
CONSEC_FAILS=0
|
CONSEC_FAILS=0
|
||||||
fi
|
fi
|
||||||
@ -2844,12 +2921,12 @@ while true; do
|
|||||||
|
|
||||||
if [ "$IBD" = "true" ]; then
|
if [ "$IBD" = "true" ]; then
|
||||||
if [ "$(echo "$PROG_PCT < 0.1" | bc -l 2>/dev/null || echo 0)" = "1" ]; then
|
if [ "$(echo "$PROG_PCT < 0.1" | bc -l 2>/dev/null || echo 0)" = "1" ]; then
|
||||||
STATUS_LINE="${YEL}⚠ Bitcoin Core is starting up and loading blockchain data…${RST}"
|
STATUS_LINE="${YEL}⚠ Bitcoin Core is starting up and loading blockchain data...${RST}"
|
||||||
else
|
else
|
||||||
STATUS_LINE="${YEL}⚠ Syncing blockchain… ${PROG_PCT}%${RST}"
|
STATUS_LINE="${YEL}⚠ Syncing blockchain... ${PROG_PCT}%${RST}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
STATUS_LINE="${GRN}✔ Bitcoin Core is ready.${RST}"
|
STATUS_LINE="${GRN}✔ Bitcoin Core is in sync.${RST}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PROGRESS_LINE="Blocks: ${BLOCKS} / ${HEADERS} $( [ "$PRUNED" = "true" ] && echo '(pruned)' )"
|
PROGRESS_LINE="Blocks: ${BLOCKS} / ${HEADERS} $( [ "$PRUNED" = "true" ] && echo '(pruned)' )"
|
||||||
|
|||||||
Reference in New Issue
Block a user