...
 
Commits (3)
<?php
// Borrowed from https://gitlab.com/MMuArFF/lnd-php-wallet
// Borrowed from https://gitlab.com/MMuArFF/lnd-php-wallet with added macaroon and genseed support by @ketominer
class LightningClient
{
public $macaroon = '';
function __construct($mac) {
$this->macaroon = $mac;
$this->macaroon = $mac;
}
private function LnRest($add,$post= "")
......@@ -20,8 +20,8 @@ class LightningClient
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if(!empty($post))
{
curl_setopt($ch,CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_POSTFIELDS, $post);
curl_setopt($ch,CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_POSTFIELDS, $post);
}
$data = curl_exec($ch);
curl_close($ch);
......@@ -88,6 +88,27 @@ class LightningClient
$res = LightningClient::LnRest('/v1/unlockwallet',$pass);
return json_decode($res,true);
}
/*
> var fs = require('fs');
> var request = require('request');
> var options = {
url: 'https://localhost:8080/v1/genseed',
// Work-around for self-signed certificates.
rejectUnauthorized: false,
json: true,
};
> request.get(options, function(error, response, body) {
console.log(body);
});
{
"cipher_seed_mnemonic": <array string>,
"enciphered_seed": <byte>,
}
*/
// public function genseed() {
// }
public function create_wallet($pass) {
$pass = json_encode(array('password' => base64_encode($pass)));
$res = LightningClient::LnRest('/v1/createwallet',$pass);
......
<?php
require_once("/etc/nodl-config.php");
require_once('/etc/nodl/config.php');
require_once('easybitcoin.php');
require_once('easylnd.php');
......@@ -16,6 +16,49 @@ $bitcoind['height'] = 0;
$bitcoind['headers'] = 0;
$bitcoind['bestblock'] = '';
// Check batch and serial
$batch = file_get_contents('/etc/nodl/batch');
$serial = file_get_contents('/etc/nodl/serial');
$res = $db->query('SELECT name,value FROM config WHERE name=\'batch\'');
if(!$res->fetch_assoc() && $batch) {
$db->query('INSERT INTO config(name,value) VALUES(\'batch\',\''.serialize($batch).'\')');
}
$res->free_result();
$res = $db->query('SELECT name,value FROM config WHERE name=\'serial\'');
if(!$res->fetch_assoc() && $serial) {
$db->query('INSERT INTO config(name,value) VALUES(\'serial\',\''.serialize($serial).'\')');
}
$res->free_result();
// Check if there are some actions to execute
$res = $db->query('SELECT name,value FROM config WHERE name LIKE \'%_status\'');
while($row = $res->fetch_assoc()) {
switch($row['value']) {
case '100':
exec('chmod +x /usr/share/nodl/install-'.str_replace('_status','',$row['name']).'.sh');
exec('/usr/share/nodl/install-'.str_replace('_status','',$row['name']).'.sh',$values,$return);
if($return == 0) {
$return = 1;
} else {
$return = (string)(1000+$return);
}
$db->query('UPDATE config SET value=\''.$return.'\' WHERE name=\''.$row['name'].'\'');
break;
case '201':
exec('systemctl start '.str_replace('_status','',$row['name']));
$db->query('UPDATE config SET value=\'2\' WHERE name=\''.$row['name'].'\'');
break;
case '202':
exec('systemctl stop '.str_replace('_status','',$row['name']));
$db->query('UPDATE config SET value=\'1\' WHERE name=\''.$row['name'].'\'');
break;
default:
break;
}
}
$res->free_result();
// This process runs periodicaly to get status of running services and apply changes requested by admin interface
// Check if bitcoind is running
......@@ -36,8 +79,6 @@ if($bitcoind_pid = exec('pgrep bitcoind')) {
$bitcoind['subversion'] = $bitcoind['networkinfo']['subversion'];
} else {
}
//print_r($bitcoind->getnetworkinfo());
//print_r($bitcoind->getblock('000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'));
}
foreach($bitcoind as $name => $value) {
......@@ -48,9 +89,7 @@ foreach($bitcoind as $name => $value) {
}
$req->close();
$query = "INSERT INTO status(name,value) VALUES(?,?)";
//if(is_array($value)) {
$value = serialize($value);
//}
$value = serialize($value);
if(!($req = $db->prepare($query)) || !$req->bind_param("ss", $name, $value) || !$req->execute()) {
error_log("Query failed: ".$db->error);
}
......@@ -107,19 +146,20 @@ if($lnd_pid = exec('pgrep lnd')) {
if(!isset($lnd['getinfo']['synced_to_chain'])) {
$lnd['unlock'] = $lnd_rpc->testunlock();
if($lnd['unlock']=='1') {
$lnd['status'] = 10; // unlocked but not synced
$lnd['status'] = 10; // unlocked but not synced
} else {
$lnd['status'] = 11; // locked
$query = "SELECT value FROM config WHERE name=?";
$name = 'lnd_autounlock';
if(!($req = $db->prepare($query)) || !$req->bind_param("s", $name) || !$req->execute() || !$req->bind_result($value) || !$req->fetch()) {
} else {
if($value == "1") {
$lnd['status'] = 11; // locked or not initialized
$res = $db->query('SELECT value FROM config WHERE name=\'lnd_autounlock\'');
if($row = $res->fetch_assoc()) {
if($row['value'] == "1") {
print_r($lnd_rpc->unlockwallet($lnd_password));
echo "Unlocking lnd wallet...\n";
}
}
$req->close();
echo "Unlocking lnd wallet...\n";
}
} else {
$lnd['status'] = 12; // not initialized
}
$res->free_result();
}
} else {
$lnd['status'] = 2; // unlocked and synced
......
#!/bin/bash
# Running nodl-service.php every 5 seconds
echo 'Running nodl-service.php every 5 seconds'
while [ 0 ];
do
/usr/bin/php /usr/local/bin/nodl-service.php
sleep 5
done;
......@@ -3,13 +3,12 @@
$db_host = 'localhost';
$db_name = 'nodladmin';
$db_user = 'nodladmin';
$db_pass = 'wae1aiy7Bei5Coath1eik3thaiqu5egh';
$db_pass = file('/etc/nodl/maria-nodl.pwd', FILE_IGNORE_NEW_LINES)[0];
$bitcoind_rpchost='127.0.0.1';
$bitcoind_rpcport='8332';
$bitcoind_rpcuser='rpc';
$bitcoind_rpcpassword='SuperGr3atPassword';
$bitcoind_rpchost = '127.0.0.1';
$bitcoind_rpcport = '8332';
$bitcoind_rpcuser = 'rpc';
$bitcoind_rpcpassword = file('/etc/nodl/bitcoind-rpc.pwd', FILE_IGNORE_NEW_LINES)[0];
$lnd_macaroon='/opt/bitcoin/.lnd/data/chain/bitcoin/mainnet/admin.macaroon';
$lnd_password='fohp0UthuuShek8u';
$lnd_password = file('/etc/nodl/lnd-wallet.pwd', FILE_IGNORE_NEW_LINES)[0];
CREATE DATABASE nodladmin;
CREATE USER nodladmin@localhost identified by 'NODLADMINPASS';
GRANT ALL ON nodladmin.* to nodladmin@localhost;
USE nodladmin;
CREATE TABLE config (
name VARCHAR(255) NOT NULL DEFAULT '' PRIMARY KEY,
value VARCHAR(255) NOT NULL DEFAULT '',
dt DATETIME NOT NULL DEFAULT NOW()
) ENGINE=InnoDB;
CREATE TABLE status (
name VARCHAR(255) NOT NULL DEFAULT '' PRIMARY KEY,
value TEXT,
dt DATETIME NOT NULL DEFAULT NOW()
) ENGINE=InnoDB;
CREATE TABLE audit (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
dt DATETIME NOT NULL DEFAULT NOW(),
old_data TEXT,
nex_data TEXT
) ENGINE=InnoDB;
INSERT INTO config(name,value) VALUES('version','v0.0.1');
<?php
$init_done = 0;
$errors = 0;
$admin = 0;
$batch = 'invalid';
$serial = 'invalid';
$version = 'version unknown';
$db = mysqli_connect ($db_host, $db_user, $db_pass, $db_name);
if(!$db) {
$errormsg = "Can't connect to the database";
}
$res = $db->query('SELECT name,value FROM config WHERE name IN (\'version\',\'batch\',\'serial\')');
while($row = $res->fetch_assoc()) {
switch($row['name']) {
case 'version':
$version = $row['value'];
break;
case 'batch':
$batch = unserialize($row['value']);
break;
case 'serial':
$serial = unserialize($row['value']);
break;
default:
break;
}
}
$res->free_result();
if($errors>0) {
error_log('Configuration is missing in the database.');
$errormsg = 'The installation is broken, please contact <a href="mailto:support@nodl.it">support</a>.';
}
$query = 'SELECT value FROM config WHERE name=?';
$value = 'firstrun';
$tmp = '';
if(!($req = $db->prepare($query)) ||
!$req->bind_param('s', $value) ||
!$req->execute() ||
!$req->bind_result($version) ||
!$req->bind_result($tmp) ||
!$req->fetch()) {
} else {
$init_done = 1;
}
try {
$req->close();
} catch (Exception $e) {
}
if($init_done) {
if(isset($_SESSION['admin']) && $_SESSION['admin'] == 'yes') {
if(isset($_POST['adm_pass']) && $_POST['adm_pass'] == 'logout') {
unset($_SESSION['admin']);
$admin = 0;
} else {
$admin = 1;
}
} else {
if(isset($_POST['adm_pass'])) {
$res = $db->query('SELECT value FROM config WHERE name=\'adm_pass\'');
if($row = $res->fetch_assoc()) {
if(password_verify($_POST['adm_pass'],$row['value'])) {
$admin = 1;
$_SESSION['admin'] = 'yes';
} else {
$admin = 0;
}
}
}
}
}
......@@ -3,12 +3,15 @@ session_start();
if( !isset($_SESSION['last_access']) || (time() - $_SESSION['last_access']) > 60 )
$_SESSION['last_access'] = time();
$errormsg = '';
include_once("inc/config.php");
include_once("inc/database.php");
include_once("inc/firstrun.php");
//include_once("inc/firstrun.php");
//include_once("inc/init.php");
//include_once("inc/utils.php");
//require_once("vendor/autoload.php");
?>
<!doctype html>
<html lang="en">
......@@ -30,9 +33,27 @@ include_once("inc/firstrun.php");
<div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 border-bottom shadow-sm">
<h5 class="my-0 mr-md-auto font-weight-normal"><img width="48" height="48" src="./images/nodl.png" alt="nodl logo"></h5>
<nav class="my-2 my-md-0 mr-md-3">
<a class="p-2" href="?p=home">home</a>
<a class="p-2" href="https://blog.nodl.it" target="_blank" rel="noopener noreferrer">blog</a>
<a class="p-2" href="mailto:contact@lightning-solutions.eu">contact</a>
</nav>
<?php
if($admin) {
?>
<form class="form-inline my-2 my-lg-0" method="POST">
<input name="adm_pass" type="hidden" value="logout">
<button class="btn btn-secondary my-2 my-sm-0" type="submit">Logout</button>
</form>
<?php
} else {
?>
<form class="form-inline my-2 my-lg-0" method="POST">
<input class="form-control mr-sm-2" name="adm_pass" type="password" placeholder="Administrator password">
<button class="btn btn-secondary my-2 my-sm-0" type="submit">Login</button>
</form>
<?php
}
?>
</div>
<?php
......@@ -42,24 +63,47 @@ if(strlen($errormsg)>1) {
echo ' <div class="card-header">Error</div>';
echo ' <div class="card-body">';
echo ' <h4 class="card-title">Fatal error</h4>';
echo ' <p class="card-text">'.htmlspecialchars($errormsg).'</p>';
echo ' <p class="card-text">'.$errormsg.'</p>';
echo ' </div>';
echo ' </div>';
echo '</div>';
}
switch($_GET["p"]) {
case "status":
include "pages/status.php";
break;
default:
if($init_done) {
if($admin) {
echo '<div class="container">';
echo ' <div class="card text-white bg-success mb-3">';
echo ' <div class="card-header">Connected</div>';
echo ' <div class="card-body">';
echo ' <h4 class="card-title">You are connected as administrator</h4>';
echo ' </div>';
echo ' </div>';
echo '</div>';
}
$p = '';
if(isset($_GET['p'])) {
$p = $_GET['p'];
}
if($init_done) {
switch($p) {
case "status":
include "pages/status.php";
} else {
break;
default:
include "pages/home.php";
}
break;
break;
}
} else {
switch($p) {
case "setup":
include "pages/setup.php";
break;
default:
include "pages/home.php";
break;
}
}
include "pages/footer.php";
?>
......
......@@ -11,10 +11,10 @@
<li><a class="text-muted" href="mailto:contact@lightning-solutions.eu">Email</a></li>
<li><a href="https://twitter.com/nodl_it" target="_blank" rel="noopener noreferrer">Twitter</a></li>
<li><a href="https://t.me/nodl_it" target="_blank" rel="noopener noreferrer">Telegram</a></li>
<li>Software <?php echo((string)$version);?></li>
</ul>
</div>
<div class="col-6 col-md">
<h5>Credits</h5>
<ul class="list-unstyled text-small">
......
<div class="container">
<h3 class="text-center">Welcome to your nodl #0/21</h3>
<p class="text-center">Click the logo below to start setup</p>
<p class="text-center"><img width="25%" src="images/nodl.png"></a></p>
<h3 class="text-center">Welcome to your nodl #<?php echo($serial.' from batch '.$batch); ?></h3>
<?php
if($init_done) {
echo('<p class="text-center">Click the logo below to access status</p>');
echo('<p class="text-center"><a href="?p=status"><img width="25%" src="images/nodl.png"></a></p>');
} else {
echo('<p class="text-center">Click the logo below to start setup</p>');
echo('<p class="text-center"><a href="?p=setup"><img width="25%" src="images/nodl.png"></a></p>');
}
?>
<p class="lead text-center">
Monetary Liberty in Your Palm.<br/>
Bitcoin Full Node.<br/>
......
<?php
echo('<div class="container">');
if($init_done) {
echo('The setup process is already completed.');
} else {
$error = 0;
$success = 0;
$errormsg = [];
if(isset($_POST['sub'])) {
if(!isset($_POST['adm_pass1']) || strlen($_POST['adm_pass1'])<8) {
$errormsg[] = 'Administrator password too short (8)';
} else {
if(!isset($_POST['adm_pass2']) || $_POST['adm_pass2'] !== $_POST['adm_pass1']) {
$errormsg[] = 'Administrator passwords don\'t match';
} else {
$options = [
'memory_cost' => 1<<17, // 128 Mb
'time_cost' => 4,
'threads' => 3,
];
$adm_hash = password_hash($_POST['adm_pass1'], PASSWORD_ARGON2I, $options);
unset($_POST['adm_pass1']);
unset($_POST['adm_pass2']);
$query = "INSERT INTO config(name,value) VALUES(?,?)";
$name = 'adm_pass';
if(!($req = $db->prepare($query)) || !$req->bind_param('ss', $name, $adm_hash) || !$req->execute()) {
$errormsg[] = 'Could not finish setup process';
} else {
$name = 'firstrun';
$value = '1';
if(!($req = $db->prepare($query)) || !$req->bind_param('ss', $name, $value) || !$req->execute()) {
$errormsg[] = 'Could not finish setup process';
} else {
$success = 1;
}
}
}
}
}
if($success) {
echo('<p class="text-center">Configuration is now finished. <a href="?p=status">click here</a> to continue.');
} else {
echo('<p class="text-center">Before you can start using your nodl, you need to answer a few questions.</p>');
foreach($errormsg as $error) {
echo('<p class="text-danger">'.$error.'</p>');
}
echo('<form method="POST">');
echo('<fieldset>');
echo('First, let\'s set an administrator password. This password will be used to run actions on this panel, such as installing or configuring an application.');
echo('<div class="form-group">');
echo('<label for="adm_pass1">Password</label>');
echo('<input type="password" class="form-control" id="adm_pass1" name="adm_pass1" placeholder="Password">');
echo('</div>');
echo('<div class="form-group">');
echo('<label for="adm_pass2">Password (again)</label>');
echo('<input type="password" class="form-control" id="adm_pass2" name="adm_pass2" placeholder="Password">');
echo('</div>');
echo('<input type="hidden" name="sub" value="sub">');
echo('<button type="submit" class="btn btn-primary">Submit</button>');
echo('</fieldset>');
echo('</form>');
}
}
echo('</div>');
echo('<div class="container">');
<script>
setTimeout(function(){
window.location.reload(1);
}, 10000);
</script>
<div class="container">
<?php
if(isset($_POST['install'])) {
switch($_POST['install']) {
case 'bitcoind':
case 'lnd':
$res = $db->query('SELECT value FROM config WHERE name=\''.$_POST['install'].'_status\'');
if($row = $res->fetch_assoc()) {
switch($row['value']) {
case '1': //installed
case '2': //running
break;
default:
$db->query('UPDATE config SET value=\'100\' WHERE name=\''.$_POST['install'].'_status\'');
break;
}
} else {
$db->query('INSERT INTO config(name,value) VALUES(\''.$_POST['install'].'_status\',\'100\')');
}
$res->free_result();
break;
default:
break;
}
}
if(isset($_POST['start'])) {
switch($_POST['start']) {
case 'bitcoind':
case 'lnd':
$res = $db->query('SELECT value FROM config WHERE name=\''.$_POST['start'].'_status\'');
if($row = $res->fetch_assoc()) {
switch($row['value']) {
case '1': //installed
$db->query('UPDATE config SET value=\'201\' WHERE name=\''.$_POST['start'].'_status\'');
break;
default:
break;
}
}
$res->free_result();
break;
default:
break;
}
}
if(isset($_POST['stop'])) {
switch($_POST['stop']) {
case 'bitcoind':
case 'lnd':
$res = $db->query('SELECT value FROM config WHERE name=\''.$_POST['stop'].'_status\'');
if($row = $res->fetch_assoc()) {
switch($row['value']) {
case '2': //running
$db->query('UPDATE config SET value=\'202\' WHERE name=\''.$_POST['stop'].'_status\'');
break;
default:
break;
}
}
$res->free_result();
break;
default:
break;
}
}
// Build status
$services[]['name'] = 'bitcoind';
$services[]['name'] = 'lnd';
$services[]['name'] = 'tor';
$services[]['name'] = 'btcpayserver';
$services[]['name'] = 'tincd';
$services[]['name'] = 'iodine';
//$services[]['name'] = 'tor';
//$services[]['name'] = 'btcpayserver';
//$services[]['name'] = 'tincd';
//$services[]['name'] = 'iodine';
foreach($services as $id => $param) {
$query = "SELECT value FROM config WHERE name=?";
$name = $param['name'].'_status';
$value = '';
if(!($req = $db->prepare($query)) ||
!$req->bind_param('s', $name) ||
!$req->execute() ||
!$req->bind_result($value) ||
!$req->fetch()) {
$services[$id]['status'] = '-1';
$res = $db->query('SELECT value FROM config WHERE name=\''.$param['name'].'_status'.'\'');
if($row = $res->fetch_assoc()) {
$services[$id]['status'] = $row['value'];
} else {
$services[$id]['status'] = $value;
$services[$id]['status'] = '-1';
}
$req->close();
$res->free_result();
}
?>
......@@ -53,25 +120,81 @@ foreach($services as $id => $param) {
echo('<div class="card text-white mb-3" style="max-width: 30rem;">'); // card start
switch($param['status']) {
case '-1':
case '0':
default:
echo('<div class="card-header bg-secondary">'.$param['name'].'</div>');
echo('<div class="card-body">'); // body start
echo('<h4 class="card-title">Unknown</h4>');
echo('<h4 class="card-title">Not installed</h4>');
echo('</div>');
if($admin) {
echo('<div class="card-body">');
echo('<form method="POST"><input type="hidden" name="install" value="'.$param['name'].'"><button type="submit" class="btn btn-primary">Schedule install</button></form>');
echo('</div>');
}
break;
case '1001':
echo('<div class="card-header bg-danger">'.$param['name'].'</div>');
echo('<div class="card-body">'); // body start
echo('<h4 class="card-title">Installation failed: download failed</h4>');
echo('</div>');
if($admin) {
echo('<div class="card-body">');
echo('<form method="POST"><input type="hidden" name="install" value="'.$param['name'].'"><button type="submit" class="btn btn-primary">Try again</button></form>');
echo('</div>');
}
break;
case '0':
echo('<div class="card-header bg-secondary">'.$param['name'].'</div>');
case '1002':
echo('<div class="card-header bg-danger">'.$param['name'].'</div>');
echo('<div class="card-body">'); // body start
echo('<h4 class="card-title">Not installed</h4>');
echo('<h4 class="card-title">Installation failed: signature check failed</h4>');
echo('</div>');
if($admin) {
echo('<div class="card-body">');
echo('<form method="POST"><input type="hidden" name="install" value="'.$param['name'].'"><button type="submit" class="btn btn-primary">Try again</button></form>');
echo('</div>');
}
break;
case '1003':
echo('<div class="card-header bg-danger">'.$param['name'].'</div>');
echo('<div class="card-body">'); // body start
echo('<h4 class="card-title">Installation failed: unknown failure</h4>');
echo('</div>');
if($admin) {
echo('<div class="card-body">');
echo('<form method="POST"><input type="hidden" name="install" value="'.$param['name'].'"><button type="submit" class="btn btn-primary">Try again</button></form>');
echo('</div>');
}
break;
case '100':
echo('<div class="card-header bg-secondary">'.$param['name'].'</div>');
echo('<div class="card-body">'); // body start
echo('<h4 class="card-title">Installation scheduled</h4>');
echo('</div>');
break;
case '201':
echo('<div class="card-header bg-secondary">'.$param['name'].'</div>');
echo('<div class="card-body">'); // body start
echo('<h4 class="card-title">Starting...</h4>');
echo('</div>');
break;
case '202':
echo('<div class="card-header bg-secondary">'.$param['name'].'</div>');
echo('<div class="card-body">'); // body start
echo('<h4 class="card-title">Stopping...</h4>');
echo('</div>');
break;
case '1':
echo('<div class="card-header bg-primary">'.$param['name'].'</div>');
echo('<div class="card-body">'); // body start
echo('<h4 class="card-title">Inactive</h4>');
echo('</div>');
if($admin) {
echo('<div class="card-body">');
echo('<form method="POST"><input type="hidden" name="start" value="'.$param['name'].'"><button type="submit" class="btn btn-primary">Start service</button></form>');
echo('</div>');
}
echo('<div class="card-body">');
echo('<a href="#" class="card-link">Configure</a>');
//echo('<a href="#" class="card-link">Configure</a>');
echo('</div>');
break;
case '2': // installed and activated, checking further information
......@@ -103,6 +226,10 @@ foreach($services as $id => $param) {
$header_color = 'bg-success';
$title_text = 'Running';
break;
case '1':
$header_color = 'bg-warning';
$title_text = 'Starting';
break;
default:
break;
}
......@@ -120,6 +247,11 @@ foreach($services as $id => $param) {
echo($item);
}
echo('</div>'); // body end
if($admin) {
echo('<div class="card-body">');
echo('<form method="POST"><input type="hidden" name="stop" value="'.$param['name'].'"><button type="submit" class="btn btn-primary">Stop service</button></form>');
echo('</div>');
}
if(strlen($dt)>1) {
echo('<div class="card-footer text-muted">Updated '.$dt.'</div>');
}
......@@ -151,6 +283,10 @@ foreach($services as $id => $param) {
$header_color = 'bg-danger';
$title_text = 'Locked';
break;
case '12':
$header_color = 'bg-warning';
$title_text = 'Wallet pending initialisation';
break;
case '2':
$header_color = 'bg-success';