Files
bitcoin-ckpool-appliance/installer
2025-11-07 08:57:45 -05:00

591 lines
18 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
#
# Bitcoin Solo Miner Appliance Installer
# An Open Source Appliance from Robbie Ferguson
# (c) 2025 Robbie Ferguson
set -e
APP_ROOT="/opt/btc-solo"
WWW_ROOT="/var/www/btc-solo"
BITCOIN_CONF="/etc/bitcoin/bitcoin.conf"
CERT_DIR="/etc/ssl/localcerts"
CKPOOL_USER="ckpool"
CKPOOL_PORT="3333"
BITCOIN_DATA="/var/lib/bitcoind"
# ---------------------------------------------------------------------------
# PURGE
# ---------------------------------------------------------------------------
if [[ "$1" == "--purge" ]]; then
systemctl stop ckpool 2>/dev/null || true
systemctl stop bitcoind 2>/dev/null || true
rm -f /var/lib/btc-solo/.setup-complete
rm -f /etc/systemd/system/ckpool.service
rm -f /etc/systemd/system/bitcoind.service
rm -f /etc/nginx/sites-enabled/btc-solo
rm -f /etc/nginx/sites-available/btc-solo
rm -f /etc/sudoers.d/btc-solo
rm -rf "$APP_ROOT" "$WWW_ROOT" "$CERT_DIR" "$BITCOIN_DATA"
rm -f /usr/local/sbin/btc-apply-admin.sh /usr/local/sbin/btc-set-prune.sh /usr/local/sbin/btc-make-cert.sh
# Remove bitcoin binaries we installed directly
rm -f /usr/local/bin/bitcoind /usr/local/bin/bitcoin-cli /usr/local/bin/bitcoin-tx /usr/local/bin/bitcoin-util 2>/dev/null || true
systemctl daemon-reload
systemctl restart nginx 2>/dev/null || true
echo "Bitcoin Solo Miner Appliance purged."
exit 0
fi
if [[ $EUID -ne 0 ]]; then
echo "Run as root."
exit 1
fi
echo "[1/8] Updating apt…"
apt-get update
echo "[2/8] Installing base packages…"
apt-get install -y \
curl ca-certificates gnupg \
nginx php-fpm php-cli \
git build-essential autoconf automake libtool pkg-config \
libjansson-dev libevent-dev libcurl4-openssl-dev libssl-dev zlib1g-dev \
openssl
# ---------------------------------------------------------------------------
# [3/8] Install Bitcoin Core from official binaries
# ---------------------------------------------------------------------------
echo "[3/8] Installing official Bitcoin Core binaries…"
BTC_VER="27.0"
BTC_BASE="bitcoin-${BTC_VER}"
BTC_TAR="${BTC_BASE}-x86_64-linux-gnu.tar.gz"
BTC_URL="https://bitcoincore.org/bin/bitcoin-core-${BTC_VER}/${BTC_TAR}"
BTC_SUMS_URL="https://bitcoincore.org/bin/bitcoin-core-${BTC_VER}/SHA256SUMS"
mkdir -p /tmp/bitcoin
cd /tmp/bitcoin
echo "Downloading Bitcoin Core ${BTC_VER}"
curl -LO "${BTC_URL}"
curl -LO "${BTC_SUMS_URL}"
echo "Verifying checksum…"
grep "${BTC_TAR}" SHA256SUMS | sha256sum -c -
echo "Extracting…"
tar -xzf "${BTC_TAR}"
install -m 0755 -o root -g root ${BTC_BASE}/bin/* /usr/local/bin/
# systemd service for bitcoind
cat > /etc/systemd/system/bitcoind.service <<EOF
[Unit]
Description=Bitcoin daemon
After=network.target
[Service]
ExecStart=/usr/local/bin/bitcoind -conf=/etc/bitcoin/bitcoin.conf -datadir=${BITCOIN_DATA}
ExecStop=/usr/local/bin/bitcoin-cli -conf=/etc/bitcoin/bitcoin.conf stop
User=root
Group=root
Type=simple
Restart=on-failure
RuntimeDirectory=bitcoind
[Install]
WantedBy=multi-user.target
EOF
mkdir -p /etc/bitcoin
mkdir -p "${BITCOIN_DATA}"
# temporary admin creds; user will change on first boot
cat > "$BITCOIN_CONF" <<EOF
server=1
txindex=0
rpcuser=user
rpcpassword=pass
rpcallowip=127.0.0.1
rpcbind=127.0.0.1
listen=1
prune=550
daemon=0
EOF
systemctl daemon-reload
systemctl enable bitcoind
systemctl restart bitcoind
echo "[3/8] bitcoind started (pruned=550 MB)."
# ---------------------------------------------------------------------------
# [4/8] Fetch/build ckpool (solo) - official Bitbucket source
# ---------------------------------------------------------------------------
echo "[4/8] Fetching ckpool from Bitbucket…"
mkdir -p "$APP_ROOT"
cd "$APP_ROOT"
CKPOOL_URL="https://bitbucket.org/ckolivas/ckpool/get/master.tar.gz"
CKPOOL_TGZ="ckpool.tar.gz"
# download (Bitbucket requires user agent + redirects)
curl -L -A "btc-solo-installer/1.0" -o "${CKPOOL_TGZ}" "${CKPOOL_URL}"
# sanity check
if ! file "${CKPOOL_TGZ}" | grep -qi "gzip compressed data"; then
echo "ERROR: ckpool download failed or returned HTML instead of tarball."
head -n 40 "${CKPOOL_TGZ}" || true
exit 1
fi
# extract
rm -rf ckpool
mkdir ckpool
tar -xzf "${CKPOOL_TGZ}" --strip-components=1 -C ckpool
cd ckpool
echo "[4/8] Building ckpool…"
./autogen.sh
CFLAGS="-O2" ./configure
make
# ensure the binary exists and is executable
if [[ ! -x "/opt/btc-solo/ckpool/src/ckpool" ]]; then
echo "ERROR: ckpool did not build correctly; /opt/btc-solo/ckpool/ckpool not found or not executable."
exit 1
fi
id -u "$CKPOOL_USER" >/dev/null 2>&1 || useradd -r -s /bin/false "$CKPOOL_USER"
mkdir -p "$APP_ROOT/conf" "$APP_ROOT/logs"
cat > "$APP_ROOT/conf/ckpool.conf" <<EOF
{
"logdir" : "${APP_ROOT}/logs",
"stratum" : {
"port" : ${CKPOOL_PORT},
"bind" : "0.0.0.0"
},
"solomining" : true,
"coinbase-msg" : "Local Solo Mining",
"donaddress" : "1MoGAsK8bRFKjHrpnpFJyqxdqamSPH19dP",
"donrate" : "2"
}
EOF
chown -R "$CKPOOL_USER":"$CKPOOL_USER" "$APP_ROOT"
cat > /etc/systemd/system/ckpool.service <<EOF
[Unit]
Description=ckpool solo mining server
After=network.target bitcoind.service
[Service]
User=${CKPOOL_USER}
Group=${CKPOOL_USER}
WorkingDirectory=${APP_ROOT}/ckpool
ExecStart=${APP_ROOT}/ckpool/src/ckpool -c ${APP_ROOT}/conf/ckpool.conf -B
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable ckpool
systemctl restart ckpool || true
echo "[4/8] ckpool service installed."
# ---------------------------------------------------------------------------
echo "[5/8] Helper scripts (sudo targets)"
# ---------------------------------------------------------------------------
# apply admin creds
cat > /usr/local/sbin/btc-apply-admin.sh <<'EOF'
#!/bin/bash
CONF="/etc/bitcoin/bitcoin.conf"
CKPOOL_CONF="/opt/btc-solo/conf/ckpool.conf"
ADMINUSER="$1"
ADMINPASS="$2"
if [[ -z "$ADMINUSER" || -z "$ADMINPASS" ]]; then
echo "Usage: $0 <adminuser> <adminpass>"
exit 1
fi
# Update bitcoin.conf
sed -i '/^rpcuser=/d' "$CONF"
sed -i '/^rpcpassword=/d' "$CONF"
echo "rpcuser=${ADMINUSER}" >> "$CONF"
echo "rpcpassword=${ADMINPASS}" >> "$CONF"
# Update ckpool config so it can still talk to bitcoind
if [[ -f "$CKPOOL_CONF" ]]; then
sed -i "s/\"user\"[[:space:]]*:[[:space:]]*\"[^\"]*\"/\"user\" : \"${ADMINUSER}\"/" "$CKPOOL_CONF"
sed -i "s/\"pass\"[[:space:]]*:[[:space:]]*\"[^\"]*\"/\"pass\" : \"${ADMINPASS}\"/" "$CKPOOL_CONF"
fi
systemctl restart bitcoind
systemctl restart ckpool
EOF
chmod 700 /usr/local/sbin/btc-apply-admin.sh
# set prune size
cat > /usr/local/sbin/btc-set-prune.sh <<'EOF'
#!/bin/bash
CONF="/etc/bitcoin/bitcoin.conf"
SIZE="$1"
if [[ -z "$SIZE" ]]; then
echo "Usage: $0 <size-in-mb|full>"
exit 1
fi
sed -i '/^prune=/d' "$CONF"
if [[ "$SIZE" != "full" ]]; then
echo "prune=${SIZE}" >> "$CONF"
fi
systemctl restart bitcoind
EOF
chmod 700 /usr/local/sbin/btc-set-prune.sh
# setup donation system
cat > /usr/local/sbin/btc-set-donation.sh <<'EOF'
#!/bin/bash
CONF="/opt/btc-solo/conf/ckpool.conf"
ADDR="$1"
RATE="$2"
if [[ -z "$ADDR" || -z "$RATE" ]]; then
echo "Usage: $0 <donation-address> <donation-rate-percent>"
exit 1
fi
# ensure conf exists
if [[ ! -f "$CONF" ]]; then
echo "ckpool.conf not found at $CONF"
exit 1
fi
# remove old lines
sed -i '/"donaddress"/d' "$CONF"
sed -i '/"donrate"/d' "$CONF"
# add new ones just before the final }
# (very simple appender; good enough for our generated file)
sed -i '$i \ ,"donaddress" : "'"$ADDR"'",\n "donrate" : '"$RATE"'' "$CONF"
systemctl restart ckpool
EOF
chmod 700 /usr/local/sbin/btc-set-donation.sh
# (re)create self-signed cert
cat > /usr/local/sbin/btc-make-cert.sh <<'EOF'
#!/bin/bash
CERTDIR="/etc/ssl/localcerts"
mkdir -p "$CERTDIR"
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout ${CERTDIR}/btc-solo.key \
-out ${CERTDIR}/btc-solo.crt \
-subj "/C=CA/ST=Ontario/L=Barrie/O=BTC-Solo/OU=IT/CN=$(hostname -f)"
chmod 600 ${CERTDIR}/btc-solo.key
systemctl restart nginx
EOF
chmod 700 /usr/local/sbin/btc-make-cert.sh
# ensure sudoers.d exists
mkdir -p /etc/sudoers.d
chmod 750 /etc/sudoers.d
# sudoers
cat > /etc/sudoers.d/btc-solo <<'EOF'
www-data ALL=(root) NOPASSWD: /usr/local/sbin/btc-apply-admin.sh
www-data ALL=(root) NOPASSWD: /usr/local/sbin/btc-set-prune.sh
www-data ALL=(root) NOPASSWD: /usr/local/sbin/btc-make-cert.sh
www-data ALL=(root) NOPASSWD: /usr/local/sbin/btc-set-donation.sh
EOF
chmod 440 /etc/sudoers.d/btc-solo
# ---------------------------------------------------------------------------
echo "[6/8] Web UI (dashboard + activate)"
# ---------------------------------------------------------------------------
# web-owned state dir
mkdir -p /var/lib/btc-solo
chown www-data:www-data /var/lib/btc-solo
# web dashboard
mkdir -p "$WWW_ROOT"
mkdir -p "$WWW_ROOT/activate"
# main dashboard
cat > "$WWW_ROOT/index.php" <<'EOF'
<?php
$flag = '/var/lib/btc-solo/.setup-complete';
if (!file_exists($flag)) {
header('Location: /activate/');
exit;
}
$opts = [
'550' => '550 MB (default, small)',
(2*1024) => '2 GB',
(4*1024) => '4 GB',
(8*1024) => '8 GB',
(16*1024) => '16 GB',
(32*1024) => '32 GB',
(64*1024) => '64 GB',
(100*1024) => '100 GB',
(200*1024) => '200 GB',
(300*1024) => '300 GB',
(400*1024) => '400 GB',
'full' => 'Full (no pruning)',
];
$current = '550';
$conf = @file_get_contents('/etc/bitcoin/bitcoin.conf');
if ($conf && preg_match('/^prune=(\d+)/m', $conf, $m)) {
$current = $m[1];
} elseif ($conf && !preg_match('/^prune=/m', $conf)) {
$current = 'full';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['prune'])) {
$sel = $_POST['prune'];
if (isset($opts[$sel])) {
shell_exec('sudo /usr/local/sbin/btc-set-prune.sh ' . escapeshellarg($sel));
$current = $sel;
sleep(1);
}
}
$host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_ADDR'] ?? gethostname();
$host = preg_replace('/:\d+$/', '', $host); // strip port if present
$stratum = 'stratum+tcp://' . $host . ':3333';
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Local Bitcoin Solo Mining</title>
<style>
body { font-family: sans-serif; background: #111; color: #eee; margin: 0; }
header { background: #222; padding: 1rem 2rem; }
h1 { margin: 0; }
.card { background: #1b1b1b; margin: 1rem 2rem; padding: 1rem 1.5rem; border-radius: 12px; }
code { background: #333; padding: 0.25rem 0.5rem; border-radius: 6px; }
label { display:block; margin-bottom: .5rem; }
select, button { padding: .4rem .6rem; border-radius: 6px; border:0; }
button { background:#4caf50; color:#fff; cursor:pointer; }
</style>
</head>
<body>
<header>
<h1>Local Bitcoin Solo Mining</h1>
<p>Point your ASICs to this server and keep 100% of your block.</p>
</header>
<div class="card">
<h2>Stratum Endpoint</h2>
<p><code><?php echo htmlspecialchars($stratum); ?></code></p>
<p>Username: your BTC address</p>
<p>Password: anything</p>
</div>
<div class="card">
<h2>Prune Size</h2>
<form method="post">
<label for="prune">Current setting:</label>
<select name="prune" id="prune">
<?php foreach ($opts as $val => $label): ?>
<option value="<?php echo $val; ?>" <?php echo ($val == $current ? 'selected' : ''); ?>>
<?php echo $label; ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit">Apply</button>
<p><small><b>Note:</b> Prune size only limits stored block data. You should also expect the node to use up to 500 MB for chainstate and metadata. Actual disk usage will be higher than the value selected here.</small></p>
<p><small>Changing this will restart bitcoind.</small></p>
</form>
</div>
<div class="card">
<h2>Operator / Donation</h2>
<p><small>Default is 2% to the project address. Set to 0 to keep the full block.</small></p>
<?php
$conf = @file_get_contents('/opt/btc-solo/conf/ckpool.conf');
$currAddr = 'YOUR_HARDCODED_DONATION_ADDRESS';
$currRate = 2;
if ($conf) {
if (preg_match('/"donaddress"\s*:\s*"([^"]+)"/', $conf, $m)) $currAddr = $m[1];
if (preg_match('/"donrate"\s*:\s*([0-9.]+)/', $conf, $m)) $currRate = $m[1];
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['donation_addr'])) {
$newAddr = trim($_POST['donation_addr']);
$newRate = floatval($_POST['donation_rate']);
if ($newAddr !== '') {
shell_exec('sudo /usr/local/sbin/btc-set-donation.sh '
. escapeshellarg($newAddr) . ' ' . escapeshellarg($newRate));
$currAddr = $newAddr;
$currRate = $newRate;
}
}
?>
<form method="post">
<label>Donation address (payout for your 2%)</label>
<input type="text" name="donation_addr" value="<?php echo htmlspecialchars($currAddr); ?>" style="width:100%;max-width:420px;">
<label>Donation %</label>
<select name="donation_rate">
<?php foreach ([0,0.5,1,2,3,4,5] as $r): ?>
<option value="<?php echo $r; ?>" <?php echo ($r == $currRate ? 'selected' : ''); ?>>
<?php echo $r; ?>%
</option>
<?php endforeach; ?>
</select>
<button type="submit">Save</button>
</form>
</div>
<div class="card">
<h2>System</h2>
<p>Check <code>systemctl status bitcoind</code> and <code>systemctl status ckpool</code>.</p>
</div>
</body>
</html>
EOF
# activation page
cat > "$WWW_ROOT/activate/index.php" <<'EOF'
<?php
$flag = '/var/lib/btc-solo/.setup-complete';
if (file_exists($flag)) {
http_response_code(403);
echo "Setup already completed.";
exit;
}
$msg = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$adminuser = trim($_POST['adminuser'] ?? '');
$adminpass = trim($_POST['adminpass'] ?? '');
$regen = isset($_POST['regen_cert']);
if ($adminuser !== '' && $adminpass !== '') {
// apply admin creds (writes rpcuser/rpcpassword, restarts services)
shell_exec('sudo /usr/local/sbin/btc-apply-admin.sh ' . escapeshellarg($adminuser) . ' ' . escapeshellarg($adminpass));
// optionally regenerate HTTPS cert
if ($regen) {
shell_exec('sudo /usr/local/sbin/btc-make-cert.sh');
}
// create setup-complete flag
file_put_contents($flag, date('c'));
// redirect to main dashboard
header('Location: /');
exit;
} else {
$msg = 'Both fields are required.';
}
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Create New Admin User</title>
<style>
body { font-family: sans-serif; background: #111; color: #eee; display:flex; justify-content:center; align-items:center; height:100vh; }
form { background: #1b1b1b; padding: 2rem; border-radius: 12px; width: 340px; }
label { display:block; margin-bottom: .5rem; }
input[type=text], input[type=password] { width:100%; padding:.5rem; margin-bottom:1rem; border:0; border-radius:6px; }
button { padding:.5rem 1rem; border:0; border-radius:6px; background:#4caf50; color:#fff; cursor:pointer; }
.msg { color: #f66; margin-bottom:1rem; }
h2 { margin-top:0; }
small { color:#aaa; }
</style>
</head>
<body>
<form method="post">
<h2>Create New Admin User</h2>
<p><small>
This sets the Bitcoin nodes administrative (RPC) credentials used to control the server itself.<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.
</small></p>
<?php if ($msg): ?>
<div class="msg"><?php echo htmlspecialchars($msg); ?></div>
<?php endif; ?>
<label>Admin Username</label>
<input type="text" name="adminuser" value="">
<label>Admin Password</label>
<input type="password" name="adminpass" value="">
<label style="display:flex;gap:.5rem;align-items:center;">
<input type="checkbox" name="regen_cert" checked>
Generate HTTPS certificate
</label>
<button type="submit">Continue</button>
</form>
</body>
</html>
EOF
# ---------------------------------------------------------------------------
echo "[7/8] NGINX with HTTPS + redirect"
# ---------------------------------------------------------------------------
mkdir -p "$CERT_DIR"
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout ${CERT_DIR}/btc-solo.key \
-out ${CERT_DIR}/btc-solo.crt \
-subj "/C=CA/ST=Ontario/L=Barrie/O=BTC-Solo/OU=IT/CN=$(hostname -f)" >/dev/null 2>&1
chmod 600 ${CERT_DIR}/btc-solo.key
cat > /etc/nginx/sites-available/btc-solo <<EOF
server {
listen 80 default_server;
return 301 https://\$host\$request_uri;
}
server {
listen 443 ssl;
ssl_certificate ${CERT_DIR}/btc-solo.crt;
ssl_certificate_key ${CERT_DIR}/btc-solo.key;
root ${WWW_ROOT};
index index.php;
# main app: try file, dir, then index.php
location / {
try_files \$uri \$uri/ /index.php;
}
# activation: let PHP regex still handle .php
location /activate/ {
try_files \$uri /activate/index.php;
}
# PHP handler (use the generic symlink, which you have)
location ~ \.php\$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
}
EOF
ln -sf /etc/nginx/sites-available/btc-solo /etc/nginx/sites-enabled/btc-solo
rm -f /etc/nginx/sites-enabled/default || true
systemctl restart nginx
# ---------------------------------------------------------------------------
echo "[8/8] Done"
# ---------------------------------------------------------------------------
echo
echo "================================================================"
echo " Bitcoin Solo Miner Appliance installed."
echo
echo " 1) Open: https://$(hostname -I | awk '{print $1}')/activate/"
echo " 2) Set Admin Username / Admin Password"
echo " 3) (Optional) Regenerate the cert"
echo " 4) You will be redirected to the dashboard."
echo
echo " Stratum endpoint for ASICs: stratum+tcp://$(hostname -I | awk '{print $1}'):${CKPOOL_PORT}"
echo "================================================================"