Merge pull request #2 from SatoshiPortal/master

rebase
This commit is contained in:
Francis Pouliot
2020-01-17 11:12:17 -05:00
committed by GitHub
172 changed files with 17181 additions and 2383 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.idea
dist/**
!dist/setup.sh
!dist/sr.sh

View File

@@ -1,47 +1,78 @@
# cyphernode
Modular Bitcoin full-node microservices API server architecture and utilities toolkit to build scalable, secure and featureful apps and services without trusted third parties.
Modular Bitcoin full-node microservices API server architecture and utilities toolkit to build scalable, secure and featureful apps and services without trusted third parties.
# What is cyphernode?
An open-source self-hosted API which allows you to spawn and call your encrypted overlay network of dockerized Bitcoin and crypto software projects (virtual machines).
Cyphernode is a Free open-source alternative to hosted services and commercial Bitcoin APIs such as Blockchain.info, Bitpay, Coinbase, BlockCypher, Bitgo, etc. You can use it to build Bitcoin services and applications using your own Bitcoin and Lightning Network full nodes. It is a substitute for the Bitcore and Insight software projects.
You can use it to build Bitcoin services and applications using your own Bitcoin and Lightning Network full nodes.
It implements a self-hosted API which allows you to spawn and call your encrypted overlay network of dockerized Bitcoin and crypto software projects (virtual machines). The Docker containers used in this project are hosted at www.bitcoindockers.com.
It is an alternative to hosted services and commercial Bitcoin APIs such as Blockchain.info, Bitpay, Coinbase, Blocypher, Bitgo, etc.
It is a substitute for the Bitcore and Insight software projects.
If aims to offer all advanced features and utilities necessary to operate entreprise grade Bitcoin services. We provide a curated list of functions using multiple software, but you can build your own private ones or add yours as a default option in the project.
It is designed to be deployed on virtual machines with launch scripts, but with efficiency and minimalism in mind so that it can also run on multiple Rasberry Pi with very low computing ressources (and extremely low if installing pre-synchronized blockchain and pruned). Because of the modular architecture, heavier modules like blockchain indexers are optional (and not needed for most commercial use-cases). For a full-node and all modules, prepare 350GB of space and 2GB of RAM.
Hardware wallets (ColdCard, Trezor) will be utilized for key generation and signing (using PSBT BIP174), as well as for connecting to self-hosted web user interfaces.
It aims to offer all advanced features and utilities necessary to operate entreprise-grade Bitcoin services. It includes a curated list of functions using multiple software, but you can build your own private ones or add yours as a default option in the project.
It is currently in production by Bylls.com, Canada's first and largest Bitcoin payment processor, as well as Bitcoin Outlet, a fixed-rate Bitcoin exchange service alternative to Coinbase which allows Canadians to purchase bitcoins sent directly to their own Bitcoin wallet.
The docker containers used in this project are hosted at www.bitcoindockers.com
The project is in **heavy development** - we are currently looking for reviews, new features, user feedback and contributors to our roadmap.
The project is in **heavy development** - we are currently looking for review, new features, user feedback and contributors to our roadmap.
![image](doc/CN-Arch.jpg)
# Low requirements, efficient use of resources
Cyphernode is designed to be deployed on virtual machines with launch scripts, but with efficiency and minimalism in mind so that it can also run on multiple Rasberry Pi with very low computing ressources (and extremely low if installing pre-synchronized blockchain and pruned). Because of the modular architecture, heavier modules like blockchain indexers are optional (and not needed for most commercial use-cases).
* For a full-node and all modules:
* 350 GB of storage, 2GB of RAM.
* Hardware wallets (ColdCard, Trezor) for key generation and signing (using PSBT BIP174), as well as for connecting to self-hosted web user interfaces.
# Cyphernode Architecture
Cyphernode is an assembly of Docker containers being called by a request dispatcher.
The request dispatcher (requesthandler.sh) is the HTTP entry point.
The request dispatcher is stateful: it keeps some data to be more effective on next calls.
The request dispatcher is where Cyphernode scales with new features: add your switch, dispatch requests to your stuff.
We are trying to construct each container so that it can be used separately, as a standalone reusable component.
Important to us:
Be as optimized as possible, using Alpine when possible and having the smallest Docker image size possible
Reuse existing software: built-in shell commands, well-established pieces of software, etc.
Use open-source software
Don't reinvent the wheel
Expose the less possible surface
Center element: proxy_docker
The proxy_docker is the container receiving and dispatching calls from clients. When adding a feature to Cyphernode, it is the first part to be modified to integrate the new feature.
proxy_docker/app/script/requesthandler.sh
You will find in there the switch statement used to dispatch the requests. Just add a case block with your command, using other cases as examples for POST or GET requests.
proxy_docker/app/config
You will find there config files. config.properties should be used to centralize configs. spender and watcher properties are used to obfuscate credentials on curl calls.
proxy_docker/app/data
watching.sql contains the data model. Called "watching" because in the beginning of the project, it was only used for watching addresses. Now could be used to index the blockchain (build an explorer) and add more features.
cron_docker
If you have jobs to be scheduled, use this container. Just add an executable and add it to the crontab.
Currently used to make sure callbacks have been called for missed transactions.
# About this project
- Created and maintained by www.satoshiportal.com
- Dedicated full-time developer @kexkey
- Project manager @FrancisPouliot
- Disclaimer: as of release on Sept. 23 2018 the project is still it its early stages (Alpha) and many of the features have yet to be implemented. The core architecture and basic wallet operations and blockchain query functions are fully functional.
* Created and maintained by www.satoshiportal.com
* Dedicated full-time developer @kexkey
* Project manager @FrancisPouliot
* Contributor: @\_\_escapee\_\_
* Disclaimer: as of release on Sept. 23 2018 the project is still it its early stages (Alpha) and many of the features have yet to be implemented. The core architecture and basic wallet operations and blockchain query functions are fully functional.
# How to use cyphernode?
The core component of cyphernode is a request handler which exposes HTTP endpoints via REST API, acting as an absctration layer between your apps and the open-source Bitcoin sofware you want to interact with.
## DOCS
## Documentation
Read the API docs here: https://github.com/SatoshiPortal/cyphernode/blob/master/doc/API.md
Installation documentation: https://github.com/SatoshiPortal/cyphernode/blob/master/doc/INSTALL.md
Step-by-step manual install: https://github.com/SatoshiPortal/cyphernode/blob/master/doc/INSTALL-MANUAL-STEPS.md
* Read the API docs here: https://github.com/SatoshiPortal/cyphernode/blob/master/doc/API.md
* Installation documentation: https://github.com/SatoshiPortal/cyphernode/blob/master/doc/INSTALL.md
* Step-by-step manual install (deprecated): https://github.com/SatoshiPortal/cyphernode/blob/master/doc/INSTALL-MANUAL-STEPS.md
## When calling a cyphernode endpoint, you are either

View File

@@ -11,12 +11,14 @@ RUN apk add --update --no-cache \
su-exec
COPY auth.sh /etc/nginx/conf.d/
COPY default-ssl.conf /etc/nginx/conf.d/default.conf
COPY statuspage.html /etc/nginx/conf.d/status/
COPY default.conf /etc/nginx/conf.d/default.conf
COPY entrypoint.sh entrypoint.sh
COPY trace.sh /etc/nginx/conf.d/
COPY tests.sh /etc/nginx/conf.d/
RUN chmod +x /etc/nginx/conf.d/auth.sh entrypoint.sh
RUN touch /var/log/gatekeeper.log
RUN chmod a+rw /var/log/gatekeeper.log
ENTRYPOINT ["./entrypoint.sh"]

View File

@@ -1,8 +1,22 @@
# The file api.properties generated by the installer should look like this.
# Watcher can:
# Stats can:
action_helloworld=stats
action_getblockchaininfo=stats
action_installation_info=stats
action_getmempoolinfo=stats
action_getblockhash=stats
# Watcher can do what the stats can do, plus:
action_watch=watcher
action_unwatch=watcher
action_watchxpub=watcher
action_unwatchxpubbyxpub=watcher
action_unwatchxpubbylabel=watcher
action_getactivewatchesbyxpub=watcher
action_getactivewatchesbylabel=watcher
action_getactivexpubwatches=watcher
action_watchtxid=watcher
action_getactivewatches=watcher
action_getbestblockhash=watcher
action_getbestblockinfo=watcher
@@ -10,11 +24,16 @@ action_getblockinfo=watcher
action_gettransaction=watcher
action_ln_getinfo=watcher
action_ln_create_invoice=watcher
action_ln_getconnectionstring=watcher
action_ln_decodebolt11=watcher
# Spender can do what the watcher can do, plus:
action_getbalance=spender
action_getbalancebyxpub=spender
action_getbalancebyxpublabel=spender
action_getnewaddress=spender
action_spend=spender
action_bumpfee=spender
action_addtobatch=spender
action_batchspend=spender
action_deriveindex=spender
@@ -23,11 +42,15 @@ action_ln_pay=spender
action_ln_newaddr=spender
action_ots_stamp=spender
action_ots_getfile=spender
action_ln_getinvoice=spender
action_ln_decodebolt11=spender
action_ln_connectfund=spender
# Admin can do what the spender can do, plus:
# Should be called from inside the Docker network only:
action_conf=internal
action_newblock=internal
action_executecallbacks=internal
action_ots_backoffice=internal

50
api_auth_docker/auth.sh Normal file → Executable file
View File

@@ -16,20 +16,19 @@
. ./trace.sh
verify_sign()
{
verify_sign() {
local returncode
local header64=$(echo ${1} | cut -sd '.' -f1)
local payload64=$(echo ${1} | cut -sd '.' -f2)
local signature=$(echo ${1} | cut -sd '.' -f3)
local header64=$(echo "${1}" | cut -sd '.' -f1)
local payload64=$(echo "${1}" | cut -sd '.' -f2)
local signature=$(echo "${1}" | cut -sd '.' -f3)
trace "[verify_sign] header64=${header64}"
trace "[verify_sign] payload64=${payload64}"
trace "[verify_sign] signature=${signature}"
local payload=$(echo -n ${payload64} | base64 -d)
local exp=$(echo ${payload} | jq ".exp")
local payload=$(echo -n "${payload64}" | base64 -d)
local exp=$(echo "${payload}" | jq ".exp")
local current=$(date +"%s")
trace "[verify_sign] payload=${payload}"
@@ -38,7 +37,7 @@ verify_sign()
if [ ${exp} -gt ${current} ]; then
trace "[verify_sign] Not expired, let's validate signature"
local id=$(echo ${payload} | jq ".id" | tr -d '"')
local id=$(echo "${payload}" | jq -r ".id")
trace "[verify_sign] id=${id}"
# Check for code injection
@@ -82,38 +81,47 @@ verify_sign()
return 1
}
verify_group()
{
verify_group() {
trace "[verify_group] Verifying group..."
local id=${1}
# REQUEST_URI should look like this: /v0/watch/2blablabla
local context=$(echo "${REQUEST_URI#\/}" | cut -d '/' -f1)
local action=$(echo "${REQUEST_URI#\/}" | cut -d '/' -f2)
trace "[verify_group] action=${action}"
trace "[verify_group] context=${context} action=${action}"
# Check for code injection
# action can be alphanum... and _ and - but nothing else
local actiontoinspect=$(echo "$action" | tr -d '_-')
local actiontoinspect=$(echo "$action" | tr -d '_-')
case $actiontoinspect in (*[![:alnum:]]*|"")
trace "[verify_group] Potential code injection, exiting"
return 1
esac
# It is so much faster to include the keys here instead of grep'ing the file for key.
. ./api.properties
local needed_group
local ugroups
eval needed_group='$action_'${action}
trace "[verify_group] needed_group=${needed_group}"
eval ugroups='$ugroups_'$id
trace "[verify_group] user groups=${ugroups}"
case "${ugroups}" in
*${needed_group}*) trace "[verify_group] Access granted"; return 0 ;;
esac
if [ ${context} = "s" ]; then
# static files only accessible by a certain group
needed_group=${action}
elif [ ${context} = "v0" ]; then
# actual api calls
# It is so much faster to include the keys here instead of grep'ing the file for key.
. ./api.properties
eval needed_group='$action_'${action}
fi
trace "[verify_group] needed_group=${needed_group}"
# If needed_group is empty, the action was not found in api.propeties.
if [ -n "${needed_group}" ]; then
case "${ugroups}" in
*${needed_group}*) trace "[verify_group] Access granted"; return 0 ;;
esac
fi
trace "[verify_group] Access NOT granted"
return 1

View File

@@ -1,12 +1,25 @@
server {
listen 80;
listen 443 ssl;
server_name localhost;
#include /etc/nginx/conf.d/ip-whitelist.conf;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
location /s/ {
auth_request /auth;
root /etc/nginx/conf.d;
}
location /v0/ {
auth_request /auth;
proxy_pass http://proxy:8888/;
# Up default 60 second timeout for 3 minutes (OTS stamping can take time)
proxy_connect_timeout 180;
proxy_send_timeout 180;
proxy_read_timeout 180;
send_timeout 180;
}
location /auth {

View File

@@ -1,9 +1,10 @@
# The file keys.properties generated by the installer should look like this.
# kapi_id="id";kapi_key="key";kapi_groups="group1,group2";leave the rest intact
kapi_id="001";kapi_key="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";kapi_groups="watcher";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="002";kapi_key="50c5e483b80964595508f214229b014aa6c013594d57d38bcb841093a39f1d83";kapi_groups="watcher";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="003";kapi_key="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";kapi_groups="watcher,spender";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="004";kapi_key="bb0458b705e774c0c9622efaccfe573aa30c82f62386d9435f04e9727cdc26fd";kapi_groups="watcher,spender";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="005";kapi_key="6c009201b123e8c24c6b74590de28c0c96f3287e88cac9460a2173a53d73fb87";kapi_groups="watcher,spender,admin";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="006";kapi_key="19e121b698014fac638f772c4ff5775a738856bf6cbdef0dc88971059c69da4b";kapi_groups="watcher,spender,admin";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="000";kapi_key="eff1eeea370eacdc5cf7e96c2d82340d1568079a5d4d87006ec8788a98883b36";kapi_groups="stats";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="001";kapi_key="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";kapi_groups="stats,watcher";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="002";kapi_key="50c5e483b80964595508f214229b014aa6c013594d57d38bcb841093a39f1d83";kapi_groups="stats,watcher";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="003";kapi_key="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";kapi_groups="stats,watcher,spender";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="004";kapi_key="bb0458b705e774c0c9622efaccfe573aa30c82f62386d9435f04e9727cdc26fd";kapi_groups="stats,watcher,spender";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="005";kapi_key="6c009201b123e8c24c6b74590de28c0c96f3287e88cac9460a2173a53d73fb87";kapi_groups="stats,watcher,spender,admin";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}
kapi_id="006";kapi_key="19e121b698014fac638f772c4ff5775a738856bf6cbdef0dc88971059c69da4b";kapi_groups="stats,watcher,spender,admin";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
$(function() {
installation_status();
});
function httpget(url) {
return fetch(url, { method: "GET" })
.catch(err => {
console.log('HTTP GET Error: ' + err.message + ' :: STACK : ' + err.stack);
$("#result").text((JSON.stringify(err.message)));
return Promise.reject(err.message);
})
.then(res => {
if (!res.ok) {
return res.json().then(data => {
console.log('HTTP GET Error: ' + data.error.message);
$("#result").text(JSON.stringify(data.error.message));
return Promise.reject(data.error.message);
});
}
return res.json();
})
.then(data => Promise.resolve(JSON.stringify(data)))
}
function installation_status() {
httpget("installation.json")
.then(result => {
$("#result").text(result);
});
}
</script>
</head>
<body>
<div id="hello">
<h1>Hello World from Cyphernode!</h1>
<h2>If you are here, it means you successfully deployed Cyphernode. Congratulations, fellow Cyphernode Operator!</h2>
</div>
<hr/>
<div id="files">
<h2>The following files have been encrypted with your configuration passphrase and your client keys passphrase, respectively:</h2>
<ul>
<li><a href="config.7z">Download your Cyphernode <b>configurations</b>, can be used for another Cyphernode deployment</a></li>
<li><a href="client.7z">Download Client <b>API ID's and keys</b>, needed in your client apps</a></li>
</ul>
</div>
<div id="Status">
<h2>This is the status of Cyphernode's installation and running components</h2>
<pre lang="xml" id="result" style="white-space: pre-wrap"></pre>
</div>
</body>
</html>

9
api_auth_docker/trace.sh Normal file → Executable file
View File

@@ -3,13 +3,6 @@
trace()
{
if [ -n "${TRACING}" ]; then
echo "[$(date +%Y-%m-%dT%H:%M:%S%z)] ${1}" 1>&2
fi
}
trace_rc()
{
if [ -n "${TRACING}" ]; then
echo "[$(date +%Y-%m-%dT%H:%M:%S%z)] Last return code: ${1}" 1>&2
echo "[$(date +%Y-%m-%dT%H:%M:%S%z)] $$ $*" 2>>/var/log/gatekeeper.log 1>&2
fi
}

View File

@@ -2,6 +2,18 @@
TRACING=1
# CYPHERNODE VERSION "v0.2.4"
CONF_VERSION="v0.2.4-local"
GATEKEEPER_VERSION="v0.2.4-local"
PROXY_VERSION="v0.2.4-local"
NOTIFIER_VERSION="v0.2.4-local"
PROXYCRON_VERSION="v0.2.4-local"
OTSCLIENT_VERSION="v0.2.4-local"
PYCOIN_VERSION="v0.2.4-local"
BITCOIN_VERSION="v0.18.0"
LIGHTNING_VERSION="v0.7.1"
GRAFANA_VERSION="v0.2.4-local"
trace()
{
if [ -n "${TRACING}" ]; then
@@ -16,60 +28,19 @@ trace_rc()
fi
}
build_docker_image() {
local dockerfile="Dockerfile"
if [[ ""$3 != "" ]]; then
dockerfile=$3
fi
trace "building docker image: $2"
#docker build -q $1 -f $1/$dockerfile -t $2:latest > /dev/null
docker build $1 -f $1/$dockerfile -t $2
}
build_docker_images() {
trace "Updating SatoshiPortal repos"
git submodule update --recursive --remote
local bitcoin_dockerfile=Dockerfile.amd64
local clightning_dockerfile=Dockerfile.amd64
local proxy_dockerfile=Dockerfile.amd64
# compat mode for SatoshiPortal repo
# TODO: add more mappings?
if [[ $(uname -m) == 'armv7l' ]]; then
bitcoin_dockerfile="Dockerfile.arm32v6"
clightning_dockerfile="Dockerfile.arm32v6"
proxy_dockerfile="Dockerfile.arm32v6"
fi
trace "Creating cyphernodeconf image"
build_docker_image install/ cyphernode/cyphernodeconf:$CN_VERSION
trace "Creating SatoshiPortal images"
build_docker_image install/SatoshiPortal/dockers/bitcoin-core cyphernode/bitcoin:$BC_VERSION $bitcoin_dockerfile
build_docker_image install/SatoshiPortal/dockers/c-lightning cyphernode/clightning:$CL_VERSION $clightning_dockerfile
docker build cyphernodeconf_docker/ -t cyphernode/cyphernodeconf:$CONF_VERSION
trace "Creating cyphernode images"
build_docker_image api_auth_docker/ cyphernode/gatekeeper:$CN_VERSION
build_docker_image proxy_docker/ cyphernode/proxy:$CN_VERSION $proxy_dockerfile
build_docker_image cron_docker/ cyphernode/proxycron:$CN_VERSION
build_docker_image pycoin_docker/ cyphernode/pycoin:$CN_VERSION
build_docker_image otsclient_docker/ cyphernode/otsclient:$CN_VERSION
docker build api_auth_docker/ -t cyphernode/gatekeeper:$GATEKEEPER_VERSION \
&& docker build proxy_docker/ -t cyphernode/proxy:$PROXY_VERSION \
&& docker build notifier_docker/ -t cyphernode/notifier:$NOTIFIER_VERSION \
&& docker build cron_docker/ -t cyphernode/proxycron:$PROXYCRON_VERSION \
&& docker build pycoin_docker/ -t cyphernode/pycoin:$PYCOIN_VERSION \
&& docker build otsclient_docker/ -t cyphernode/otsclient:$OTSCLIENT_VERSION
}
# CYPHERNODE VERSION
GATEKEEPER_VERSION="latest"
PROXY_VERSION="latest"
PROXYCRON_VERSION="latest"
OTSCLIENT_VERSION="latest"
PYCOIN_VERSION="latest"
BITCOIN_VERSION="latest"
LIGHTNING_VERSION="latest"
build_docker_images

View File

@@ -0,0 +1,12 @@
node_modules
Dockerfile
.dockerignore
.eslintignore
.eslintrc.json
.gitignore
jest.config.js
LICENSE
build.sh
run.sh
jest.config.js
test

View File

@@ -0,0 +1,4 @@
usr
scripts
config
node_modules

View File

@@ -0,0 +1,67 @@
{
"env": {
"es6": true,
"node": true,
"mocha": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"rules": {
"padding-line-between-statements": [
"error",
{
"blankLine": "always",
"prev": "function",
"next": "*"
}
],
"newline-per-chained-call": "error",
"no-tabs": "error",
"no-nested-ternary": "error",
"no-trailing-spaces": "error",
"no-multiple-empty-lines": "error",
"no-whitespace-before-property": "error",
"new-cap": "error",
"lines-around-comment": "error",
"key-spacing": "error",
"comma-style": "error",
"brace-style": "error",
"camelcase": "error",
"handle-callback-err": "error",
"no-new-require": "warn",
"no-sync": "warn",
"no-mixed-requires": "error",
"global-require": "error",
"comma-spacing": "error",
"eqeqeq": "error",
"curly": "error",
"keyword-spacing": "error",
"object-curly-spacing": [
"warn",
"always"
],
"object-curly-newline": [
"warn",
"always"
],
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}

8
cyphernodeconf_docker/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
*~
*.log
*.sw[a-z]
DEADJOE
node_modules
coverage
.DS_Store
.idea

View File

@@ -0,0 +1,16 @@
FROM node:12.2.0-alpine
ENV EDITOR=/usr/bin/nano
COPY . /app
WORKDIR /app
RUN mkdir /data && \
apk add --update su-exec p7zip openssl nano apache2-utils git && \
rm -rf /var/cache/apk/* && \
npm ci --production
WORKDIR /app
ENTRYPOINT ["/sbin/su-exec"]

5
cyphernodeconf_docker/build.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
VERSION=v0.2.4
docker build . -t cyphernode/cyphernodeconf:${VERSION}

View File

@@ -17,7 +17,9 @@
"gatekeeper_edit_apiproperties": "If you know what you are doing, it is possible to manually edit the API endpoints/groups authorization. (Not recommended)",
"gatekeeper_apiproperties": "You are about to edit the api.properties file. The format of the file is pretty simple: for each action, you will find what access group can access it. <font color='# 0000ff'>Admin</font> group can do what <font color='# 0000ff'>Spender</font> group can, and <font color='# 0000ff'>Spender</font> group can do what <font color='# 0000ff'>Watcher</font> group can. <font color='# 0000ff'>Internal</font> group is for the endpoints accessible only within the Docker network, like the backoffice tasks used by the Cron container. The access groups for each API id/key are found in the <font color='# 0000ff'>keys.properties</font> file.",
"gatekeeper_cns": "I use <font underline='true'>domain names</font> and/or <font underline='true'>IP addresses</font> to create valid TLS certificates. For example, if <font color='# 0000ff'>https://cyphernodehost/getbestblockhash</font> and <font color='# 0000ff'>https://192.168.7.44/getbestblockhash</font> will be used, enter <font color='# 0000ff'>cyphernodehost, 192.168.7.44</font> as a possible domains. <font color='# 0000ff'>127.0.0.1, localhost, gatekeeper</font> will be automatically added to your list. Make sure the provided domain names are in your DNS or client's hosts file and is reachable.",
"bitcoin_mode": "Cyphernode can spawn a new <font underline='true'>Bitcoin Core</font> full node for its own use. But if you already have a Bitcoin Core node running, Cyphernode can use that.",
"traefik_datapath": "The Traefik's files will be stored in a container's mounted directory. Please provide the <font underline='true'>local mounted path</font> to that directory. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>",
"traefik_datapath_custom": "Provide the <font underline='true'>full path name</font> where the Traefik's files will be saved.",
"bitcoin_mode": "Cyphernode will spawn a new <font underline='true'>Bitcoin Core</font> full node for its own use. If you already have Bitcoin Core node data, you can use the directory containing that data directly or copy the contents of it to a new directory to be used by cyphernode. Be aware that the files might change ownership, if you run cyphernode as a different user. In case you want to move the blockchain data to another node you might need to change the owner to fit the configuration of that node.",
"bitcoin_node_ip": "Cyphernode uses <font color='#00ff00'>Bitcoin Core</font> RPC interface for its tasks. Please provide the <font underline='true'>IP address</font> of your current Bitcoin Core node.",
"bitcoin_rpcuser": "Bitcoin Core's <font underline='true'>RPC username</font> used by Cyphernode when calling the node.",
"bitcoin_rpcpassword": "Bitcoin Core's <font underline='true'>RPC password</font> used by Cyphernode when calling the node.",
@@ -28,6 +30,7 @@
"bitcoin_datapath_custom": " ",
"bitcoin_expose": "By default, Bitcoin node ports (RPC and protocol) won't be <font underline='true'>published</font> outside of Docker. Do you want to expose them so that your node can be accessed from outside of the Docker network?",
"lightning_implementation": "Multiple <font underline='true'>LN implementations</font> exist. Please choose the one you want to use with Cyphernode.",
"lightning_external_ip": "If you want you LN node to be accessible from the Internet, provide the <font underline='true'>IP address</font> that other LN nodes will use to connect to it. This is usually your router's public IP. <font color='#00ff00'>NOTE</font>: In case you are running Cyphernode at home. This option won't make your router forward needed LN ports; you still need to configure and manage that part yourself in your router configuration.",
"lightning_nodename": "LN nodes have names. Choose the <font underline='true'>name you want</font> for yours.",
"lightning_nodecolor": "LN nodes have colors. Choose the <font underline='true'>color you want</font> for yours in RGB format (RRGGBB). For example, pure red would be <font color='#ff0000'>ff0000</font>.",
"lightning_datapath": "<font underline='true'>Path name</font> to where LN's data files are stored. This directory will be mounted into the LN node's container. <font color='#ff0000'>If running on OSX, check mountable directories in Docker's File Sharing configs.</font>",

View File

@@ -0,0 +1,12 @@
const App = require( './lib/app.js' );
const main = async ( args ) => {
const app = new App();
const noWizard = args.indexOf('recreate') !== -1;
await app.start( {
noWizard: noWizard,
noSplashScreen: noWizard
} );
};
main( process.argv.slice( 2, process.argv.length ) );

View File

@@ -0,0 +1,185 @@
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// Respect "browser" field in package.json when resolving modules
// browser: false,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/fb/q84grm8d0w3gz0y0yx_bvx700000gn/T/jest_dx",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: null,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: [
//"json",
"text",
//"lcov",
//"clover"
],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: null,
// A path to a custom dependency extractor
// dependencyExtractor: null,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files usin a array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: null,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: null,
// A set of global variables that need to be available in all test environments
// globals: {},
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "json",
// "jsx",
// "ts",
// "tsx",
// "node"
// ],
// A map from regular expressions to module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: null,
// Run tests from one or more projects
// projects: null,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: null,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: null,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: "node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: null,
// This option allows use of a custom test runner
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: null,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: null,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};

View File

@@ -0,0 +1,4 @@
module.exports = {
reset: '\u001B8\u001B[u',
clear: '\u001Bc'
};

View File

@@ -1,13 +1,24 @@
const spawn = require('child_process').spawn;
const configEntryRegexp = /^kapi_id="(\w+)";kapi_key="(\w+)";kapi_groups="(.+)";(.+)$/;
module.exports = class ApiKey {
constructor( id, groups, key, script ) {
this.setId(id || '001');
this.setGroups(groups || ['admin'] );
this.setId(id || '000');
this.setGroups(groups || ['stats'] );
this.setScript(script || 'eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}' );
this.setKey(key);
}
setFromConfigEntry(configEntry ) {
const match = configEntryRegexp.exec( configEntry );
if( match ) {
this.setId( match[1] );
this.setKey( match[2] );
this.setGroups( match[3].split(',').map( (e)=>e.trim() ) )
this.setScript( match[4] );
}
}
setGroups( groups ) {
this.groups = groups;
}
@@ -44,15 +55,13 @@ module.exports = class ApiKey {
});
dd.stdout.on('error', function(err) {
console.log(err);
reject(err);
})
});
this.key = result;
} catch( err ) {
console.log( err );
return;
reject(err);
}
}
@@ -71,6 +80,4 @@ module.exports = class ApiKey {
return `${this.id}=${this.key}`;
}
}
//dd if=/dev/urandom bs=32 count=1 2> /dev/null | xxd -pc 32
};

View File

@@ -0,0 +1,646 @@
const {promisify} = require('util');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const wrap = require('wrap-ansi');
const validator = require('validator');
const coinstring = require('coinstring');
const inquirer = require('inquirer');
const colorsys = require('colorsys');
const ejs = require( 'ejs' );
const ejsRenderFileAsync = promisify( ejs.renderFile ).bind( ejs );
const html2ansi = require('./html2ansi.js');
const name = require('./name.js');
const Archive = require('./archive.js');
const ApiKey = require('./apikey.js');
const Cert = require('./cert.js');
const htpasswd = require( './htpasswd.js');
const Config = require('./config.js');
const SplashScreen = require( './splashScreen.js' );
const ansi = require( './ansi.js' );
const features = require('../features.json');
const uaCommentRegexp = /^[a-zA-Z0-9 \.,:_\-\?\/@]+$/; // TODO: look for spec of unsafe chars
const userRegexp = /^[a-zA-Z0-9\._\-]+$/;
const maxWidth = 82;
const keyIds = {
'000': ['stats'],
'001': ['stats','watcher'],
'002': ['stats','watcher','spender'],
'003': ['stats','watcher','spender','admin']
};
const configArchiveFileName = 'config.7z';
const keyArchiveFileName = 'client.7z';
const destinationDirName = '.cyphernodeconf';
const prefix = () => {
return chalk.green('Cyphernode')+': ';
};
const randomColor = () => {
const hex = colorsys.hslToHex( { h: (Math.random()*360)<<0, s: 50, l: 50 } );
return hex.substr(1);
};
let prompters = [];
fs.readdirSync(path.join(__dirname, '..','prompters')).forEach((file) => {
prompters.push(require(path.join(__dirname, '..','prompters',file)));
});
module.exports = class App {
constructor() {
this.features = features;
if( fs.existsSync(path.join('/data', destinationDirName, 'exitStatus.sh')) ) {
fs.unlinkSync(path.join('/data', destinationDirName, 'exitStatus.sh'));
}
this.splash = new SplashScreen( {
frameDir: path.join(__dirname, '..', 'splash' ),
enableFortune: false,
fortuneChalk: chalk.default.bold,
width: maxWidth,
fortuneSpacing: 3
} );
}
async start( options ) {
options = options || {};
this.sessionData = {
defaultDataDirBase: process.env.DEFAULT_DATADIR_BASE || process.env.HOME,
setupDir: process.env.SETUP_DIR || path.join( process.env.HOME, 'cyphernode' ),
default_username: process.env.DEFAULT_USER || '',
gatekeeper_version: process.env.GATEKEEPER_VERSION,
gatekeeper_cns: process.env.DEFAULT_CERT_HOSTNAME,
proxy_version: process.env.PROXY_VERSION,
proxycron_version: process.env.PROXYCRON_VERSION,
pycoin_version: process.env.PYCOIN_VERSION,
otsclient_version: process.env.OTSCLIENT_VERSION,
bitcoin_version: process.env.BITCOIN_VERSION,
lightning_version: process.env.LIGHTNING_VERSION,
notifier_version: process.env.NOTIFIER_VERSION,
setup_version: process.env.SETUP_VERSION,
noWizard: !!options.noWizard,
noSplashScreen: !!options.noSplashScreen,
lightning_nodename: name.generate(),
lightning_nodecolor: randomColor(),
installer_cleanup: false,
devmode: process.env.DEVMODE || false
};
await this.setupConfigArchive();
if( !this.sessionData.noSplashScreen ) {
await this.splash.show();
}
let missingProperties = [];
if( this.config.validateErrors && this.config.validateErrors.length ) {
for( let error of this.config.validateErrors ) {
if( error.keyword === 'required' && error.params && error.params.missingProperty ) {
missingProperties.push( error.params.missingProperty );
}
}
}
if( this.sessionData.noWizard && missingProperties.length && this.config.isLoaded ) {
console.log(chalk.bold.red('Unable to migrate client.7z non-interactively. Rerun without the -r option') );
process.exit(1);
}
if( !this.sessionData.noWizard ) {
// save gatekeeper key password to check if it changed
this.sessionData.gatekeeper_clientkeyspassword = this.config.data.gatekeeper_clientkeyspassword;
if( missingProperties.length && this.config.isLoaded ) {
this.sessionData.markProperties = missingProperties;
}
await this.startWizard();
}
await this.processProps();
await this.writeFiles();
}
async setupConfigArchive() {
this.config = new Config( {
setup_version: this.sessionData.setup_version,
docker_versions: {
'cyphernode/bitcoin': this.sessionData.bitcoin_version,
'cyphernode/gatekeeper': this.sessionData.gatekeeper_version,
'cyphernode/proxy': this.sessionData.proxy_version,
'cyphernode/proxycron': this.sessionData.proxycron_version,
'cyphernode/pycoin': this.sessionData.pycoin_version,
'cyphernode/otsclient': this.sessionData.otsclient_version,
'cyphernode/clightning': this.sessionData.lightning_version,
'cyphernode/notifier': this.sessionData.notifier_version,
'traefik': 'v1.7.9-alpine',
'eclipse-mosquitto': '1.6'
}
} );
if( !fs.existsSync(this.destinationPath(configArchiveFileName)) ) {
let r = {};
process.stdout.write(ansi.clear+ansi.reset);
while( !r.password0 || !r.password1 || r.password0 !== r.password1 ) {
if( r.password0 && r.password1 && r.password0 !== r.password1 ) {
console.log(chalk.bold.red('Passwords do not match')+'\n');
}
r = await this.prompt([{
type: 'password',
name: 'password0',
message: prefix()+chalk.bold.blue('Choose your configuration password'),
filter: this.trimFilter
},
{
type: 'password',
name: 'password1',
message: prefix()+chalk.bold.blue('Confirm your configuration password'),
filter: this.trimFilter
}]);
}
this.sessionData.configurationPassword = r.password0;
} else {
try {
let r = {};
if( process.env.CFG_PASSWORD ) {
this.sessionData.configurationPassword = process.env.CFG_PASSWORD;
} else {
process.stdout.write(ansi.reset);
while( !r.password ) {
r = await this.prompt([{
type: 'password',
name: 'password',
message: prefix()+chalk.bold.blue('Enter your configuration password?'),
filter: this.trimFilter
}]);
}
this.sessionData.configurationPassword = r.password;
}
try {
await this.config.deserialize(
this.destinationPath(configArchiveFileName),
this.sessionData.configurationPassword,
);
// store clientkeyspassword in sessionData so it can be retrieved by getDefault
// and a simple return will not result in a password mismatch
if( this.config.data.hasOwnProperty('gatekeeper_clientkeyspassword') ) {
this.sessionData.gatekeeper_clientkeyspassword_c =
this.config.data.gatekeeper_clientkeyspassword;
}
} catch (e) {
console.log(chalk.bold.red(e));
process.exit();
}
} catch( err ) {
console.log(chalk.bold.red('config archive is corrupt.'));
process.exit(1);
}
}
this.config.data.adminhash = await htpasswd(this.sessionData.configurationPassword);
for( let feature of this.features ) {
feature.checked = this.isChecked( 'features', feature.value );
}
}
async startWizard() {
let r = await this.prompt([{
type: 'confirm',
name: 'enablehelp',
message: prefix()+'Enable help?',
default: this.getDefault( 'enablehelp' ),
}]);
this.config.data.enablehelp = r.enablehelp;
if( this.config.data.enablehelp ) {
this.help = require('../help.json');
}
let prompts = [];
for( let m of prompters ) {
let newPrompts = m.prompts(this);
if( this.sessionData.markProperties &&
this.sessionData.markProperties.length &&
this.config.isLoaded ) {
for( let prompt of newPrompts ) {
if( this.sessionData.markProperties.indexOf(prompt.name) !== -1 ) {
prompt.message = prompt.message+' '+chalk.bgGreen('new option');
}
}
}
prompts = prompts.concat(newPrompts);
}
const props = await this.prompt(prompts);
this.config.data = Object.assign(this.config.data, props);
}
async processProps() {
// creates keys if they don't exist or we say so.
if( this.config.data.gatekeeper_recreatekeys ||
this.config.data.gatekeeper_keys.configEntries.length===0 ) {
delete this.config.data.gatekeeper_recreatekeys;
let configEntries = [];
let clientInformation = [];
for( let keyId in keyIds ) {
const apikey = await this.createRandomKey( keyId, keyIds[keyId] );
configEntries.push(apikey.getConfigEntry());
clientInformation.push(apikey.getClientInformation());
}
this.config.data.gatekeeper_keys = {
configEntries: configEntries,
clientInformation: clientInformation
}
}
const cert = new Cert();
this.sessionData.cns = cert.cns(this.config.data.gatekeeper_cns);
// create certs if they don't exist or we say so.
if( this.config.data.gatekeeper_recreatecert ||
!this.config.data.gatekeeper_sslcert ||
!this.config.data.gatekeeper_sslkey ) {
delete this.config.data.gatekeeper_recreatecert;
const cert = new Cert();
console.log(chalk.bold.green( '☕ Generating gatekeeper cert. This may take a while ☕' ));
try {
const result = await cert.create(this.sessionData.cns);
if( result.code === 0 ) {
this.config.data.gatekeeper_sslkey = result.key.toString();
this.config.data.gatekeeper_sslcert = result.cert.toString();
} else {
console.log(chalk.bold.red( 'error! Gatekeeper cert was not created' ));
}
} catch( err ) {
console.log(chalk.bold.red( 'error! Gatekeeper cert was not created' ));
}
}
}
async createRandomKey( id, groups ) {
if( !id || !groups || !groups.length ) {
return;
}
const apikey = new ApiKey();
apikey.setId(id);
apikey.setGroups(groups);
await apikey.randomiseKey();
return apikey
}
async writeFiles() {
console.log( chalk.green( ' create' )+' '+configArchiveFileName );
if( !this.config.serialize(
this.destinationPath(configArchiveFileName),
this.sessionData.configurationPassword
) ) {
console.log(chalk.bold.red( 'error! Config archive was not written' ));
}
const pathProps = [
'gatekeeper_datapath',
'traefik_datapath',
'proxy_datapath',
'bitcoin_datapath',
'lightning_datapath',
'otsclient_datapath'
];
for( let pathProp of pathProps ) {
if( this.config.data[pathProp] === '_custom' ) {
this.config.data[pathProp] = this.config.data[pathProp+'_custom'] || '';
}
}
this.sessionData.installationInfo = this.installationInfo();
for( let m of prompters ) {
const name = m.name();
for( let t of m.templates(this.config.data) ) {
const p = path.join(name,t);
const destFile = this.destinationPath( path.join( destinationDirName, p ) );
const targetDir = path.dirname( destFile );
if( !fs.existsSync(targetDir) ) {
fs.mkdirSync(targetDir, { recursive: true });
}
const result = await ejsRenderFileAsync( this.templatePath(p), Object.assign({}, this.sessionData, this.config.data), {} );
console.log( chalk.green( ' create' )+' '+p );
fs.writeFileSync( destFile, result );
}
}
console.log( chalk.green( ' create' )+' '+keyArchiveFileName );
if( this.config.data.gatekeeper_keys && this.config.data.gatekeeper_keys.clientInformation ) {
if( this.sessionData.gatekeeper_clientkeyspassword !== this.config.data.gatekeeper_clientkeyspassword &&
fs.existsSync(this.destinationPath(keyArchiveFileName)) ) {
fs.unlinkSync( this.destinationPath(keyArchiveFileName) );
}
const archive = new Archive( this.destinationPath(keyArchiveFileName), this.config.data.gatekeeper_clientkeyspassword );
if( !await archive.writeEntry( 'keys.txt', this.config.data.gatekeeper_keys.clientInformation.join('\n') ) ) {
console.log(chalk.bold.red( 'error! Client gatekeeper key archive was not written' ));
}
if( !await archive.writeEntry( 'cacert.pem', this.config.data.gatekeeper_sslcert ) ) {
console.log(chalk.bold.red( 'error! Client gatekeeper key archive was not written' ));
}
}
fs.writeFileSync(path.join('/data', destinationDirName, 'exitStatus.sh'), 'EXIT_STATUS=0');
}
installationInfo() {
for( let feature of this.features ) {
feature.checked = this.isChecked( 'features', feature.value );
}
const cert = new Cert();
const gatekeeper_cns = cert.cns( this.config.data.gatekeeper_cns );
const features = [
{
name: 'Bitcoin core node',
label: 'bitcoin',
host: 'bitcoin',
networks: ['cyphernodenet'],
docker: 'cyphernode/bitcoin:'+this.config.docker_versions['cyphernode/bitcoin'],
extra: {
prune: this.config.data.bitcoin_prune,
prune_size: this.config.data.bitcoin_prune_size,
expose: this.config.data.bitcoin_expose,
uacomment: this.config.data.bitcoin_uacomment
}
},
{
name: 'Gatekeeper',
label: 'gatekeeper',
host: 'gatekeeper',
networks: ['cyphernodenet'],
docker: 'cyphernode/gatekeeper:'+this.config.docker_versions['cyphernode/gatekeeper'],
extra: {
port: this.config.data.gatekeeper_port,
cns: gatekeeper_cns
}
},
{
name: 'Proxy',
label: 'proxy',
host: 'proxy',
networks: ['cyphernodenet'],
docker: 'cyphernode/proxy:'+this.config.docker_versions['cyphernode/proxy']
},
{
name: 'Proxy cron',
label: 'proxycron',
host: 'proxycron',
networks: ['cyphernodenet'],
docker: 'cyphernode/proxycron:'+this.config.docker_versions['cyphernode/proxycron']
},
{
name: 'Pycoin',
label: 'pycoin',
host: 'pycoin',
networks: ['cyphernodenet'],
docker: 'cyphernode/pycoin:'+this.config.docker_versions['cyphernode/pycoin']
},
{
name: 'Notifier',
label: 'notifier',
host: 'notifier',
networks: ['cyphernodenet', 'cyphernodeappsnet'],
docker: 'cyphernode/notifier:'+this.config.docker_versions['cyphernode/notifier']
},
{
name: 'MQ broker',
label: 'broker',
host: 'broker',
networks: ['cyphernodenet'],
docker: 'eclipse-mosquitto:'+this.config.docker_versions['eclipse-mosquitto']
}
];
const optional_features = [];
const optional_features_data = {
otsclient: {
docker: "cyphernode/otsclient:" + this.config.docker_versions['cyphernode/otsclient']
},
lightning: {
docker: "cyphernode/clightning:"+this.config.docker_versions['cyphernode/clightning'],
extra: {
nodename: this.config.data.lightning_nodename,
nodecolor: this.config.data.lightning_nodecolor,
expose: this.config.data.lightning_expose,
external_ip: this.config.data.lightning_external_ip,
implementation: this.config.data.lightning_implementation
}
}
}
for( let feature of this.features ) {
const f = {
active: feature.checked,
name: feature.name,
label: feature.value,
host: feature.value,
networks: ['cyphernodenet'],
docker: optional_features_data[feature.value].docker
};
if( feature.checked ) {
f.extra = optional_features_data[feature.value].extra
}
optional_features.push( f );
}
let bitcoin_version = this.config.docker_versions['cyphernode/bitcoin'];
if( bitcoin_version[0] === 'v' ) {
bitcoin_version = bitcoin_version.substr(1);
}
const ii = {
api_versions: ['v0'],
setup_version: this.config.setup_version,
bitcoin_version: bitcoin_version,
features: features,
optional_features: optional_features,
devmode: this.sessionData.devmode,
};
return ii;
}
async prompt( questions ) {
if( !questions ) {
return {};
}
const r = await inquirer.prompt( questions );
return r;
}
/* some utils */
destinationPath( relPath ) {
return path.join( '/data', relPath );
}
templatePath( relPath ) {
return path.join(__dirname, '..','templates',relPath );
}
isChecked(name, value ) {
return this.config.data && this.config.data[name] && this.config.data[name].indexOf(value) != -1 ;
}
getDefault(name) {
if( this.config && this.config.data && this.config.data.hasOwnProperty(name) ) {
return this.config.data[name];
}
if( this.sessionData && this.sessionData.hasOwnProperty(name) ) {
return this.sessionData[name];
}
}
optional(input, validator) {
if( input === undefined ||
input === null ||
input === '' ) {
return true;
}
return validator(input);
}
ipOrFQDNValidator(host ) {
host = (host+"").trim();
if( !(validator.isIP(host) ||
validator.isFQDN(host)) ) {
throw new Error( 'No IP address or fully qualified domain name' )
}
return true;
}
xkeyValidator(xpub ) {
// TOOD: check for version
if( !coinstring.isValid( xpub ) ) {
throw new Error('Not an extended key.');
}
return true;
}
pathValidator(p ) {
return true;
}
derivationPathValidator(path ) {
return true;
}
colorValidator(color) {
if( !validator.isHexadecimal(color) ) {
throw new Error('Not a hex color.');
}
return true;
}
lightningNodeNameValidator(name) {
if( !name || name.length > 32 ) {
throw new Error('Please enter anything shorter than 32 characters');
}
return true;
}
notEmptyValidator(path ) {
if( !path ) {
throw new Error('Please enter something');
}
return true;
}
usernameValidator(user ) {
if( !userRegexp.test( user ) ) {
throw new Error('Choose a valid username');
}
return true;
}
UACommentValidator(comment ) {
if( !uaCommentRegexp.test( comment ) ) {
throw new Error('Unsafe characters in UA comment. Please use only a-z, A-Z, 0-9, SPACE and .,:_?@');
}
return true;
}
trimFilter(input ) {
return (input+"").trim();
}
featureChoices() {
return this.features;
}
setupDir() {
return this.sessionData.setupDir;
}
defaultDataDirBase() {
return this.sessionData.defaultDataDirBase;
}
getHelp(topic ) {
if( !this.config.data.enablehelp || !this.help ) {
return '';
}
const helpText = this.help[topic] || this.help['__default__'];
if( !helpText ||helpText === '' ) {
return '';
}
return "\n\n"+wrap( html2ansi(helpText),maxWidth )+"\n\n";
}
};

View File

@@ -1,4 +1,3 @@
const fs = require('fs');
const spawn = require('child_process').spawn;
const stringio = require('@rauschma/stringio');
const defaultArgs = ['-t7z', '-ms=on', '-mhe=on'];
@@ -16,7 +15,7 @@ module.exports = class Archive {
}
let args = defaultArgs.slice();
args.unshift('x');
args.push( '-so' );
args.push( '-so' );
if( this.password ) {
args.push('-p'+this.password );
}
@@ -41,7 +40,7 @@ module.exports = class Archive {
if( this.password ) {
args.push('-p'+this.password );
}
args.push( '-si'+entryName );
args.push( '-si'+entryName );
args.push( this.file )
const archiver = spawn('7z', args, { stdio: ['pipe', 'ignore', 'ignore' ] } );
await stringio.streamWrite(archiver.stdin, content);

View File

@@ -20,7 +20,9 @@ subjectAltName = @alt_names
`;
const domainTmpl = 'DNS.%#% = %DOMAIN%';
const ipTmpl = 'IP.%#% = %IP%'
const ipTmpl = 'IP.%#% = %IP%';
const standardCNs = ['127.0.0.1','localhost','gatekeeper'];
module.exports = class Cert {
@@ -29,6 +31,18 @@ module.exports = class Cert {
this.args = options.args || { days: 3650 };
}
cns( cnsString ) {
if( !cnsString ) {
return standardCNs;
}
const cns = cnsString.split(',').map(e=>e.trim().toLowerCase()).filter(e=>!!e);
if( cns.length ) {
return standardCNs.concat(cns);
}
return standardCNs;
}
buildConfig( cns ) {
let ips = [];
@@ -62,9 +76,9 @@ module.exports = class Cert {
}
async create( cns ) {
cns = cns || [];
cns = cns.concat(['127.0.0.1','localhost','gatekeeper']);
if (!cns || !cns.length) {
throw 'No cns';
}
let args = defaultArgs.slice();
@@ -87,9 +101,9 @@ module.exports = class Cert {
const conf = this.buildConfig( cns );
fs.writeFileSync( confFileTmp.name, conf );
const openssl = spawn('openssl', args, { stdio: ['ignore', 'ignore', 'ignore'] } );
let code = await new Promise( function(resolve, reject) {
const openssl = spawn('openssl', args, { stdio: ['ignore', 'ignore', 'ignore'] } );
openssl.on('exit', (code) => {
resolve(code);
});
@@ -105,29 +119,7 @@ module.exports = class Cert {
return {
code: code,
key: key,
cert: cert,
cns: cns
cert: cert
}
}
getFullPath() {
return path.join( this.folder, this.filename );
}
async passwd( pw ) {
const openssl = spawn('openssl', [ "passwd", pw ], {stdio: ['ignore', 'pipe', 'ignore' ]});
const result = await new Promise( function(resolve, reject ) {
let result = '';
openssl.stdout.on('data', (data) => {
result += data.toString();
});
openssl.on('exit', (code) => {
resolve(result);
});
});
return result;
}
}
};

View File

@@ -0,0 +1,220 @@
const Ajv = require('ajv');
const fs = require('fs');
const Archive = require('./archive.js');
const ApiKey = require('./apikey.js');
const name = require('./name.js');
const colorsys = require( 'colorsys');
const schemas = {
'0.1.0': require('../schema/config-v0.1.0.json'),
'0.2.0': require('../schema/config-v0.2.0.json'),
'0.2.2': require('../schema/config-v0.2.2.json')
};
const versionHistory = [ '0.1.0', '0.2.0', '0.2.2' ];
const defaultSchemaVersion=versionHistory[0];
const latestSchemaVersion=versionHistory[versionHistory.length-1];
module.exports = class Config {
constructor( options ) {
options = options || {};
this.setup_version = options.setup_version;
this.docker_versions = options.docker_versions;
const ajv = new Ajv({
removeAdditional: true,
useDefaults: true,
coerceTypes: true,
allErrors: true
});
this.validators = {};
for( let v in schemas ) {
this.validators[v]=ajv.compile(schemas[v]);
}
this.migrations = {
'0.1.0->0.2.0': this.migrate_0_1_0_to_0_2_0,
'0.2.0->0.2.2': this.migrate_0_2_0_to_0_2_2
};
this.setData( { schema_version: latestSchemaVersion } );
this.isLoaded = false;
}
generateMigrationPathToLatest( currentVersion ) {
if( currentVersion === latestSchemaVersion ) {
return;
}
const index = versionHistory.indexOf( currentVersion );
if( index === -1 ) {
return;
}
let path = [];
for( let i=index; i<versionHistory.length-1; i++ ) {
const methodLabel = versionHistory[i]+'->'+versionHistory[i+1];
path.push( this.migrations[methodLabel] );
}
return path;
}
setData( data ) {
if( !data ) {
return;
}
this.data = data;
this.data.schema_version = this.data.schema_version || this.__version || defaultSchemaVersion;
this.data.setup_version = this.data.setup_version || this.setup_version;
this.data.docker_versions = this.data.docker_versions || this.docker_versions;
this.validate();
}
async serialize( path, password ) {
this.resolveConfigConflicts();
this.validate();
const configJsonString = JSON.stringify(this.data, null, 4);
const archive = new Archive( path, password );
return archive.writeEntry( 'config.json', configJsonString );
}
async deserialize( path, password ) {
if( fs.existsSync(path) ) {
const archive = new Archive( path, password );
const r = await archive.readEntry('config.json');
if( r.error ) {
throw( 'Password is wrong. Have a nice day.' );
}
if( !r.value ) {
throw('config archive is corrupt.');
}
this.setData( JSON.parse(r.value) );
this.isLoaded = true;
}
//this.resolveConfigConflicts();
const version = this.data.schema_version || this.data.__version || defaultSchemaVersion;
if( version !== latestSchemaVersion ) {
// migrate here
// create a copy of the old config
fs.copyFileSync( path, path+'-'+version );
await this.migrateFrom(version);
// validate again to strip all illegal properties from config with latest version
this.validate();
}
}
resolveConfigConflicts() {
// TODO solve this in config schema
if( this.data.features && this.data.features.length && this.data.features.indexOf('lightning') !== -1 ) {
this.data.bitcoin_prune = false;
delete this.data.bitcoin_prune_size;
}
}
validate() {
const version = this.data.schema_version || this.data.__version;
if( !version || !this.validators[version] || Object.keys( schemas ).indexOf( version ) == -1 ) {
throw "Unknown version in data"
}
// this will assign default values from the schema
this.valid = this.validators[version]( this.data );
this.validateErrors = this.validators[version].errors;
}
async migrateFrom(sourceVersion) {
const migrations = this.generateMigrationPathToLatest(sourceVersion)
if( !migrations ) {
return;
}
for( let migration of migrations ) {
await migration.apply(this);
}
}
// migration methods:
async migrate_0_1_0_to_0_2_0() {
const currentVersion = this.data.schema_version || this.data.__version;
if( currentVersion != '0.1.0' ) {
return;
}
this.data.schema_version = '0.2.0';
// rewrite specific properties with incompatible content
// gatekeeper_keys: add stats group to all keys and add a label containing only
// the stats group
const gatekeeper_keys = this.data.gatekeeper_keys;
for( let i=0; i<gatekeeper_keys.configEntries.length; i++ ) {
const apiKey = new ApiKey();
apiKey.setFromConfigEntry(gatekeeper_keys.configEntries[i]);
apiKey.groups.unshift('stats');
gatekeeper_keys.configEntries[i]=apiKey.getConfigEntry();
gatekeeper_keys.clientInformation[i]=apiKey.getClientInformation();
}
const apiKeyStatsOnly = new ApiKey('000',['stats']);
await apiKeyStatsOnly.randomiseKey();
gatekeeper_keys.configEntries.unshift( apiKeyStatsOnly.getConfigEntry() );
gatekeeper_keys.clientInformation.unshift( apiKeyStatsOnly.getClientInformation() );
// remove all empty props to generate proper errors
for( let k in this.data ) {
if( !this.data.hasOwnProperty(k) ) {
continue;
}
if( this.data[k] === '' ) {
delete this.data[k];
}
}
// lightning_nodecolor
if( !this.data.lightning_nodecolor ) {
this.data.lightning_nodecolor =
colorsys.hslToHex( { h: (Math.random()*360)<<0, s: 50, l: 50 } ).substr(1);
}
// lightning_nodename
if( !this.data.lightning_nodename ) {
this.data.lightning_nodename = name.generate();
}
// xpub && use_xpub
this.data.use_xpub = !!this.data.xpub;
}
async migrate_0_2_0_to_0_2_2() {
const currentVersion = this.data.schema_version;
if( currentVersion != '0.2.0' ) {
return;
}
this.data.schema_version = '0.2.2';
// create identical behaviour to 0.2.0 config version
this.data.lightning_announce = !!this.data.lightning_external_ip;
this.data.gatekeeper_expose = true;
}
};

View File

@@ -0,0 +1,23 @@
const exec = require('child_process').exec;
module.exports = async ( password ) => {
if( !password ) {
return null;
}
password = password.replace(/'/g, `'\\''`);
return await new Promise( (resolve) => {
exec('htpasswd -bnB admin \''+password+'\' | cut -sd \':\' -f2', (error, stdout, stderr) => {
if (error) {
return resolve(null);
}
// remove newline at the end
resolve(stdout.substr(0,stdout.length-1));
});
});
};

View File

@@ -1,4 +1,4 @@
const MAXLENGTH = 30;
const MAXLENGTH = 28;
const ADJECTIVES = [
/*a*/ ["Abiding",

View File

@@ -0,0 +1,135 @@
const fs = require('fs');
const path = require('path');
const ansi = require( './ansi.js' );
const lunicode = require('lunicode');
const easeOutCubic = (t, b, c, d) => {
return c*((t=t/d-1)*t*t+1)+b;
};
lunicode.tools.creepify.options.top = true; // add diacritics on top. Default: true
lunicode.tools.creepify.options.middle = true; // add diacritics in the middle. Default: true
lunicode.tools.creepify.options.bottom = true; // add diacritics on the bottom. Default: true
lunicode.tools.creepify.options.maxHeight = 15; // How many diacritic marks shall we put on top/bottom? Default: 15
lunicode.tools.creepify.options.randomization = 100; // 0-100%. maxHeight 100 and randomization 20%: the height goes from 80 to 100. randomization 70%: height goes from 30 to 100. Default: 100
const fortunes = [
'Cause fuck central banking',
'Not your keys, not your bitcoin',
'Don\'t trust, verify',
'Craig Wright is a fraud',
'HODL!'
];
module.exports = class SplashScreen {
constructor( options ) {
options = options || {};
if( !options.frameDir ) {
throw "no frame directory to load"
}
this.width = options.width || 82;
this.fortuneEnabled = !!options.enableFortune;
this.fortuneSpacing = options.fortuneSpacing || 0;
this.fortuneChalk = options.fortuneChalk;
this.loadFramesFromDir( options.frameDir );
if( this.fortuneEnabled ) {
let fortune = this.fortune();
if( fortune.length > this.width-2 ) {
fortune = fortune.substr(0,this.width-2);
}
fortune = this.center(fortune);
let fortuneLines = [];
fortuneLines.push( this.creepify(fortune) )+'\n';
for( let i=0; i<this.frames.length; i++ ) {
for( let j=0; j<fortuneLines.length; j++ ) {
this.frames[i] += fortuneLines[j];
}
}
}
}
loadFramesFromDir( frameDir ) {
this.frames = [];
fs.readdirSync(frameDir).forEach((file) => {
this.frames.push(fs.readFileSync(path.join(__dirname,'..','splash',file)));
});
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
fortune() {
return fortunes[ Math.random()*fortunes.length << 0 ];
}
creepify( string ) {
if( this.fortuneChalk ) {
return this.fortuneChalk(lunicode.tools.creepify.encode( string ));
}
return lunicode.tools.creepify.encode( string );
}
center( string ) {
const offset = ((this.width - string.length)*0.5) << 0;
for( let i=0; i<offset; i++ ) {
string = ' '+string+' ';
}
return string;
}
async show() {
const frame0 = this.frames[0];
const frame0lines = frame0.toString().split('\n');
const frame0lineCount = frame0lines.length;
const steps = 10;
await this.sleep(250);
process.stdout.write(ansi.clear);
await this.sleep(150);
for( let i=0; i<=steps; i++ ) {
const pos = easeOutCubic( i, 0, frame0lineCount, steps ) | 0;
process.stdout.write(ansi.reset);
for( let l=frame0lineCount-pos; l<frame0lineCount; l++ ) {
process.stdout.write( frame0lines[l]+'\n' );
}
await this.sleep(33);
}
if( this.frames.length > 1 ) {
await this.sleep(400);
for( let frame of this.frames ) {
process.stdout.write(ansi.reset);
process.stdout.write(frame.toString());
await this.sleep(33);
}
}
await this.sleep(400);
process.stdout.write('\n');
}
};

5508
cyphernodeconf_docker/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
{
"name": "cyphernodeconf",
"version": "0.0.0",
"description": "",
"homepage": "",
"scripts": {
"test": "jest",
"coverageHtml": "jest --coverage --coverageReporters html",
"lint": "./node_modules/.bin/eslint lib/**/*.js",
"lintfix": "./node_modules/.bin/eslint lib/**/**.js --fix"
},
"author": "jash <jash@schulterklopfer-productions.de>",
"main": "lib/index.js",
"keywords": [
"cyphernode"
],
"dependencies": {
"@rauschma/stringio": "^1.4.0",
"ajv": "^6.10.0",
"chalk": "^2.4.2",
"coinstring": "^2.3.0",
"colorsys": "^1.0.22",
"ejs": "^2.6.1",
"inquirer": "^6.3.1",
"lunicode": "^2.0.1",
"parse5": "^5.1.0",
"tmp": "^0.1.0",
"validator": "^10.11.0",
"wrap-ansi": "^5.1.0"
},
"devDependencies": {
"eslint": "^5.16.0",
"jest": "^24.8.0"
},
"repository": "git@github.com:schulterklopfer/cyphernodeconf.git",
"license": "MIT"
}

View File

@@ -20,27 +20,30 @@ module.exports = {
// input, confirm, list, rawlist, expand, checkbox, password, editor
type: 'checkbox',
name: 'features',
message: prefix()+'What features do you want to add to your cyphernode?'+utils._getHelp('features'),
choices: utils._featureChoices()
message: prefix()+'What features do you want to add to your cyphernode?'+utils.getHelp('features'),
choices: utils.featureChoices()
},
{
type: 'list',
name: 'net',
default: utils._getDefault( 'net' ),
message: prefix()+'What net do you want to run on?'+utils._getHelp('net'),
default: utils.getDefault( 'net' ),
message: prefix()+'What net do you want to run on?'+utils.getHelp('net'),
choices: [{
name: "Testnet",
value: "testnet"
},{
name: "Mainnet",
value: "mainnet"
},{
name: "RegTest",
value: "regtest"
}]
},
{
type: 'confirm',
name: 'run_as_different_user',
default: utils._getDefault( 'run_as_different_user' ),
message: prefix()+'Run as different user?'+utils._getHelp('run_as_different_user')
default: utils.getDefault( 'run_as_different_user' ),
message: prefix()+'Run as different user?'+utils.getHelp('run_as_different_user')
},
{
when: function( props ) {
@@ -48,16 +51,16 @@ module.exports = {
},
type: 'input',
name: 'username',
default: utils._getDefault( 'username' ),
message: prefix()+'What username will cyphernode run under?'+utils._getHelp('username'),
filter: utils._trimFilter,
validate: utils._usernameValidator
default: utils.getDefault( 'username' ),
message: prefix()+'What username will cyphernode run under?'+utils.getHelp('username'),
filter: utils.trimFilter,
validate: utils.usernameValidator
},
{
type: 'confirm',
name: 'use_xpub',
default: utils._getDefault( 'use_xpub' )||false,
message: prefix()+'Use a default xpub key to watch or generate adresses?'+utils._getHelp('use_xpub'),
default: utils.getDefault( 'use_xpub' )||false,
message: prefix()+'Use a default xpub key to watch or generate adresses?'+utils.getHelp('use_xpub'),
},
{
when: function( props ) {
@@ -65,10 +68,10 @@ module.exports = {
},
type: 'input',
name: 'xpub',
default: utils._getDefault( 'xpub' ),
message: prefix()+'What is your default xpub key?'+utils._getHelp('xpub'),
filter: utils._trimFilter,
validate: utils._xkeyValidator
default: utils.getDefault( 'xpub' ),
message: prefix()+'What is your default xpub key?'+utils.getHelp('xpub'),
filter: utils.trimFilter,
validate: utils.xkeyValidator
},
{
when: function( props ) {
@@ -76,13 +79,13 @@ module.exports = {
},
type: 'input',
name: 'derivation_path',
default: utils._getDefault( 'derivation_path' ),
message: prefix()+'What is your default derivation path?'+utils._getHelp('derivation_path'),
filter: utils._trimFilter,
validate: utils._derivationPathValidator
default: utils.getDefault( 'derivation_path' ),
message: prefix()+'What is your default derivation path?'+utils.getHelp('derivation_path'),
filter: utils.trimFilter,
validate: utils.derivationPathValidator
}];
},
templates: function( props ) {
return [];
return ['info.json'];
}
};

View File

@@ -0,0 +1,95 @@
const chalk = require('chalk');
const name = 'gatekeeper';
const capitalise = function( txt ) {
return txt.charAt(0).toUpperCase() + txt.substr(1);
};
const prefix = function() {
return chalk.green(capitalise(name)+': ');
};
const hasAuthKeys = function( props ) {
return props &&
props.gatekeeper_keys &&
props.gatekeeper_keys.configEntries &&
props.gatekeeper_keys.configEntries.length > 0;
}
const hasCert = function( props ) {
return props &&
props.gatekeeper_sslkey &&
props.gatekeeper_sslcert
}
let password = '';
module.exports = {
name: function() {
return name;
},
prompts: function( utils ) {
// TODO: delete clientKeys archive when password chnages
return [{
type: 'password',
name: 'gatekeeper_clientkeyspassword',
default: utils.getDefault( 'gatekeeper_clientkeyspassword' ),
message: prefix()+'Enter a password to protect your client keys with'+utils.getHelp('gatekeeper_clientkeyspassword'),
filter: utils.trimFilter,
validate: utils.notEmptyValidator
},
{
when: function( props ) {
// hacky hack
password = props.gatekeeper_clientkeyspassword;
return true;
},
type: 'password',
name: 'gatekeeper_clientkeyspassword_c',
default: utils.getDefault( 'gatekeeper_clientkeyspassword_c' ),
message: prefix()+'Confirm your client keys password.'+utils.getHelp('gatekeeper_clientkeyspassword_c'),
filter: utils.trimFilter,
validate: function( input ) {
if(input !== password) {
throw new Error( 'Client keys passwords do not match' );
}
return true;
}
},
{
type: 'input',
name: 'gatekeeper_port',
default: utils.getDefault( 'gatekeeper_port' ),
message: prefix()+'The port gatekeeper will listen on for requests'+utils.getHelp('gatekeeper_port'),
filter: utils.trimFilter,
validate: function( port ) {
return utils.notEmptyValidator( port ) && !isNaN( parseInt(port) )
}
},
{
when: function() { return hasAuthKeys( utils.props ); },
type: 'confirm',
name: 'gatekeeper_recreatekeys',
default: false,
message: prefix()+'Recreate gatekeeper keys?'+utils.getHelp('gatekeeper_recreatekeys')
},
{
when: function() { return hasCert( utils.props ); },
type: 'confirm',
name: 'gatekeeper_recreatecert',
default: false,
message: prefix()+'Recreate gatekeeper certificate?'+utils.getHelp('gatekeeper_recreatecert')
},
{
when: function(props) { return !hasCert( utils.props ) || props.gatekeeper_recreatecert },
type: 'input',
name: 'gatekeeper_cns',
default: utils.getDefault( 'gatekeeper_cns' ),
message: prefix()+'Gatekeeper cert CNS (ips, domains, wildcard domains seperated by comma)?'+utils.getHelp('gatekeeper_cns')
}];
},
templates: function( props ) {
return [ 'keys.properties', 'api.properties', 'cert.pem', 'key.pem', 'default.conf' ];
}
};

View File

@@ -0,0 +1,44 @@
const chalk = require('chalk');
const name = 'traefik';
const capitalise = function( txt ) {
return txt.charAt(0).toUpperCase() + txt.substr(1);
};
const prefix = function() {
return chalk.green(capitalise(name)+': ');
};
module.exports = {
name: function() {
return name;
},
prompts: function( utils ) {
return [
{
type: 'input',
name: 'traefik_http_port',
default: utils.getDefault( 'traefik_http_port' ),
message: prefix()+'The HTTP port your apps will be accessible to the internet on.'+utils.getHelp('traefik_http_port'),
filter: utils.trimFilter,
validate: function( port ) {
return utils.notEmptyValidator( port ) && !isNaN( parseInt(port) )
}
},
{
type: 'input',
name: 'traefik_https_port',
default: utils.getDefault( 'traefik_https_port' ),
message: prefix()+'The HTTPS port your apps will be accessible to the internet on.'+utils.getHelp('traefik_https_port'),
filter: utils.trimFilter,
validate: function( port ) {
return utils.notEmptyValidator( port ) && !isNaN( parseInt(port) )
}
}
];
},
templates: function( props ) {
return [ 'acme.json', 'traefik.toml', 'htpasswd' ];
}
};

View File

@@ -17,7 +17,7 @@ const featureCondition = function(props) {
const templates = {
'lnd': [ path.join('lnd','lnd.conf') ],
'c-lightning': [ path.join('c-lightning','config'), path.join('c-lightning','bitcoin.conf') ]
'c-lightning': [ path.join('c-lightning','config') ]
};
module.exports = {
@@ -31,8 +31,8 @@ module.exports = {
when: featureCondition,
type: 'list',
name: 'lightning_implementation',
default: utils._getDefault( 'lightning_implementation' ),
message: prefix()+'What lightning implementation do you want to use?'+utils._getHelp('lightning_implementation'),
default: utils.getDefault( 'lightning_implementation' ),
message: prefix()+'What lightning implementation do you want to use?'+utils.getHelp('lightning_implementation'),
choices: [
{
name: 'C-lightning',
@@ -46,36 +46,52 @@ module.exports = {
]
},
*/
{
when: featureCondition,
type: 'confirm',
name: 'lightning_announce',
default: utils.getDefault( 'lightning_announce' ),
message: prefix()+'Do you want to announce your lightning node?'+utils.getHelp('lightning_announce'),
},
{
when: (props) => { return featureCondition(props) && props.lightning_announce },
type: 'input',
name: 'lightning_external_ip',
default: utils.getDefault( 'lightning_external_ip' ),
filter: utils.trimFilter,
validate: utils.ipOrFQDNValidator,
message: prefix()+'What external ip does your lightning node have?'+utils.getHelp('lightning_external_ip'),
},
{
when: featureCondition,
type: 'input',
name: 'lightning_nodename',
default: utils._getDefault( 'lightning_nodename' ),
filter: utils._trimFilter,
default: utils.getDefault( 'lightning_nodename' ),
filter: utils.trimFilter,
validate: (input)=>{
if( !input.trim() ) {
return true;
}
return utils._lightningNodeNameValidator(input);
return utils.lightningNodeNameValidator(input);
},
message: prefix()+'What name has your lightning node?'+utils._getHelp('lightning_nodename'),
message: prefix()+'What name has your lightning node?'+utils.getHelp('lightning_nodename'),
},
{
when: featureCondition,
type: 'input',
name: 'lightning_nodecolor',
default: utils._getDefault( 'lightning_nodecolor' ),
filter: utils._trimFilter,
default: utils.getDefault( 'lightning_nodecolor' ),
filter: utils.trimFilter,
validate: (input)=>{
if( !input.trim() ) {
return true;
}
return utils._colorValidator(input);
return utils.colorValidator(input);
},
message: prefix()+'What color has your lightning node?'+utils._getHelp('lightning_nodecolor'),
message: prefix()+'What color has your lightning node?'+utils.getHelp('lightning_nodecolor'),
}];
},
templates: function( props ) {
return templates[props.lightning_implementation]
return featureCondition(props)?templates[props.lightning_implementation]:[];
}
};

View File

@@ -23,7 +23,7 @@ const bitcoinInternalAndPrune = function(props) {
};
module.exports = {
name: function() {
name: function() {
return name;
},
prompts: function( utils ) {
@@ -31,16 +31,12 @@ module.exports = {
{
type: 'list',
name: 'bitcoin_mode',
default: utils._getDefault( 'bitcoin_mode' ),
message: prefix()+'Where is your bitcoin full node running?'+utils._getHelp('bitcoin_mode'),
default: utils.getDefault( 'bitcoin_mode' ),
message: prefix()+'Cyphernode will manage your bitcoin full node.'+utils.getHelp('bitcoin_mode'),
choices: [
{
name: 'Nowhere! I want cyphernode to run one.',
name: 'Ok. That is awesome',
value: 'internal'
},
{
name: 'I have a full node running.',
value: 'external'
}
]
},
@@ -48,38 +44,42 @@ module.exports = {
when: bitcoinExternal,
type: 'input',
name: 'bitcoin_node_ip',
default: utils._getDefault( 'bitcoin_node_ip' ),
filter: utils._trimFilter,
validate: utils._ipOrFQDNValidator,
message: prefix()+'What is your full node ip address?'+utils._getHelp('bitcoin_node_ip'),
default: utils.getDefault( 'bitcoin_node_ip' ),
filter: utils.trimFilter,
validate: utils.ipOrFQDNValidator,
message: prefix()+'What is your full node ip address?'+utils.getHelp('bitcoin_node_ip'),
},
{
type: 'input',
name: 'bitcoin_rpcuser',
default: utils._getDefault( 'bitcoin_rpcuser' ),
message: prefix()+'Name of bitcoin rpc user?'+utils._getHelp('bitcoin_rpcuser'),
filter: utils._trimFilter,
default: utils.getDefault( 'bitcoin_rpcuser' ),
message: prefix()+'Name of bitcoin rpc user?'+utils.getHelp('bitcoin_rpcuser'),
filter: utils.trimFilter,
},
{
type: 'password',
name: 'bitcoin_rpcpassword',
default: utils._getDefault( 'bitcoin_rpcpassword' ),
message: prefix()+'Password of bitcoin rpc user?'+utils._getHelp('bitcoin_rpcpassword'),
filter: utils._trimFilter,
default: utils.getDefault( 'bitcoin_rpcpassword' ),
message: prefix()+'Password of bitcoin rpc user?'+utils.getHelp('bitcoin_rpcpassword'),
filter: utils.trimFilter,
},
{
when: bitcoinInternal,
when: function(props) {
return bitcoinInternal( props ) && props.features.indexOf('lightning') === -1;
},
type: 'confirm',
name: 'bitcoin_prune',
default: utils._getDefault( 'bitcoin_prune' ),
message: prefix()+'Run bitcoin node in prune mode?'+utils._getHelp('bitcoin_prune'),
default: utils.getDefault( 'bitcoin_prune' ),
message: prefix()+'Run bitcoin node in prune mode?'+utils.getHelp('bitcoin_prune'),
},
{
when: bitcoinInternalAndPrune,
when: function(props) {
return bitcoinInternalAndPrune( props ) && props.features.indexOf('lightning') === -1;
},
type: 'input',
name: 'bitcoin_prune_size',
default: utils._getDefault( 'bitcoin_prune_size' ),
message: prefix()+'What is the maximum size of your blockchain data in megabytes?'+utils._getHelp('bitcoin_prune_size'),
default: utils.getDefault( 'bitcoin_prune_size' ),
message: prefix()+'What is the maximum size of your blockchain data in megabytes?'+utils.getHelp('bitcoin_prune_size'),
validate: function( input ) {
if( ! /^\d+$/.test(input) ) {
throw new Error( "Not a number");
@@ -94,16 +94,16 @@ module.exports = {
when: bitcoinInternal,
type: 'input',
name: 'bitcoin_uacomment',
default: utils._getDefault( 'bitcoin_uacomment' ),
message: prefix()+'Any UA comment?'+utils._getHelp('bitcoin_uacomment'),
filter: utils._trimFilter,
validate: (input)=> {return utils._optional(input,utils._UACommentValidator) }
default: utils.getDefault( 'bitcoin_uacomment' ),
message: prefix()+'Any UA comment?'+utils.getHelp('bitcoin_uacomment'),
filter: utils.trimFilter,
validate: (input)=> {return utils.optional(input,utils.UACommentValidator) }
}];
},
env: function( props ) {
return 'VAR0=VALUE0\nVAR1=VALUE1'
},
templates: function( props ) {
return ['bitcoin.conf']
return ['bitcoin.conf', 'bitcoin-client.conf']
}
};

View File

@@ -0,0 +1,309 @@
const path = require('path');
const chalk = require('chalk');
const name = 'installer';
const capitalise = function( txt ) {
return txt.charAt(0).toUpperCase() + txt.substr(1);
};
const prefix = function() {
return chalk.green(capitalise(name)+': ');
};
const installerDocker = function(props) {
return props.installer_mode === 'docker'
};
module.exports = {
name: function() {
return name;
},
prompts: function( utils ) {
return [{
type: 'list',
name: 'installer_mode',
default: utils.getDefault( 'installer_mode' ),
message: prefix()+chalk.red('Where do you want to install cyphernode?')+utils.getHelp('installer_mode'),
choices: [{
name: "Docker",
value: "docker"
}]
},
{
when: installerDocker,
type: 'list',
name: 'traefik_datapath',
default: utils.getDefault( 'traefik_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/traefik",
value: utils.setupDir()+"/cyphernode/traefik"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/traefik",
value: utils.defaultDataDirBase()+"/cyphernode/traefik"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/traefik",
value: utils.defaultDataDirBase()+"/.cyphernode/traefik"
},
{
name: utils.defaultDataDirBase()+"/traefik",
value: utils.defaultDataDirBase()+"/traefik"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your traefik data?'+utils.getHelp('traefik_datapath'),
},
{
when: (props)=>{ return installerDocker(props) && (props.traefik_datapath === '_custom') },
type: 'input',
name: 'traefik_datapath_custom',
default: utils.getDefault( 'traefik_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Custom path for traefik data?'+utils.getHelp('traefik_datapath_custom'),
},
{
when: installerDocker,
type: 'list',
name: 'gatekeeper_datapath',
default: utils.getDefault( 'gatekeeper_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/gatekeeper",
value: utils.setupDir()+"/cyphernode/gatekeeper"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/gatekeeper",
value: utils.defaultDataDirBase()+"/cyphernode/gatekeeper"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/gatekeeper",
value: utils.defaultDataDirBase()+"/.cyphernode/gatekeeper"
},
{
name: utils.defaultDataDirBase()+"/gatekeeper",
value: utils.defaultDataDirBase()+"/gatekeeper"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your gatekeeper data?'+utils.getHelp('gatekeeper_datapath'),
},
{
when: (props)=>{ return installerDocker(props) && (props.gatekeeper_datapath === '_custom') },
type: 'input',
name: 'gatekeeper_datapath_custom',
default: utils.getDefault( 'gatekeeper_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Custom path for gatekeeper data?'+utils.getHelp('gatekeeper_datapath_custom'),
},
{
when: installerDocker,
type: 'list',
name: 'proxy_datapath',
default: utils.getDefault( 'proxy_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/proxy",
value: utils.setupDir()+"/cyphernode/proxy"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/proxy",
value: utils.defaultDataDirBase()+"/cyphernode/proxy"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/proxy",
value: utils.defaultDataDirBase()+"/.cyphernode/proxy"
},
{
name: utils.defaultDataDirBase()+"/proxy",
value: utils.defaultDataDirBase()+"/proxy"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your proxy data?'+utils.getHelp('proxy_datapath'),
},
{
when: (props)=>{ return installerDocker(props) && (props.proxy_datapath === '_custom') },
type: 'input',
name: 'proxy_datapath_custom',
default: utils.getDefault( 'proxy_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Custom path for your proxy data?'+utils.getHelp('proxy_datapath_custom'),
},
{
when: function(props) { return installerDocker(props) && props.bitcoin_mode === 'internal' },
type: 'list',
name: 'bitcoin_datapath',
default: utils.getDefault( 'bitcoin_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/bitcoin",
value: utils.setupDir()+"/cyphernode/bitcoin"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/bitcoin",
value: utils.defaultDataDirBase()+"/cyphernode/bitcoin"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/bitcoin",
value: utils.defaultDataDirBase()+"/.cyphernode/bitcoin"
},
{
name: utils.defaultDataDirBase()+"/bitcoin",
value: utils.defaultDataDirBase()+"/bitcoin"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your bitcoin full node data?'+utils.getHelp('bitcoin_datapath'),
},
{
when: function(props) { return installerDocker(props) && props.bitcoin_mode === 'internal' && props.bitcoin_datapath === '_custom' },
type: 'input',
name: 'bitcoin_datapath_custom',
default: utils.getDefault( 'bitcoin_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Custom path for your bitcoin full node data?'+utils.getHelp('bitcoin_datapath_custom'),
},
{
when: function(props) { return installerDocker(props) && props.features.indexOf('lightning') !== -1 },
type: 'list',
name: 'lightning_datapath',
default: utils.getDefault( 'lightning_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/lightning",
value: utils.setupDir()+"/cyphernode/lightning"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/lightning",
value: utils.defaultDataDirBase()+"/cyphernode/lightning"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/lightning",
value: utils.defaultDataDirBase()+"/.cyphernode/lightning"
},
{
name: utils.defaultDataDirBase()+"/lightning",
value: utils.defaultDataDirBase()+"/lightning"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your lightning node data?'+utils.getHelp('lightning_datapath'),
},
{
when: function(props) { return installerDocker(props) && props.features.indexOf('lightning') !== -1 && props.lightning_datapath === '_custom'},
type: 'input',
name: 'lightning_datapath_custom',
default: utils.getDefault( 'lightning_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Custom path for your lightning node data?'+utils.getHelp('lightning_datapath_custom'),
},
{
when: function(props) { return installerDocker(props) && props.features.indexOf('otsclient') !== -1 },
type: 'list',
name: 'otsclient_datapath',
default: utils.getDefault( 'otsclient_datapath' ),
choices: [
{
name: utils.setupDir()+"/cyphernode/otsclient",
value: utils.setupDir()+"/cyphernode/otsclient"
},
{
name: utils.defaultDataDirBase()+"/cyphernode/otsclient",
value: utils.defaultDataDirBase()+"/cyphernode/otsclient"
},
{
name: utils.defaultDataDirBase()+"/.cyphernode/otsclient",
value: utils.defaultDataDirBase()+"/.cyphernode/otsclient"
},
{
name: utils.defaultDataDirBase()+"/otsclient",
value: utils.defaultDataDirBase()+"/otsclient"
},
{
name: "Custom path",
value: "_custom"
}
],
message: prefix()+'Where do you want to store your OTS data?'+utils.getHelp('otsclient_datapath'),
},
{
when: function(props) { return installerDocker(props) && props.features.indexOf('otsclient') !== -1 && props.otsclient_datapath === '_custom' },
type: 'input',
name: 'otsclient_datapath_custom',
default: utils.getDefault( 'otsclient_datapath_custom' ),
filter: utils.trimFilter,
validate: utils.pathValidator,
message: prefix()+'Where is your otsclient data?'+utils.getHelp('otsclient_datapath_custom'),
},
{
type: 'confirm',
name: 'gatekeeper_expose',
default: utils.getDefault( 'gatekeeper_expose' ),
message: prefix()+'Expose gatekeeper outside of the docker network?'+utils.getHelp('gatekeeper_expose'),
},
{
when: function(props) { return installerDocker(props) && props.bitcoin_mode === 'internal' },
type: 'confirm',
name: 'bitcoin_expose',
default: utils.getDefault( 'bitcoin_expose' ),
message: prefix()+'Expose bitcoin full node outside of the docker network?'+utils.getHelp('bitcoin_expose'),
},
{
when: function(props) { return installerDocker(props) && props.features.indexOf('lightning') !== -1 },
type: 'confirm',
name: 'lightning_expose',
default: utils.getDefault( 'lightning_expose' ),
message: prefix()+'Expose lightning node outside of the docker network?'+utils.getHelp('lightning_expose'),
},
{
when: installerDocker,
type: 'list',
name: 'docker_mode',
default: utils.getDefault( 'docker_mode' ),
message: prefix()+'What docker mode: docker swarm or docker-compose?'+utils.getHelp('docker_mode'),
choices: [{
name: "docker swarm",
value: "swarm"
},
{
name: "docker-compose",
value: "compose"
}]
},
{
type: 'confirm',
name: 'installer_cleanup',
default: utils.getDefault( 'installer_cleanup' ),
message: prefix()+'Cleanup installer after installation?'+utils.getHelp('installer_cleanup'),
}];
},
templates: function( props ) {
if( props.installer_mode === 'docker' ) {
return ['config.sh','start.sh', 'stop.sh', 'testfeatures.sh', 'testdeployment.sh', path.join('docker', 'docker-compose.yaml')];
}
return ['config.sh','start.sh', 'stop.sh', 'testfeatures.sh', 'testdeployment.sh'];
}
};

42
cyphernodeconf_docker/run.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/bin/sh
export SETUP_DIR=$(pwd)/../dist
export DEFAULT_USER=$USER
export DEFAULT_CERT_HOSTNAME=disk0book.local
export PROXYCRON_VERSION=v0.2.4
export PYCOIN_VERSION=v0.2.4
export SETUP_VERSION=v0.2.4
export BITCOIN_VERSION=v0.18.0
export LIGHTNING_VERSION=v0.7.1
export DEFAULT_DATADIR_BASE=$HOME
export GATEKEEPER_VERSION=v0.2.4
export PROXY_VERSION=v0.2.4
export OTSCLIENT_VERSION=v0.2.4
export NOTIFIER_VERSION=v0.2.4
export EDITOR=/usr/bin/nano
user=$(id -u):$(id -g)
if [ "${MODE}" = 'docker' ]; then
docker build . -t cyphernodeconf:local
docker run -v $(pwd)/testinst:/data \
-e DEFAULT_USER=jash \
-e DEFAULT_DATADIR_BASE=$HOME \
-e SETUP_DIR=$SETUP_DIR \
-e DEFAULT_CERT_HOSTNAME=$(hostname) \
-e GATEKEEPER_VERSION=$GATEKEEPER_VERSION \
-e PROXY_VERSION=$PROXY_VERSION \
-e NOTIFIER_VERSION=$NOTIFIER_VERSION \
-e PROXYCRON_VERSION=$PROXYCRON_VERSION \
-e OTSCLIENT_VERSION=$OTSCLIENT_VERSION \
-e PYCOIN_VERSION=$PYCOIN_VERSION \
-e BITCOIN_VERSION=$BITCOIN_VERSION \
-e LIGHTNING_VERSION=$LIGHTNING_VERSION \
-e SETUP_VERSION=$SETUP_VERSION \
-e DEFAULT_USER=$DEFAULT_USER \
--log-driver=none \
--network none \
--rm -it cyphernodeconf:local $user node index.js $@
else
/usr/local/bin/node index.js $@
fi

View File

@@ -0,0 +1,466 @@
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://cyphernode.io/config-v0.1.0.json",
"type": "object",
"title": "Cyphernode config file structure v0.1.0",
"additionalProperties": false,
"required": [
"__version",
"features",
"net",
"xpub",
"derivation_path",
"installer_mode",
"run_as_different_user",
"username",
"docker_mode",
"bitcoin_rpcuser",
"bitcoin_rpcpassword",
"bitcoin_prune",
"bitcoin_datapath",
"bitcoin_node_ip",
"bitcoin_mode",
"bitcoin_expose",
"gatekeeper_apiproperties",
"gatekeeper_keys",
"gatekeeper_sslcert",
"gatekeeper_sslkey",
"gatekeeper_cns",
"gatekeeper_clientkeyspassword",
"gatekeeper_clientkeyspassword_c",
"gatekeeper_statuspw",
"gatekeeper_datapath",
"lightning_expose",
"lightning_external_ip",
"lightning_implementation",
"lightning_datapath",
"lightning_nodename",
"lightning_nodecolor",
"otsclient_datapath",
"proxy_datapath"
],
"allOf": [
{
"if": {
"properties": {
"run_as_different_user": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"username"
]
}
},
{
"if": {
"properties": {
"bitcoin_prune": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"bitcoin_prune_size"
]
}
}
],
"properties": {
"__version": {
"type": "string",
"enum": ["0.1.0"],
"default": "0.1.0",
"examples": ["0.1.0"]
},
"features": {
"$id": "#/properties/features",
"type": "array",
"title": "The optional features of this cyphernode",
"default": [],
"items": {
"$id": "#/properties/features/items",
"type": "string",
"enum": [
"lightning",
"otsclient"
],
"title": "The feature",
"examples": [
"lightning",
"otsclient"
]
}
},
"net": {
"$id": "#/properties/net",
"type": "string",
"enum": [
"testnet",
"mainnet"
],
"title": "The net cyphernode is running on",
"default": "testnet",
"examples": [
"testnet"
]
},
"xpub": {
"$id": "#/properties/xpub",
"type": "string",
"title": "Default xpub to derive addresses from",
"pattern": "^(\\w+)$"
},
"derivation_path": {
"$id": "#/properties/derivation_path",
"type": "string",
"title": "Default derivation path",
"default": "0/n",
"examples": [
"0/n"
]
},
"installer_mode": {
"$id": "#/properties/installer_mode",
"type": "string",
"enum": [
"docker"
],
"title": "Install mode",
"default": "docker",
"examples": [
"docker"
]
},
"run_as_different_user": {
"$id": "#/properties/run_as_different_user",
"type": "boolean",
"title": "Run as different user",
"default": true,
"examples": [
true
]
},
"username": {
"$id": "#/properties/username",
"type": "string",
"title": "Username to run under",
"default": "cyphernode",
"examples": [
"cyphernode"
]
},
"docker_mode": {
"$id": "#/properties/docker_mode",
"type": "string",
"enum": [
"swarm",
"compose"
],
"title": "How to run the containers",
"default": "compose",
"examples": [
"compose"
]
},
"bitcoin_rpcuser": {
"$id": "#/properties/bitcoin_rpcuser",
"type": "string",
"title": "Bitcoin rpc user",
"default": "bitcoin",
"examples": [
"bitcoin"
]
},
"bitcoin_rpcpassword": {
"$id": "#/properties/bitcoin_rpcpassword",
"type": "string",
"title": "Bitcoin rpc password",
"default": "CHANGEME",
"examples": [
"CHANGEME"
]
},
"bitcoin_uacomment": {
"$id": "#/properties/bitcoin_uacomment",
"type": "string",
"title": "Bitcoin user agent comment",
"examples": [
"cyphernode"
]
},
"bitcoin_prune": {
"$id": "#/properties/bitcoin_prune",
"type": "boolean",
"title": "Bitcoin prune",
"default": false,
"examples": [
"false"
]
},
"bitcoin_prune_size": {
"$id": "#/properties/bitcoin_prune_size",
"type": "integer",
"title": "Bitcoin prune size",
"default": 550,
"examples": [
550
]
},
"bitcoin_datapath": {
"$id": "#/properties/bitcoin_datapath",
"type": "string",
"title": "Bitcoin datapath",
"examples": [
"/tmp/cyphernode/bitcoin"
]
},
"bitcoin_datapath_custom": {
"$id": "#/properties/bitcoin_datapath_custom",
"type": "string",
"title": "Bitcoin custom datapath",
"examples": [
"/tmp/cyphernode/bitcoin"
]
},
"lightning_datapath": {
"$id": "#/properties/lightning_datapath",
"type": "string",
"title": "Lightning datapath",
"examples": [
"/tmp/cyphernode/lightning"
]
},
"lightning_datapath_custom": {
"$id": "#/properties/lightning_datapath_custom",
"type": "string",
"title": "Lightning custom datapath",
"examples": [
"/tmp/cyphernode/lightning"
]
},
"proxy_datapath": {
"$id": "#/properties/proxy_datapath",
"type": "string",
"title": "Proxy datapath",
"examples": [
"/tmp/cyphernode/proxy"
]
},
"proxy_datapath_custom": {
"$id": "#/properties/proxy_datapath_custom",
"type": "string",
"title": "Proxy custom datapath",
"examples": [
"/tmp/cyphernode/proxy"
]
},
"otsclient_datapath": {
"$id": "#/properties/otsclient_datapath",
"type": "string",
"title": "OTS Client datapath",
"examples": [
"/tmp/cyphernode/otsclient"
]
},
"otsclient_datapath_custom": {
"$id": "#/properties/otsclient_datapath_custom",
"type": "string",
"title": "OTS Client custom datapath",
"examples": [
"/tmp/cyphernode/otsclient"
]
},
"bitcoin_node_ip": {
"$id": "#/properties/bitcoin_node_ip",
"type": "string",
"format": "ipv4",
"title": "Bitcoin node ip",
"examples": [
"123.123.123.123"
]
},
"bitcoin_mode": {
"$id": "#/properties/bitcoin_mode",
"type": "string",
"enum": [
"internal"
],
"title": "Bitcoin mode",
"examples": [
"internal"
]
},
"bitcoin_expose": {
"$id": "#/properties/bitcoin_expose",
"type": "boolean",
"title": "Expose bitcoin node",
"default": false,
"examples": [
false
]
},
"lightning_expose": {
"$id": "#/properties/lightning_expose",
"type": "boolean",
"title": "Expose lightning node",
"default": false,
"examples": [
false
]
},
"lightning_external_ip": {
"$id": "#/properties/lightning_external_ip",
"type": "string",
"format": "ipv4",
"title": "Lightning node reachable ip",
"examples": [
"123.123.123.123"
]
},
"gatekeeper_datapath": {
"$id": "#/properties/gatekeeper_datapath",
"type": "string",
"title": "Gatekeeper datapath",
"examples": [
"/tmp/cyphernode/gatekeeper"
]
},
"gatekeeper_datapath_custom": {
"$id": "#/properties/gatekeeper_datapath_custom",
"type": "string",
"title": "Gatekeeper custom datapath",
"examples": [
"/tmp/cyphernode/gatekeeper"
]
},
"gatekeeper_apiproperties": {
"$id": "#/properties/gatekeeper_apiproperties",
"type": "string",
"title": "API properties",
"examples": [
"# Stats can:\naction_getblockchaininfo=stats\n\n# Watcher can:\naction_watch=watcher\naction_unwatch=watcher\naction_watchxpub=watcher\naction_unwatchxpubbyxpub=watcher\naction_unwatchxpubbylabel=watcher\naction_getactivewatchesbyxpub=watcher\naction_getactivewatchesbylabel=watcher\naction_getactivexpubwatches=watcher\naction_watchtxid=watcher\naction_getactivewatches=watcher\naction_getbestblockhash=watcher\naction_getbestblockinfo=watcher\naction_getblockinfo=watcher\naction_gettransaction=watcher\naction_ln_getinfo=watcher\naction_ln_create_invoice=watcher\naction_ln_getconnectionstring=watcher\naction_ln_decodebolt11=watcher\n\n# Spender can do what the watcher can do, plus:\naction_getbalance=spender\naction_getbalancebyxpub=spender\naction_getbalancebyxpublabel=spender\naction_getnewaddress=spender\naction_spend=spender\naction_addtobatch=spender\naction_batchspend=spender\naction_deriveindex=spender\naction_derivepubpath=spender\naction_ln_pay=spender\naction_ln_newaddr=spender\naction_ots_stamp=spender\naction_ots_getfile=spender\naction_ln_getinvoice=spender\naction_ln_decodebolt11=spender\naction_ln_connectfund=spender\n\n# Admin can do what the spender can do, plus:\n\n\n# Should be called from inside the Docker network only:\naction_conf=internal\naction_newblock=internal\naction_executecallbacks=internal\naction_ots_backoffice=internal"
]
},
"gatekeeper_keys": {
"$id": "#/properties/gatekeeper_keys",
"type": "object",
"title": "Gatekeeper keys",
"default": {
"configEntries": [],
"clientInformation": []
},
"required": [
"configEntries",
"clientInformation"
],
"properties": {
"configEntries": {
"$id": "#/properties/gatekeeper_keys/configEntries",
"type": "array",
"items": {
"$id": "#/properties/gatekeeper_keys/configEntries/entry",
"type": "string",
"pattern": "^kapi_id=\".+\";kapi_key=\".+\";kapi_groups=\".+\";.+$"
},
"examples": [
[
"kapi_id=\"001\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"002\";kapi_key=\"fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d\";kapi_groups=\"watcher,spender\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"003\";kapi_key=\"f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417\";kapi_groups=\"watcher,spender,admin\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}"
]
]
},
"clientInformation": {
"$id": "#/properties/gatekeeper_keys/clientInformation",
"type": "array",
"items": {
"$id": "#/properties/gatekeeper_keys/clientInformation/entry",
"type": "string",
"pattern": "^.+=.+$"
},
"examples": [
[
"001=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"002=fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d",
"003=f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417"
]
]
}
}
},
"gatekeeper_clientkeyspassword": {
"$id": "#/properties/gatekeeper_clientkeyspassword",
"type": "string",
"title": "Password for the encrypted client keys archive"
},
"gatekeeper_clientkeyspassword_c": {
"$id": "#/properties/gatekeeper_clientkeyspassword",
"type": "string",
"title": "Password for the encrypted client keys archive, verified"
},
"gatekeeper_statuspw": {
"$id": "#/properties/adminhash",
"type": "string",
"title": "MD5 hash of admin password"
},
"gatekeeper_sslcert": {
"$id": "#/properties/gatekeeper_sslcert",
"type": "string",
"title": "Gatekeeper SSL Cert"
},
"gatekeeper_sslkey": {
"$id": "#/properties/gatekeeper_sslkey",
"type": "string",
"title": "Gatekeeper SSL Key"
},
"gatekeeper_cns": {
"$id": "#/properties/gatekeeper_cns",
"type": "string",
"title": "Gatekeeper cns",
"examples": [
"myhost.mydomain.com,*.myotherdomain.com,123.123.123.123"
]
},
"lightning_implementation": {
"$id": "#/properties/lightning_implementation",
"type": "string",
"enum": [
"c-lightning"
],
"title": "The lightning implementation",
"default": "c-lightning",
"examples": [
"c-lightning"
]
},
"lightning_nodename": {
"$id": "#/properties/lightning_nodename",
"type": "string",
"title": "The lightning node name",
"examples": [
"🚀 Mighty Moose 🚀"
]
},
"lightning_nodecolor": {
"$id": "#/properties/lightning_nodecolor",
"type": "string",
"pattern": "^[0-9A-Fa-f]{6}$",
"title": "The lightning node color",
"default": "ff0000",
"examples": [
"ff0000",
"00ff00",
"00ffff"
]
}
}
}

View File

@@ -0,0 +1,515 @@
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://cyphernode.io/config-v0.2.0.json",
"type": "object",
"title": "Cyphernode config file structure v0.2.0",
"additionalProperties": false,
"required": [
"schema_version",
"setup_version",
"features",
"net",
"use_xpub",
"installer_mode",
"run_as_different_user",
"docker_mode",
"docker_versions",
"adminhash",
"bitcoin_rpcuser",
"bitcoin_rpcpassword",
"bitcoin_prune",
"bitcoin_datapath",
"bitcoin_mode",
"bitcoin_expose",
"gatekeeper_keys",
"gatekeeper_sslcert",
"gatekeeper_sslkey",
"gatekeeper_cns",
"gatekeeper_clientkeyspassword",
"gatekeeper_datapath",
"gatekeeper_port",
"lightning_expose",
"lightning_implementation",
"lightning_datapath",
"lightning_nodename",
"lightning_nodecolor",
"proxy_datapath",
"otsclient_datapath",
"traefik_datapath"
],
"allOf": [
{
"if": {
"properties": {
"run_as_different_user": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"username"
]
}
},
{
"if": {
"properties": {
"use_xpub": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"xpub",
"derivation_path"
]
}
},
{
"if": {
"properties": {
"bitcoin_prune": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"bitcoin_prune_size"
]
}
}
],
"properties": {
"schema_version": {
"type": "string",
"enum": [
"0.2.0"
],
"default": "0.2.0",
"examples": [
"0.2.0"
]
},
"setup_version": {
"type": "string",
"examples": [
"v0.2.0"
]
},
"docker_versions": {
"$id": "#/properties/dockerVersions",
"type": "object",
"title": "All versions of the docker containers",
"default": {},
"additionalProperties": {
"type": "string"
}
},
"features": {
"$id": "#/properties/features",
"type": "array",
"title": "The optional features of this cyphernode",
"default": [],
"items": {
"$id": "#/properties/features/items",
"type": "string",
"enum": [
"lightning",
"otsclient"
],
"title": "The feature",
"default": "",
"examples": [
"lightning",
"otsclient"
]
}
},
"net": {
"$id": "#/properties/net",
"type": "string",
"enum": [
"testnet",
"mainnet"
],
"title": "The net cyphernode is running on",
"default": "testnet",
"examples": [
"testnet"
]
},
"use_xpub": {
"$id": "#/properties/use_xpub",
"type": "boolean",
"title": "Use xpub key?",
"default": false,
"examples": [
false
]
},
"xpub": {
"$id": "#/properties/xpub",
"type": "string",
"title": "Default xpub to derive addresses from",
"pattern": "^(\\w+)$"
},
"derivation_path": {
"$id": "#/properties/derivation_path",
"type": "string",
"title": "Default derivation path",
"default": "0/n",
"examples": [
"0/n"
]
},
"installer_mode": {
"$id": "#/properties/installer_mode",
"type": "string",
"enum": [
"docker"
],
"title": "Install mode",
"default": "docker",
"examples": [
"docker"
]
},
"run_as_different_user": {
"$id": "#/properties/run_as_different_user",
"type": "boolean",
"title": "Run as different user",
"default": true,
"examples": [
true
]
},
"username": {
"$id": "#/properties/username",
"type": "string",
"title": "Username to run under",
"default": "cyphernode",
"examples": [
"cyphernode"
]
},
"docker_mode": {
"$id": "#/properties/docker_mode",
"type": "string",
"enum": [
"swarm",
"compose"
],
"title": "How to run the containers",
"default": "compose",
"examples": [
"compose"
]
},
"bitcoin_rpcuser": {
"$id": "#/properties/bitcoin_rpcuser",
"type": "string",
"title": "Bitcoin rpc user",
"default": "bitcoin",
"examples": [
"bitcoin"
]
},
"bitcoin_rpcpassword": {
"$id": "#/properties/bitcoin_rpcpassword",
"type": "string",
"title": "Bitcoin rpc password",
"default": "CHANGEME",
"examples": [
"CHANGEME"
]
},
"bitcoin_uacomment": {
"$id": "#/properties/bitcoin_uacomment",
"type": "string",
"title": "Bitcoin user agent comment",
"examples": [
"cyphernode"
]
},
"bitcoin_prune": {
"$id": "#/properties/bitcoin_prune",
"type": "boolean",
"title": "Bitcoin prune",
"default": false,
"examples": [
"false"
]
},
"bitcoin_prune_size": {
"$id": "#/properties/bitcoin_prune_size",
"type": "integer",
"title": "Bitcoin prune size",
"default": 550,
"examples": [
550
]
},
"bitcoin_datapath": {
"$id": "#/properties/bitcoin_datapath",
"type": "string",
"title": "Bitcoin datapath",
"examples": [
"/tmp/cyphernode/bitcoin"
]
},
"bitcoin_datapath_custom": {
"$id": "#/properties/bitcoin_datapath_custom",
"type": "string",
"title": "Bitcoin custom datapath",
"examples": [
"/tmp/cyphernode/bitcoin"
]
},
"lightning_datapath": {
"$id": "#/properties/lightning_datapath",
"type": "string",
"title": "Lightning datapath",
"examples": [
"/tmp/cyphernode/lightning"
]
},
"lightning_datapath_custom": {
"$id": "#/properties/lightning_datapath_custom",
"type": "string",
"title": "Lightning custom datapath",
"examples": [
"/tmp/cyphernode/lightning"
]
},
"proxy_datapath": {
"$id": "#/properties/proxy_datapath",
"type": "string",
"title": "Proxy datapath",
"examples": [
"/tmp/cyphernode/proxy"
]
},
"proxy_datapath_custom": {
"$id": "#/properties/proxy_datapath_custom",
"type": "string",
"title": "Proxy custom datapath",
"examples": [
"/tmp/cyphernode/proxy"
]
},
"otsclient_datapath": {
"$id": "#/properties/otsclient_datapath",
"type": "string",
"title": "OTS Client datapath",
"examples": [
"/tmp/cyphernode/otsclient"
]
},
"otsclient_datapath_custom": {
"$id": "#/properties/otsclient_datapath_custom",
"type": "string",
"title": "OTS Client custom datapath",
"examples": [
"/tmp/cyphernode/otsclient"
]
},
"traefik_datapath": {
"$id": "#/properties/traefik_datapath",
"type": "string",
"title": "Traefik datapath",
"examples": [
"/tmp/cyphernode/traefik"
]
},
"traefik_datapath_custom": {
"$id": "#/properties/traefik_datapath_custom",
"type": "string",
"title": "Traefik custom datapath",
"examples": [
"/tmp/cyphernode/traefik"
]
},
"lightning_external_ip": {
"$id": "#/properties/lightning_external_ip",
"type": "string",
"format": "ipv4",
"title": "External lightning node ip",
"examples": [
"123.123.123.123"
]
},
"bitcoin_mode": {
"$id": "#/properties/bitcoin_mode",
"type": "string",
"enum": [
"internal"
],
"title": "Bitcoin mode",
"default": "internal",
"examples": [
"internal"
]
},
"bitcoin_expose": {
"$id": "#/properties/bitcoin_expose",
"type": "boolean",
"title": "Expose bitcoin node",
"default": false,
"examples": [
false
]
},
"lightning_expose": {
"$id": "#/properties/lightning_expose",
"type": "boolean",
"title": "Expose lightning node",
"default": false,
"examples": [
false
]
},
"gatekeeper_datapath": {
"$id": "#/properties/gatekeeper_datapath",
"type": "string",
"title": "Gatekeeper datapath",
"examples": [
"/tmp/cyphernode/gatekeeper"
]
},
"gatekeeper_datapath_custom": {
"$id": "#/properties/gatekeeper_datapath_custom",
"type": "string",
"title": "Gatekeeper custom datapath",
"examples": [
"/tmp/cyphernode/gatekeeper"
]
},
"gatekeeper_port": {
"$id": "#/properties/gatekeeper_port",
"type": "integer",
"title": "Gatekeeper port",
"default": 2009,
"examples": [
2009
]
},
"gatekeeper_keys": {
"$id": "#/properties/gatekeeper_keys",
"type": "object",
"title": "Gatekeeper keys",
"default": {
"configEntries": [],
"clientInformation": []
},
"required": [
"configEntries",
"clientInformation"
],
"properties": {
"configEntries": {
"$id": "#/properties/gatekeeper_keys/configEntries",
"type": "array",
"items": {
"$id": "#/properties/gatekeeper_keys/configEntries/entry",
"type": "string",
"pattern": "^kapi_id=\".+\";kapi_key=\".+\";kapi_groups=\".+\";.+$"
},
"examples": [
[
"kapi_id=\"000\";kapi_key=\"a27f9e73fdde6a5005879c259c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"stats\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"001\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"stats,watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"002\";kapi_key=\"fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d\";kapi_groups=\"stats,watcher,spender\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"003\";kapi_key=\"f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417\";kapi_groups=\"stats,watcher,spender,admin\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}"
]
]
},
"clientInformation": {
"$id": "#/properties/gatekeeper_keys/clientInformation",
"type": "array",
"items": {
"$id": "#/properties/gatekeeper_keys/clientInformation/entry",
"type": "string",
"pattern": "^.+=.+$"
},
"examples": [
[
"000=a27f9e73fdde6a5005879c259c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"001=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"002=fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d",
"003=f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417"
]
]
}
}
},
"gatekeeper_sslcert": {
"$id": "#/properties/gatekeeper_sslcert",
"type": "string",
"title": "Gatekeeper SSL Cert"
},
"gatekeeper_sslkey": {
"$id": "#/properties/gatekeeper_sslkey",
"type": "string",
"title": "Gatekeeper SSL Key"
},
"gatekeeper_cns": {
"$id": "#/properties/gatekeeper_cns",
"type": "string",
"title": "Gatekeeper cns",
"examples": [
"myhost.mydomain.com,*.myotherdomain.com,123.123.123.123"
]
},
"gatekeeper_clientkeyspassword": {
"$id": "#/properties/gatekeeper_clientkeyspassword",
"type": "string",
"title": "Password for the encrypted client keys archive"
},
"adminhash": {
"$id": "#/properties/adminhash",
"type": "string",
"title": "Bcrypted hash of admin password"
},
"lightning_implementation": {
"$id": "#/properties/lightning_implementation",
"type": "string",
"enum": [
"c-lightning"
],
"title": "The lightning implementation",
"default": "c-lightning",
"examples": [
"c-lightning"
]
},
"lightning_nodename": {
"$id": "#/properties/lightning_nodename",
"type": "string",
"title": "The lightning node name",
"examples": [
"🚀 Mighty Moose 🚀"
]
},
"lightning_nodecolor": {
"$id": "#/properties/lightning_nodecolor",
"type": "string",
"pattern": "^[0-9A-Fa-f]{6}$",
"title": "The lightning node color",
"examples": [
"ff0000",
"00ff00",
"00ffff"
]
}
}
}

View File

@@ -0,0 +1,590 @@
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://cyphernode.io/config-v0.2.2.json",
"type": "object",
"title": "Cyphernode config file structure v0.2.2",
"additionalProperties": false,
"required": [
"schema_version",
"setup_version",
"features",
"net",
"use_xpub",
"installer_mode",
"run_as_different_user",
"docker_mode",
"docker_versions",
"adminhash",
"bitcoin_rpcuser",
"bitcoin_rpcpassword",
"bitcoin_prune",
"bitcoin_datapath",
"bitcoin_mode",
"bitcoin_expose",
"gatekeeper_expose",
"gatekeeper_keys",
"gatekeeper_sslcert",
"gatekeeper_sslkey",
"gatekeeper_cns",
"gatekeeper_clientkeyspassword",
"gatekeeper_datapath",
"gatekeeper_port",
"proxy_datapath",
"traefik_datapath",
"traefik_http_port",
"traefik_https_port"
],
"allOf": [
{
"if": {
"properties": {
"run_as_different_user": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"username"
]
}
},
{
"if": {
"properties": {
"use_xpub": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"xpub",
"derivation_path"
]
}
},
{
"if": {
"properties": {
"bitcoin_prune": {
"enum": [
true
]
}
}
},
"then": {
"required": [
"bitcoin_prune_size"
]
}
},
{
"if": {
"properties": {
"features": {
"contains": {
"enum": [
"lightning"
]
}
}
}
},
"then": {
"required": [
"lightning_announce",
"lightning_expose",
"lightning_implementation",
"lightning_datapath",
"lightning_nodename",
"lightning_nodecolor"
]
}
},
{
"if": {
"properties": {
"features": {
"contains": {
"enum": [
"otsclient"
]
}
}
}
},
"then": {
"required": [
"otsclient_datapath"
]
}
}
],
"properties": {
"schema_version": {
"type": "string",
"enum": [
"0.2.2"
],
"default": "0.3.0",
"examples": [
"0.2.2"
]
},
"setup_version": {
"type": "string",
"examples": [
"v0.2.0"
]
},
"docker_versions": {
"$id": "#/properties/dockerVersions",
"type": "object",
"title": "All versions of the docker containers",
"default": {},
"additionalProperties": {
"type": "string"
}
},
"features": {
"$id": "#/properties/features",
"type": "array",
"title": "The optional features of this cyphernode",
"default": [],
"items": {
"$id": "#/properties/features/items",
"type": "string",
"enum": [
"lightning",
"otsclient"
],
"title": "The feature",
"default": "",
"examples": [
"lightning",
"otsclient"
]
}
},
"net": {
"$id": "#/properties/net",
"type": "string",
"enum": [
"testnet",
"mainnet",
"regtest"
],
"title": "The net cyphernode is running on",
"default": "testnet",
"examples": [
"testnet"
]
},
"use_xpub": {
"$id": "#/properties/use_xpub",
"type": "boolean",
"title": "Use xpub key?",
"default": false,
"examples": [
false
]
},
"xpub": {
"$id": "#/properties/xpub",
"type": "string",
"title": "Default xpub to derive addresses from",
"pattern": "^(\\w+)$"
},
"derivation_path": {
"$id": "#/properties/derivation_path",
"type": "string",
"title": "Default derivation path",
"default": "0/n",
"examples": [
"0/n"
]
},
"installer_mode": {
"$id": "#/properties/installer_mode",
"type": "string",
"enum": [
"docker"
],
"title": "Install mode",
"default": "docker",
"examples": [
"docker"
]
},
"run_as_different_user": {
"$id": "#/properties/run_as_different_user",
"type": "boolean",
"title": "Run as different user",
"default": true,
"examples": [
true
]
},
"username": {
"$id": "#/properties/username",
"type": "string",
"title": "Username to run under",
"default": "cyphernode",
"examples": [
"cyphernode"
]
},
"docker_mode": {
"$id": "#/properties/docker_mode",
"type": "string",
"enum": [
"swarm",
"compose"
],
"title": "How to run the containers",
"default": "compose",
"examples": [
"compose"
]
},
"bitcoin_rpcuser": {
"$id": "#/properties/bitcoin_rpcuser",
"type": "string",
"title": "Bitcoin rpc user",
"default": "bitcoin",
"examples": [
"bitcoin"
]
},
"bitcoin_rpcpassword": {
"$id": "#/properties/bitcoin_rpcpassword",
"type": "string",
"title": "Bitcoin rpc password",
"default": "CHANGEME",
"examples": [
"CHANGEME"
]
},
"bitcoin_uacomment": {
"$id": "#/properties/bitcoin_uacomment",
"type": "string",
"title": "Bitcoin user agent comment",
"examples": [
"cyphernode"
]
},
"bitcoin_prune": {
"$id": "#/properties/bitcoin_prune",
"type": "boolean",
"title": "Bitcoin prune",
"default": false,
"examples": [
"false"
]
},
"bitcoin_prune_size": {
"$id": "#/properties/bitcoin_prune_size",
"type": "integer",
"title": "Bitcoin prune size",
"default": 550,
"examples": [
550
]
},
"bitcoin_datapath": {
"$id": "#/properties/bitcoin_datapath",
"type": "string",
"title": "Bitcoin datapath",
"examples": [
"/tmp/cyphernode/bitcoin"
]
},
"bitcoin_datapath_custom": {
"$id": "#/properties/bitcoin_datapath_custom",
"type": "string",
"title": "Bitcoin custom datapath",
"examples": [
"/tmp/cyphernode/bitcoin"
]
},
"lightning_datapath": {
"$id": "#/properties/lightning_datapath",
"type": "string",
"title": "Lightning datapath",
"examples": [
"/tmp/cyphernode/lightning"
]
},
"lightning_datapath_custom": {
"$id": "#/properties/lightning_datapath_custom",
"type": "string",
"title": "Lightning custom datapath",
"examples": [
"/tmp/cyphernode/lightning"
]
},
"proxy_datapath": {
"$id": "#/properties/proxy_datapath",
"type": "string",
"title": "Proxy datapath",
"examples": [
"/tmp/cyphernode/proxy"
]
},
"proxy_datapath_custom": {
"$id": "#/properties/proxy_datapath_custom",
"type": "string",
"title": "Proxy custom datapath",
"examples": [
"/tmp/cyphernode/proxy"
]
},
"otsclient_datapath": {
"$id": "#/properties/otsclient_datapath",
"type": "string",
"title": "OTS Client datapath",
"examples": [
"/tmp/cyphernode/otsclient"
]
},
"otsclient_datapath_custom": {
"$id": "#/properties/otsclient_datapath_custom",
"type": "string",
"title": "OTS Client custom datapath",
"examples": [
"/tmp/cyphernode/otsclient"
]
},
"traefik_http_port": {
"$id": "#/properties/traefik_port",
"type": "integer",
"title": "Traefik HTTP port",
"default": 80,
"examples": [
80
]
},
"traefik_https_port": {
"$id": "#/properties/traefik_https_port",
"type": "integer",
"title": "Traefik HTTPS port",
"default": 443,
"examples": [
443
]
},
"traefik_datapath": {
"$id": "#/properties/traefik_datapath",
"type": "string",
"title": "Traefik datapath",
"examples": [
"/tmp/cyphernode/traefik"
]
},
"traefik_datapath_custom": {
"$id": "#/properties/traefik_datapath_custom",
"type": "string",
"title": "Traefik custom datapath",
"examples": [
"/tmp/cyphernode/traefik"
]
},
"lightning_announce": {
"$id": "#/properties/lightning_announce",
"type": "boolean",
"title": "Announce lightning ip",
"default": false,
"examples": [
false
]
},
"lightning_external_ip": {
"$id": "#/properties/lightning_external_ip",
"type": "string",
"format": "ipv4",
"title": "External lightning node ip",
"examples": [
"123.123.123.123"
]
},
"bitcoin_mode": {
"$id": "#/properties/bitcoin_mode",
"type": "string",
"enum": [
"internal"
],
"title": "Bitcoin mode",
"default": "internal",
"examples": [
"internal"
]
},
"bitcoin_expose": {
"$id": "#/properties/bitcoin_expose",
"type": "boolean",
"title": "Expose bitcoin node",
"default": false,
"examples": [
false
]
},
"lightning_expose": {
"$id": "#/properties/lightning_expose",
"type": "boolean",
"title": "Expose lightning node",
"default": false,
"examples": [
false
]
},
"gatekeeper_expose": {
"$id": "#/properties/gatekeeper_expose",
"type": "boolean",
"title": "Expose gatekeeper port",
"default": false,
"examples": [
false
]
},
"gatekeeper_datapath": {
"$id": "#/properties/gatekeeper_datapath",
"type": "string",
"title": "Gatekeeper datapath",
"examples": [
"/tmp/cyphernode/gatekeeper"
]
},
"gatekeeper_datapath_custom": {
"$id": "#/properties/gatekeeper_datapath_custom",
"type": "string",
"title": "Gatekeeper custom datapath",
"examples": [
"/tmp/cyphernode/gatekeeper"
]
},
"gatekeeper_port": {
"$id": "#/properties/gatekeeper_port",
"type": "integer",
"title": "Gatekeeper port",
"default": 2009,
"examples": [
2009
]
},
"gatekeeper_keys": {
"$id": "#/properties/gatekeeper_keys",
"type": "object",
"title": "Gatekeeper keys",
"default": {
"configEntries": [],
"clientInformation": []
},
"required": [
"configEntries",
"clientInformation"
],
"properties": {
"configEntries": {
"$id": "#/properties/gatekeeper_keys/configEntries",
"type": "array",
"items": {
"$id": "#/properties/gatekeeper_keys/configEntries/entry",
"type": "string",
"pattern": "^kapi_id=\".+\";kapi_key=\".+\";kapi_groups=\".+\";.+$"
},
"examples": [
[
"kapi_id=\"000\";kapi_key=\"a27f9e73fdde6a5005879c259c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"stats\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"001\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"stats,watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"002\";kapi_key=\"fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d\";kapi_groups=\"stats,watcher,spender\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"003\";kapi_key=\"f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417\";kapi_groups=\"stats,watcher,spender,admin\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}"
]
]
},
"clientInformation": {
"$id": "#/properties/gatekeeper_keys/clientInformation",
"type": "array",
"items": {
"$id": "#/properties/gatekeeper_keys/clientInformation/entry",
"type": "string",
"pattern": "^.+=.+$"
},
"examples": [
[
"000=a27f9e73fdde6a5005879c259c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"001=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"002=fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d",
"003=f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417"
]
]
}
}
},
"gatekeeper_sslcert": {
"$id": "#/properties/gatekeeper_sslcert",
"type": "string",
"title": "Gatekeeper SSL Cert"
},
"gatekeeper_sslkey": {
"$id": "#/properties/gatekeeper_sslkey",
"type": "string",
"title": "Gatekeeper SSL Key"
},
"gatekeeper_cns": {
"$id": "#/properties/gatekeeper_cns",
"type": "string",
"title": "Gatekeeper cns",
"examples": [
"myhost.mydomain.com,*.myotherdomain.com,123.123.123.123"
]
},
"gatekeeper_clientkeyspassword": {
"$id": "#/properties/gatekeeper_clientkeyspassword",
"type": "string",
"title": "Password for the encrypted client keys archive"
},
"adminhash": {
"$id": "#/properties/adminhash",
"type": "string",
"title": "Bcrypted hash of admin password"
},
"lightning_implementation": {
"$id": "#/properties/lightning_implementation",
"type": "string",
"enum": [
"c-lightning"
],
"title": "The lightning implementation",
"default": "c-lightning",
"examples": [
"c-lightning"
]
},
"lightning_nodename": {
"$id": "#/properties/lightning_nodename",
"type": "string",
"title": "The lightning node name",
"examples": [
"🚀 Mighty Moose 🚀"
]
},
"lightning_nodecolor": {
"$id": "#/properties/lightning_nodecolor",
"type": "string",
"pattern": "^[0-9A-Fa-f]{6}$",
"title": "The lightning node color",
"examples": [
"ff0000",
"00ff00",
"00ffff"
]
}
}
}

View File

@@ -1,6 +1,9 @@
<% if (net === 'testnet') { %>
# testnet
testnet=1
<% } else if( net === 'regtest' ) { %>
# regtest
regtest=1
<% } %>
rpcconnect=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>

View File

@@ -1,9 +1,12 @@
<% if (net === 'testnet') { %>
# testnet
testnet=1
<% } else if( net === 'regtest' ) { %>
# regtest
regtest=1
<% } %>
<% if (bitcoin_prune) { %>
<% if (bitcoin_prune && bitcoin_mode === 'internal') { %>
prune=<%= bitcoin_prune_size || 550 %>
<% } else { %>
txindex=1
@@ -28,16 +31,31 @@ server=1
<% if (net === 'testnet') { %>
test.wallet=watching01.dat
test.wallet=xpubwatching01.dat
test.wallet=spending01.dat
test.wallet=ln01.dat
test.rpcbind=0.0.0.0
<% } else if ( net === 'regtest' ) { %>
regtest.wallet=watching01.dat
regtest.wallet=xpubwatching01.dat
regtest.wallet=spending01.dat
regtest.wallet=ln01.dat
regtest.rpcbind=0.0.0.0
regtest.rpcport=18443
<% } else { %>
main.wallet=watching01.dat
main.wallet=xpubwatching01.dat
main.wallet=spending01.dat
main.wallet=ln01.dat
main.rpcbind=0.0.0.0
<% } %>
walletnotify=curl proxy:8888/conf/%s
walletnotify=/usr/bin/curl proxy:8888/conf/%s
blocknotify=/usr/bin/curl proxy:8888/newblock/%s
<% if ( bitcoin_uacomment != null && bitcoin_uacomment != '' ) { %>
uacomment=<%= bitcoin_uacomment %>
<% } %>
addresstype=bech32
walletrbf=1

View File

@@ -0,0 +1 @@
<%- JSON.stringify( installationInfo, null, 2 ) %>

View File

@@ -0,0 +1,60 @@
# Watcher can do stuff
# Spender can do what the watcher can do plus more stuff
# Admin can do what the spender can do plus even more stuff
# Stats can:
action_helloworld=stats
action_getblockchaininfo=stats
action_installation_info=stats
action_getmempoolinfo=stats
# Watcher can:
action_watch=watcher
action_unwatch=watcher
action_watchxpub=watcher
action_unwatchxpubbyxpub=watcher
action_unwatchxpubbylabel=watcher
action_getactivewatchesbyxpub=watcher
action_getactivewatchesbylabel=watcher
action_getactivexpubwatches=watcher
action_watchtxid=watcher
action_getactivewatches=watcher
action_getbestblockhash=watcher
action_getbestblockinfo=watcher
action_getblockinfo=watcher
action_gettransaction=watcher
action_ots_verify=watcher
action_ots_info=watcher
action_ln_getinfo=watcher
action_ln_create_invoice=watcher
action_ln_getconnectionstring=watcher
action_ln_decodebolt11=watcher
# Spender can do what the watcher can do, plus:
action_getbalance=spender
action_getbalancebyxpub=spender
action_getbalancebyxpublabel=spender
action_getnewaddress=spender
action_spend=spender
action_bumpfee=spender
action_addtobatch=spender
action_batchspend=spender
action_deriveindex=spender
action_derivepubpath=spender
action_ln_pay=spender
action_ln_newaddr=spender
action_ots_stamp=spender
action_ots_getfile=spender
action_ln_getinvoice=spender
action_ln_decodebolt11=spender
action_ln_connectfund=spender
# Admin can do what the spender can do, plus:
# Should be called from inside the Docker network only:
action_conf=internal
action_newblock=internal
action_executecallbacks=internal
action_ots_backoffice=internal

View File

@@ -1,22 +1,25 @@
server {
listen 443 ssl;
listen <%= gatekeeper_port %> ssl;
server_name localhost;
#include /etc/nginx/conf.d/ip-whitelist.conf;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
location /status {
auth_basic "status";
auth_basic_user_file conf.d/status/htpasswd;
location /s/ {
auth_request /auth;
root /etc/nginx/conf.d;
index statuspage.html;
}
location /v0/ {
auth_request /auth;
proxy_pass http://proxy:8888/;
# Up default 60 second timeout for 3 minutes (OTS stamping can take time)
proxy_connect_timeout 180;
proxy_send_timeout 180;
proxy_read_timeout 180;
send_timeout 180;
}
location /auth {

View File

@@ -5,9 +5,12 @@ FEATURE_OTSCLIENT=<%= (features.indexOf('otsclient') != -1)?'true':'false' %>
LIGHTNING_IMPLEMENTATION=<%= lightning_implementation %>
PROXY_DATAPATH=<%= proxy_datapath %>
GATEKEEPER_DATAPATH=<%= gatekeeper_datapath %>
GATEKEEPER_PORT=<%= gatekeeper_port %>
TRAEFIK_DATAPATH=<%= traefik_datapath %>
DOCKER_MODE=<%= docker_mode %>
RUN_AS_USER=<%= run_as_different_user?username:'' %>
CLEANUP=<%= installer_cleanup?'true':'false' %>
SHARED_HTPASSWD_PATH=<%= traefik_datapath %>/htpasswd
<% if ( features.indexOf('lightning') !== -1 && lightning_implementation === 'c-lightning' ) { %>
LIGHTNING_DATAPATH=<%= lightning_datapath %>
<% } %>

View File

@@ -0,0 +1,268 @@
version: "3"
services:
<% if( bitcoin_mode === 'internal' ) { %>
##########################
# BITCOIN #
##########################
bitcoin:
image: cyphernode/bitcoin:<%= bitcoin_version %>
command: $USER bitcoind
<% if( bitcoin_expose ) { %>
ports:
- "<%= (net === 'regtest') ? '18443:18443' : ((net === 'testnet') ? '18332:18332' : '8332:8332') %>"
- "<%= (net === 'regtest') ? '18444:18444' : ((net === 'testnet') ? '18333:18333' : '8333:8333') %>"
<% } %>
volumes:
- "<%= bitcoin_datapath %>:/.bitcoin"
- bitcoin_monitor:/bitcoin_monitor
healthcheck:
test: bitcoin-cli echo && touch /bitcoin_monitor/up || rm -f /bitcoin_monitor/up
interval: 20s
timeout: 5s
retries: 10
networks:
- cyphernodenet
restart: always
# deploy:
# placement:
# constraints: [node.hostname==dev]
<% } %>
##########################
# PROXY #
##########################
proxy:
image: cyphernode/proxy:<%= proxy_version %>
command: $USER ./startproxy.sh
environment:
- "TRACING=1"
- "WATCHER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal') ? 'bitcoin' : bitcoin_node_ip %>:<%= (net === 'regtest') ? '18443' : ((net === 'testnet') ? '18332' : '8332') %>/wallet"
- "WATCHER_BTC_NODE_DEFAULT_WALLET=watching01.dat"
- "WATCHER_BTC_NODE_XPUB_WALLET=xpubwatching01.dat"
- "WATCHER_BTC_NODE_RPC_USER=<%= bitcoin_rpcuser %>:<%= bitcoin_rpcpassword %>"
- "WATCHER_BTC_NODE_RPC_CFG=/tmp/watcher_btcnode_curlcfg.properties"
- "SPENDER_BTC_NODE_RPC_URL=<%= (bitcoin_mode === 'internal') ? 'bitcoin' : bitcoin_node_ip %>:<%= (net === 'regtest') ? '18443' : ((net === 'testnet') ? '18332' : '8332') %>/wallet"
- "SPENDER_BTC_NODE_DEFAULT_WALLET=spending01.dat"
- "SPENDER_BTC_NODE_RPC_USER=<%= bitcoin_rpcuser %>:<%= bitcoin_rpcpassword %>"
- "SPENDER_BTC_NODE_RPC_CFG=/tmp/spender_btcnode_curlcfg.properties"
- "PROXY_LISTENING_PORT=8888"
- "DB_PATH=/proxy/db"
- "DB_FILE=/proxy/db/proxydb"
- "PYCOIN_CONTAINER=pycoin:7777"
<% if ( use_xpub && xpub ) { %>
- "DERIVATION_PUB32=<%= xpub %>"
- "DERIVATION_PATH=<%= derivation_path %>"
<% } %>
- "WATCHER_BTC_NODE_PRUNED=<%= bitcoin_prune ? 'true' : 'false' %>"
- "OTSCLIENT_CONTAINER=otsclient:6666"
- "OTS_FILES=/proxy/otsfiles"
- "XPUB_DERIVATION_GAP=100"
<% if ( devmode ) { %>
ports:
- "8888:8888"
<% } %>
volumes:
- "<%= proxy_datapath %>:/proxy/db"
<% if ( features.indexOf('lightning') !== -1 && lightning_implementation === 'c-lightning' ) { %>
- "<%= lightning_datapath %>:/.lightning"
<% } %>
<% if ( features.indexOf('otsclient') !== -1 ) { %>
- "<%= otsclient_datapath %>:/proxy/otsfiles"
<% } %>
networks:
- cyphernodenet
restart: always
# deploy:
# placement:
# constraints: [node.hostname==dev]
##########################
# PROXYCRON #
##########################
proxycron:
image: cyphernode/proxycron:<%= proxycron_version %>
environment:
- "TX_CONF_URL=proxy:8888/executecallbacks"
- "OTS_URL=proxy:8888/ots_backoffice"
networks:
- cyphernodenet
restart: always
depends_on:
- proxy
# deploy:
# placement:
# constraints: [node.hostname==dev]
##########################
# BROKER #
##########################
broker:
image: eclipse-mosquitto:1.6
networks:
- cyphernodenet
restart: always
# deploy:
# placement:
# constraints: [node.hostname==dev]
##########################
# NOTIFIER #
##########################
notifier:
image: cyphernode/notifier:<%= notifier_version %>
command: $USER ./startnotifier.sh
networks:
- cyphernodenet
- cyphernodeappsnet
restart: always
depends_on:
- broker
# deploy:
# placement:
# constraints: [node.hostname==dev]
##########################
# PYCOIN #
##########################
pycoin:
image: cyphernode/pycoin:<%= pycoin_version %>
command: $USER ./startpycoin.sh
environment:
- "TRACING=1"
- "PYCOIN_LISTENING_PORT=7777"
<% if ( devmode ) { %>
ports:
- "7777:7777"
<% } %>
networks:
- cyphernodenet
restart: always
# deploy:
# placement:
# constraints: [node.hostname==dev]
<% if ( features.indexOf('otsclient') !== -1 ) { %>
##########################
# OTSCLIENT #
##########################
otsclient:
image: cyphernode/otsclient:<%= otsclient_version %>
command: $USER /script/startotsclient.sh
environment:
- "TRACING=1"
- "OTSCLIENT_LISTENING_PORT=6666"
<% if (net === 'testnet') { %>
- "TESTNET=1"
<% } %>
volumes:
- "<%= otsclient_datapath %>:/otsfiles"
- "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf"
command: $USER /script/startotsclient.sh
networks:
- cyphernodenet
restart: always
# deploy:
# placement:
# constraints: [node.hostname==dev]
<% } %>
##########################
# GATEKEEPER #
##########################
gatekeeper:
# HTTP authentication API gate
image: cyphernode/gatekeeper:<%= gatekeeper_version %>
command: $USER
environment:
- "TRACING=1"
<% if( gatekeeper_expose ) { %>
ports:
- "<%= gatekeeper_port %>:<%= gatekeeper_port %>"
<% } %>
volumes:
- "<%= gatekeeper_datapath %>/certs:/etc/ssl/certs"
- "<%= gatekeeper_datapath %>/private:/etc/ssl/private"
- "<%= gatekeeper_datapath %>/keys.properties:/etc/nginx/conf.d/keys.properties"
- "<%= gatekeeper_datapath %>/api.properties:/etc/nginx/conf.d/api.properties"
- "<%= gatekeeper_datapath %>/default.conf:/etc/nginx/conf.d/default.conf"
- "<%= gatekeeper_datapath %>/htpasswd:/etc/nginx/conf.d/status/htpasswd"
- "<%= gatekeeper_datapath %>/installation.json:/etc/nginx/conf.d/s/stats/installation.json"
- "<%= gatekeeper_datapath %>/client.7z:/etc/nginx/conf.d/s/stats/client.7z"
- "<%= gatekeeper_datapath %>/config.7z:/etc/nginx/conf.d/s/stats/config.7z"
networks:
- cyphernodenet
- cyphernodeappsnet
restart: always
depends_on:
- proxy
# deploy:
# placement:
# constraints: [node.hostname==dev]
##########################
# TRAEFIK #
##########################
traefik:
image: traefik:v1.7.9-alpine
ports:
- 80:80
- 443:443
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "<%= traefik_datapath%>/traefik.toml:/traefik.toml"
- "<%= traefik_datapath%>/acme.json:/acme.json"
- "<%= traefik_datapath%>/htpasswd:/htpasswd/htpasswd"
networks:
- cyphernodeappsnet
restart: always
depends_on:
- gatekeeper
# deploy:
# placement:
# constraints: [node.hostname==dev]
<% if ( features.indexOf('lightning') !== -1 && lightning_implementation === 'c-lightning' ) { %>
##########################
# LIGHTNING #
##########################
lightning:
image: cyphernode/clightning:<%= lightning_version %>
command: $USER sh -c 'while [ ! -f "/bitcoin_monitor/up" ]; do echo "bitcoin not ready" ; sleep 10 ; done ; echo "bitcoin ready!" ; lightningd'
<% if( lightning_expose ) { %>
ports:
- "9735:9735"
<% } %>
volumes:
- "<%= lightning_datapath %>:/.lightning"
- "<%= bitcoin_datapath %>/bitcoin-client.conf:/.bitcoin/bitcoin.conf"
- bitcoin_monitor:/bitcoin_monitor:ro
networks:
- cyphernodenet
restart: always
depends_on:
- bitcoin
# deploy:
# placement:
# constraints: [node.hostname==dev]
<% } %>
volumes:
bitcoin_monitor:
networks:
cyphernodenet:
external: true
cyphernodeappsnet:
external: true

View File

@@ -0,0 +1,69 @@
#!/bin/sh
. ./.cyphernodeconf/installer/config.sh
# be aware that randomly downloaded cyphernode apps will have access to
# your configuration and filesystem.
# !!!!!!!!! DO NOT INCLUDE APPS WITHOUT REVIEW !!!!!!!!!!
# TODO: Test if we can mitigate this security issue by
# running app dockers inside a docker container
start_apps() {
local SCRIPT_NAME="start.sh"
local APP_SCRIPT_PATH
local APP_START_SCRIPT_PATH
local APP_ID
for i in $current_path/apps/*
do
APP_SCRIPT_PATH=$(echo $i)
if [ -d "$APP_SCRIPT_PATH" ] && [ ! -f "$APP_SCRIPT_PATH/ignoreThisApp" ]; then
APP_START_SCRIPT_PATH="$APP_SCRIPT_PATH/$SCRIPT_NAME"
APP_ID=$(basename $APP_SCRIPT_PATH)
if [ -f "$APP_START_SCRIPT_PATH" ]; then
. $APP_START_SCRIPT_PATH
elif [ -f "$APP_SCRIPT_PATH/docker-compose.yaml" ]; then
export SHARED_HTPASSWD_PATH
export GATEKEEPER_DATAPATH
export GATEKEEPER_PORT
export LIGHTNING_DATAPATH
export BITCOIN_DATAPATH
export APP_SCRIPT_PATH
export APP_ID
export DOCKER_MODE
if [ "$DOCKER_MODE" = "swarm" ]; then
docker stack deploy -c $APP_SCRIPT_PATH/docker-compose.yaml $APP_ID
elif [ "$DOCKER_MODE" = "compose" ]; then
docker-compose -f $APP_SCRIPT_PATH/docker-compose.yaml up -d --remove-orphans
fi
fi
fi
done
}
<% if (run_as_different_user) { %>
OS=$(uname -s)
if [ "$OS" = "Darwin" ]; then
printf "\r\n\033[0;91m'Run as another user' feature is not supported on OSX. User <%= default_username %> will be used to run Cyphernode.\033[0m\r\n\r\n"
export USER=$(id -u <%= default_username %>):$(id -g <%= default_username %>)
else
export USER=$(id -u <%= username %>):$(id -g <%= username %>)
fi
<% } else { %>
export USER=$(id -u <%= default_username %>):$(id -g <%= default_username %>)
<% } %>
export ARCH=$(uname -m)
current_path="$(cd "$(dirname "$0")" >/dev/null && pwd)"
<% if (docker_mode == 'swarm') { %>
docker stack deploy -c $current_path/docker-compose.yaml cyphernode
<% } else if(docker_mode == 'compose') { %>
docker-compose -f $current_path/docker-compose.yaml up -d --remove-orphans
<% } %>
start_apps
. ./testdeployment.sh

View File

@@ -0,0 +1,59 @@
#!/bin/sh
current_path="$(cd "$(dirname "$0")" >/dev/null && pwd)"
# be aware that randomly downloaded cyphernode apps will have access to
# your configuration and filesystem.
# !!!!!!!!! DO NOT INCLUDE APPS WITHOUT REVIEW !!!!!!!!!!
# TODO: Test if we can mitigate this security issue by
# running app dockers inside a docker container
stop_apps() {
local SCRIPT_NAME="stop.sh"
local APP_SCRIPT_PATH
local APP_START_SCRIPT_PATH
local APP_ID
for i in $current_path/apps/*
do
APP_SCRIPT_PATH=$(echo $i)
if [ -d "$APP_SCRIPT_PATH" ] && [ ! -f "$APP_SCRIPT_PATH/ignoreThisApp" ]; then
APP_STOP_SCRIPT_PATH="$APP_SCRIPT_PATH/$SCRIPT_NAME"
APP_ID=$(basename $APP_SCRIPT_PATH)
if [ -f "$APP_STOP_SCRIPT_PATH" ]; then
. $APP_STOP_SCRIPT_PATH
elif [ -f "$APP_SCRIPT_PATH/docker-compose.yaml" ]; then
export SHARED_HTPASSWD_PATH
export GATEKEEPER_DATAPATH
export GATEKEEPER_PORT
export LIGHTNING_DATAPATH
export BITCOIN_DATAPATH
export APP_SCRIPT_PATH
export APP_ID
export DOCKER_MODE
if [ "$DOCKER_MODE" = "swarm" ]; then
docker stack rm $APP_ID
elif [ "$DOCKER_MODE" = "compose" ]; then
docker-compose -f $APP_SCRIPT_PATH/docker-compose.yaml down
fi
fi
fi
done
}
. ./.cyphernodeconf/installer/config.sh
stop_apps
<% if (docker_mode == 'swarm') { %>
export USER=$(id -u):$(id -g)
export ARCH=$(uname -m)
docker stack rm cyphernode
<% } else if(docker_mode == 'compose') { %>
export USER=$(id -u):$(id -g)
export ARCH=$(uname -m)
docker-compose -f $current_path/docker-compose.yaml down
<% } %>

View File

@@ -1,9 +1,46 @@
#!/bin/sh
. ./.cyphernodeconf/installer/config.sh
# be aware that randomly downloaded cyphernode apps will have access to
# your configuration and filesystem.
# !!!!!!!!! DO NOT INCLUDE APPS WITHOUT REVIEW !!!!!!!!!!
# TODO: Test if we can mitigate this security issue by
# running app dockers inside a docker container
test_apps() {
local SCRIPT_NAME="test.sh"
local APP_SCRIPT_PATH
local APP_START_SCRIPT_PATH
local APP_ID
local returncode=0
for i in $current_path/apps/*
do
APP_SCRIPT_PATH=$(echo $i)
if [ -d "$APP_SCRIPT_PATH" ]; then
APP_TEST_SCRIPT_PATH="$APP_SCRIPT_PATH/$SCRIPT_NAME"
if [ -f "$APP_TEST_SCRIPT_PATH" ] && [ ! -f "$APP_SCRIPT_PATH/ignoreThisApp" ]; then
APP_ID=$(basename "$APP_SCRIPT_PATH")
printf "\r\n\e[1;36mTesting $APP_ID... \e[1;0m"
. $APP_TEST_SCRIPT_PATH
local rc=$?
if [ ""$rc -eq "0" ]; then
printf "\e[1;36m$APP_ID rocks!\e[1;0m"
fi
returncode=$(($rc | ${returncode}))
echo ""
fi
fi
done
return $returncode
}
<% if (run_as_different_user) { %>
OS=$(uname -s)
if [ "$OS" = "Darwin" ]; then
printf "\r\n\033[0;91m'Run as another user' feature is not supported on OSX. User <%= default_username %> will be used to run Cyphernode.\033[0m\r\n\r\n"
export USER=$(id -u <%= default_username %>):$(id -g <%= default_username %>)
else
export USER=$(id -u <%= username %>):$(id -g <%= username %>)
@@ -15,12 +52,6 @@ export USER=$(id -u <%= default_username %>):$(id -g <%= default_username %>)
export ARCH=$(uname -m)
current_path="$(cd "$(dirname "$0")" >/dev/null && pwd)"
<% if (docker_mode == 'swarm') { %>
docker stack deploy -c $current_path/docker-compose.yaml cyphernode
<% } else if(docker_mode == 'compose') { %>
docker-compose -f $current_path/docker-compose.yaml up -d --remove-orphans
<% } %>
arch=$(uname -m)
case "${arch}" in arm*)
printf "\r\n\033[1;31mSince we're on a slow RPi, let's give Docker 60 more seconds before performing our tests...\033[0m\r\n\r\n"
@@ -32,19 +63,26 @@ esac
docker run --rm -it -v $current_path/testfeatures.sh:/testfeatures.sh \
-v <%= gatekeeper_datapath %>:/gatekeeper \
-v $current_path:/dist \
--network cyphernodenet alpine:3.8 /testfeatures.sh
-v cyphernode_bitcoin_monitor:/bitcoin_monitor:ro \
--network cyphernodenet eclipse-mosquitto:1.6.2 /testfeatures.sh
if [ -f $current_path/exitStatus.sh ]; then
. $current_path/exitStatus.sh
rm -f $current_path/exitStatus.sh
fi
test_apps
EXIT_STATUS=$(($? | ${EXIT_STATUS}))
printf "\r\n\e[1;32mTests finished.\e[0m\n"
if [ "$EXIT_STATUS" -ne "0" ]; then
printf "\r\n\033[1;31mThere was an error during cyphernode installation. Please see Docker's logs for more information. Run ./stop.sh to stop cyphernode.\r\n\r\n\033[0m"
printf "\r\n\033[1;31mThere was an error during cyphernode installation. Please see Docker's logs for more information. Run ./testdeployment.sh to rerun the tests. Run ./stop.sh to stop cyphernode.\r\n\r\n\033[0m"
exit 1
fi
printf "\r\n\033[0;92mDepending on your current location and DNS settings, point your favorite browser to one of the following URLs to access Cyphernode's status page:\r\n"
printf "\r\n"
printf "\033[0;95m<% cns.forEach(cn => { %><%= ('https://' + cn + '/status/\\r\\n') %><% }) %>\033[0m\r\n"
printf "\033[0;95m<% cns.forEach(cn => { %><%= ('https://' + cn + '/welcome\\r\\n') %><% }) %>\033[0m\r\n"
printf "\033[0;92mUse 'admin' as the username with the configuration password you selected at the beginning of the configuration process.\r\n\r\n\033[0m"

View File

@@ -24,7 +24,7 @@ checkgatekeeper() {
sleep 2
echo " Testing expired request... " > /dev/console
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper/v0/getblockinfo)
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper:<%= gatekeeper_port %>/v0/getblockinfo)
[ "${rc}" -ne "403" ] && return 10
# Let's test authentication (signature)
@@ -34,7 +34,7 @@ checkgatekeeper() {
token="$h64.$p64.a$s"
echo " Testing bad signature... " > /dev/console
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper/v0/getblockinfo)
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper:<%= gatekeeper_port %>/v0/getblockinfo)
[ "${rc}" -ne "403" ] && return 30
# Let's test authorization (action access for groups)
@@ -42,7 +42,7 @@ checkgatekeeper() {
token="$h64.$p64.$s"
echo " Testing watcher trying to do a spender action... " > /dev/console
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper/v0/getbalance)
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper:<%= gatekeeper_port %>/v0/getbalance)
[ "${rc}" -ne "403" ] && return 40
id="002"
@@ -52,7 +52,7 @@ checkgatekeeper() {
token="$h64.$p64.$s"
echo " Testing spender trying to do an internal action call... " > /dev/console
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper/v0/conf)
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper:<%= gatekeeper_port %>/v0/conf)
[ "${rc}" -ne "403" ] && return 50
@@ -63,7 +63,7 @@ checkgatekeeper() {
token="$h64.$p64.$s"
echo " Testing admin trying to do an internal action call... " > /dev/console
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper/v0/conf)
rc=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $token" --cacert /gatekeeper/certs/cert.pem https://gatekeeper:<%= gatekeeper_port %>/v0/conf)
[ "${rc}" -ne "403" ] && return 60
echo -e "\e[1;36mGatekeeper rocks!" > /dev/console
@@ -83,6 +83,34 @@ checkpycoin() {
return 0
}
checkbroker() {
echo -en "\r\n\e[1;36mTesting Broker... " > /dev/console
local rc
rc=$(mosquitto_pub -h broker -t "testtopic" -m "testbroker")
[ "$?" -ne "0" ] && return 110
echo -e "\e[1;36mBroker rocks!" > /dev/console
return 0
}
checknotifier() {
echo -en "\r\n\e[1;36mTesting Notifier... " > /dev/console
local response
local returncode
response=$(mosquitto_rr -h broker -W 15 -t notifier -e "response/$$" -m "{\"response-topic\":\"response/$$\",\"cmd\":\"web\",\"url\":\"http://proxy:8888/helloworld\"}")
returncode=$?
[ "${returncode}" -ne "0" ] && return 115
http_code=$(echo "${response}" | jq -r ".http_code")
[ "${http_code}" -ge "400" ] && return 118
echo -e "\e[1;36mNotifier rocks!" > /dev/console
return 0
}
checkots() {
echo -en "\r\n\e[1;36mTesting OTSclient... " > /dev/console
local rc
@@ -121,8 +149,8 @@ checklnnode() {
}
checkservice() {
local interval=10
local totaltime=120
local interval=15
local totaltime=180
local outcome
local returncode=0
local endtime=$(($(date +%s) + ${totaltime}))
@@ -133,12 +161,12 @@ checkservice() {
while :
do
outcome=0
for container in gatekeeper proxy proxycron pycoin <%= (features.indexOf('otsclient') != -1)?'otsclient ':'' %>bitcoin <%= (features.indexOf('lightning') != -1)?'lightning ':'' %>; do
for container in gatekeeper proxy proxycron broker notifier pycoin <%= (features.indexOf('otsclient') != -1)?'otsclient ':'' %>bitcoin <%= (features.indexOf('lightning') != -1)?'lightning ':'' %>; do
echo -e " \e[0;32mVerifying \e[0;33m${container}\e[0;32m..." > /dev/console
(ping -c 10 ${container} 2> /dev/null | grep "0% packet loss" > /dev/null) &
eval ${container}=$!
done
for container in gatekeeper proxy proxycron pycoin <%= (features.indexOf('otsclient') != -1)?'otsclient ':'' %>bitcoin <%= (features.indexOf('lightning') != -1)?'lightning ':'' %>; do
for container in gatekeeper proxy proxycron broker notifier pycoin <%= (features.indexOf('otsclient') != -1)?'otsclient ':'' %>bitcoin <%= (features.indexOf('lightning') != -1)?'lightning ':'' %>; do
eval wait '$'${container} ; returncode=$? ; outcome=$((${outcome} + ${returncode}))
eval c_${container}=${returncode}
done
@@ -158,9 +186,9 @@ checkservice() {
# { "name": "pycoin", "active":true },
# { "name": "otsclient", "active":true },
# { "name": "bitcoin", "active":true },
# { "name": "lightning", "active":true }
# { "name": "lightning", "active":true },
# ]
for container in gatekeeper proxy proxycron pycoin <%= (features.indexOf('otsclient') != -1)?'otsclient ':'' %>bitcoin <%= (features.indexOf('lightning') != -1)?'lightning ':'' %>; do
for container in gatekeeper proxy proxycron broker notifier pycoin <%= (features.indexOf('otsclient') != -1)?'otsclient ':'' %>bitcoin <%= (features.indexOf('lightning') != -1)?'lightning ':'' %>; do
[ -n "${result}" ] && result="${result},"
result="${result}{\"name\":\"${container}\",\"active\":"
eval "returncode=\$c_${container}"
@@ -179,8 +207,8 @@ checkservice() {
}
timeout_feature() {
local interval=10
local totaltime=60
local interval=15
local totaltime=120
local testwhat=${1}
local returncode
local endtime=$(($(date +%s) + ${totaltime}))
@@ -218,14 +246,14 @@ feature_status() {
# { "name": "pycoin", "active":true },
# { "name": "otsclient", "active":true },
# { "name": "bitcoin", "active":true },
# { "name": "lightning", "active":true }
# { "name": "lightning", "active":true },
# ],
# "features": [
# { "name": "gatekeeper", "working":true },
# { "name": "pycoin", "working":true },
# { "name": "otsclient", "working":true },
# { "name": "bitcoin", "working":true },
# { "name": "lightning", "working":true }
# { "name": "lightning", "working":true },
# ]
#}
@@ -233,7 +261,11 @@ feature_status() {
echo "EXIT_STATUS=1" > /dist/exitStatus.sh
brokenproxy="false"
#############################
# Ping containers and PROXY #
#############################
workingproxy="true"
containers=$(checkservice)
returncode=$?
finalreturncode=${returncode}
@@ -242,7 +274,7 @@ if [ "${returncode}" -ne "0" ]; then
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"proxy\") | .active")
if [ "${status}" = "false" ]; then
echo -e "\e[1;31mThe Proxy, the main Cyphernode's component, is not responding. We will only test the gatekeeper if its container is up, but you'll see errors for the other components. Please check the logs." > /dev/console
brokenproxy="true"
workingproxy="false"
fi
else
echo -e "\e[1;36mCyphernode seems to be correctly deployed. Let's run more thourough tests..." > /dev/console
@@ -254,10 +286,14 @@ fi
# { "name": "pycoin", "working":true },
# { "name": "otsclient", "working":true },
# { "name": "bitcoin", "working":true },
# { "name": "lightning", "working":true }
# { "name": "lightning", "working":true },
# ]
result="${containers},\"features\":[{\"name\":\"gatekeeper\",\"working\":"
#############################
# GATEKEEPER #
#############################
result="${containers},\"features\":[{\"coreFeature\":true, \"name\":\"proxy\",\"working\":${workingproxy}}, {\"coreFeature\":true, \"name\":\"gatekeeper\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"gatekeeper\") | .active")
if [ "${status}" = "true" ]; then
timeout_feature checkgatekeeper
@@ -268,9 +304,43 @@ fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Gatekeeper error!')}"
result="${result},{\"name\":\"pycoin\",\"working\":"
#############################
# BROKER #
#############################
result="${result},{\"coreFeature\":true, \"name\":\"broker\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"broker\") | .active")
if [[ "${workingproxy}" = "true" && "${status}" = "true" ]]; then
timeout_feature checkbroker
returncode=$?
else
returncode=1
fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Broker error!')}"
#############################
# NOTIFIER #
#############################
result="${result},{\"coreFeature\":true, \"name\":\"notifier\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"notifier\") | .active")
if [[ "${workingproxy}" = "true" && "${status}" = "true" ]]; then
timeout_feature checknotifier
returncode=$?
else
returncode=1
fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Notifier error!')}"
#############################
# PYCOIN #
#############################
result="${result},{\"coreFeature\":true, \"name\":\"pycoin\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"pycoin\") | .active")
if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then
if [[ "${workingproxy}" = "true" && "${status}" = "true" ]]; then
timeout_feature checkpycoin
returncode=$?
else
@@ -280,9 +350,13 @@ finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Pycoin error!')}"
<% if (features.indexOf('otsclient') != -1) { %>
result="${result},{\"name\":\"otsclient\",\"working\":"
#############################
# OTSCLIENT #
#############################
result="${result},{\"coreFeature\":false, \"name\":\"otsclient\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"otsclient\") | .active")
if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then
if [[ "${workingproxy}" = "true" && "${status}" = "true" ]]; then
timeout_feature checkots
returncode=$?
else
@@ -292,9 +366,16 @@ finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'OTSclient error!')}"
<% } %>
result="${result},{\"name\":\"bitcoin\",\"working\":"
#############################
# BITCOIN #
#############################
echo -e "\r\n\e[1;36mWaiting for Bitcoin Core to be ready... " > /dev/console
timeout_feature '[ -f "/bitcoin_monitor/up" ]'
result="${result},{\"coreFeature\":true, \"name\":\"bitcoin\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"bitcoin\") | .active")
if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then
if [[ "${workingproxy}" = "true" && "${status}" = "true" ]]; then
timeout_feature checkbitcoinnode
returncode=$?
else
@@ -304,9 +385,13 @@ finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Bitcoin error!')}"
<% if (features.indexOf('lightning') != -1) { %>
result="${result},{\"name\":\"lightning\",\"working\":"
#############################
# LIGHTNING #
#############################
result="${result},{\"coreFeature\":false, \"name\":\"lightning\",\"working\":"
status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"lightning\") | .active")
if [[ "${brokenproxy}" != "true" && "${status}" = "true" ]]; then
if [[ "${workingproxy}" = "true" && "${status}" = "true" ]]; then
timeout_feature checklnnode
returncode=$?
else
@@ -314,12 +399,11 @@ else
fi
finalreturncode=$((${returncode} | ${finalreturncode}))
result="${result}$(feature_status ${returncode} 'Lightning error!')}"
<% } %>
result="{${result}]}"
echo "${result}" > /gatekeeper/installation.json
echo -e "\r\n\e[1;32mTests finished.\e[0m" > /dev/console
echo "EXIT_STATUS=${finalreturncode}" > /dist/exitStatus.sh

View File

@@ -0,0 +1,5 @@
# How to create the hmac for the cookie file:
```
# echo -n "access-key" | openssl dgst -hmac "cyphernode:sparkwallet" -sha256 -binary | base64 | sed 's/[\+\W]//g'
```

View File

@@ -1,4 +1,7 @@
<% if (net === 'testnet') { %>
<% if (net === 'regtest') { %>
# regtest
network=regtest
<% } else if (net === 'testnet') { %>
# testnet
network=testnet
<% } else if (net === 'mainnet') { %>
@@ -10,6 +13,9 @@ alias=<%= lightning_nodename %>
<% if( lightning_nodecolor ) { %>
rgb=<%= lightning_nodecolor %>
<% } %>
bitcoin-rpcconnect=<%= (bitcoin_mode === 'internal')?'bitcoin':bitcoin_node_ip %>
bitcoin-rpcuser=<%= bitcoin_rpcuser %>
bitcoin-rpcpassword=<%= bitcoin_rpcpassword %>
addr=0.0.0.0:9735
<% if( locals.lightning_external_ip ) { %>
announce-addr=<%= locals.lightning_external_ip %>:9735
<% } %>

View File

@@ -0,0 +1 @@
cyphernode:sparkwallet:FoeDdQw5yl7pPfqdlGy3OEk/txGqyJjSbVtffhzs7kc=

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
admin:<%- adminhash %>

View File

@@ -0,0 +1,31 @@
debug = false
logLevel = "ERROR"
defaultEntryPoints = ["https","http"]
[entryPoints]
[entryPoints.http]
address = ":<%= traefik_http_port %>"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":<%= traefik_https_port %>"
[entryPoints.https.tls]
[retry]
[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "cyphernode.localhost"
watch = true
exposedByDefault = false
[acme]
email = "letsencrypt@yourdomain.com"
storage = "acme.json"
entryPoint = "https"
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"
[[acme.domains]]
main = "cyphernode.yourdomain.com"

View File

@@ -0,0 +1,42 @@
const ApiKey = require('../lib/apikey.js');
test( 'Create ApiKey instance', ()=>{
const apiKey = new ApiKey('testId',['group1','group2']);
expect( apiKey ).not.toBe( undefined );
expect( apiKey.id ).toEqual( 'testId' );
expect( apiKey.groups ).toEqual( ['group1','group2'] );
expect( apiKey.key ).toBe( undefined );
expect( apiKey.script ).toEqual( 'eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}' );
});
test( 'Create ApiKey instance and randomise it', async ()=>{
const apiKey = new ApiKey('testId',['group1','group2']);
await apiKey.randomiseKey();
expect( apiKey ).not.toBe( undefined );
expect( apiKey.id ).toEqual( 'testId' );
expect( apiKey.groups ).toEqual( ['group1','group2'] );
expect( apiKey.key ).not.toBe( undefined );
expect( apiKey.script ).toEqual( 'eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}' );
});
test( 'Create ApiKey instance, randomise it and use getters', async ()=>{
const apiKey = new ApiKey('testId',['group1','group2']);
await apiKey.randomiseKey();
const keyString = apiKey.getKey();
const script = apiKey.script;
expect( keyString ).not.toBe( undefined );
expect( apiKey.id ).toEqual( 'testId' );
expect( apiKey.getClientInformation() ).toEqual( 'testId='+keyString );
expect( apiKey.getConfigEntry() ).toEqual( `kapi_id="testId";kapi_key="${keyString}";kapi_groups="group1,group2";${script}` );
});
test( 'Set properties of ApiKey instance from config entry', async () => {
const configEntry = 'kapi_id="000";kapi_key="b1fdc782037609f8ecc063ac192e92d57544263a950c637ed6b7d79cc9eb9f95";kapi_groups="stats";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}';
const apiKey = new ApiKey();
apiKey.setFromConfigEntry(configEntry);
expect( apiKey.id ).toEqual('000');
expect( apiKey.groups ).toEqual(['stats']);
expect( apiKey.key ).toEqual('b1fdc782037609f8ecc063ac192e92d57544263a950c637ed6b7d79cc9eb9f95');
expect( apiKey.script ).toEqual('eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}')
})

View File

@@ -0,0 +1,26 @@
const Archive = require('../lib/archive.js');
const tmp = require('tmp');
const path = require('path');
test( 'Create Archive instance', ()=>{
new Archive( '/tmp/testArchive.7z', 'test123' );
});
test( 'Write, Read, Delete', async ()=>{
const tmpDir = tmp.dirSync();
const archive = new Archive( path.join(tmpDir.name,'archive.7z'), 'test123' );
await archive.writeEntry('testEntry', 'testContent' );
const c0 = await archive.readEntry( 'testEntry' );
expect( c0.value ).toEqual( 'testContent' );
await archive.deleteEntry('testEntry');
const c1 = await archive.readEntry( 'testEntry' );
expect( c1.value ).toBe( '' );
tmpDir.removeCallback();
});

View File

@@ -0,0 +1,60 @@
const Cert = require('../lib/cert.js');
test( 'Create Cert instance', ()=>{
const cert = new Cert();
expect( cert.args.days ).toBe( 3650 );
});
test( 'buildConfig', ()=>{
const cert = new Cert();
const conf = cert.buildConfig(['127.0.0.1','localhost','gatekeeper']);
expect( conf ).toEqual(`
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no
[req_distinguished_name]
CN = localhost
[v3_ca]
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = gatekeeper
IP.1 = 127.0.0.1
`);
});
test( 'cns', () => {
const cert = new Cert();
const cns = cert.cns(' abc, cde' );
expect( cns ).toEqual([
'127.0.0.1',
'localhost',
'gatekeeper',
'abc',
'cde'
]);
});
test( 'create', async ()=>{
jest.setTimeout(999999);
const cert = new Cert();
const cns = cert.cns('abc,cde' );
const r = await cert.create( cns );
expect( r.code ).toBe(0);
expect( r.key ).not.toBe(undefined);
expect( r.cert ).not.toBe(undefined);
});
test( 'create throws', async ()=>{
const cert = new Cert();
let err;
try {
await cert.create();
} catch( e ) {
err = e;
}
expect( err ).not.toBe(undefined);
});

View File

@@ -0,0 +1,151 @@
const Config = require('../lib/config.js');
const ApiKey = require('../lib/apikey.js');
const fs = require('fs');
const { promisify } = require( 'util' );
const existsAsync = promisify( fs.exists );
const configV010 = require('./data/config.0.1.0.json');
const configV020 = require('./data/config.0.2.0.json');
const configV022 = require('./data/config.0.2.2.json');
const expect020 = async (data) => {
const gatekeeper_keys = JSON.parse(JSON.stringify(configV010.gatekeeper_keys));
expect( data.gatekeeper_keys.configEntries.length ).toBe( 4 );
for( let i=0; i<3; i++ ) {
const configEntry = data.gatekeeper_keys.configEntries[i+1];
const oldConfigEntry = gatekeeper_keys.configEntries[i];
const key = new ApiKey();
key.setFromConfigEntry( configEntry )
const oldKey = new ApiKey();
oldKey.setFromConfigEntry( oldConfigEntry );
expect( key.id ).toEqual( oldKey.id );
expect( key.key ).toEqual( oldKey.key );
expect( key.script ).toEqual( oldKey.script );
for( let oldGroup of oldKey.groups ) {
expect( key.groups ).toContain(oldGroup);
}
expect( key.groups ).toContain('stats');
}
};
const expect022 = async (data) => {
expect( data.lightning_announce ).not.toBe(undefined);
expect( data.gatekeeper_expose ).not.toBe(undefined);
};
let configFileName;
beforeAll(() => {
configFileName = '/tmp/config'+Math.round(Math.random()*100000000)+'.7z';
});
test( 'create config v0.1.0', () => {
new Config(configV010);
});
test( 'create config v0.2.0', () => {
new Config(configV020);
});
test( 'validate config v0.1.0', () => {
const config = new Config(JSON.parse(JSON.stringify(configV010)));
config.data.foo = "bar";
config.data.bar = "foo";
config.validate();
expect( config.data.foo ).toBe( undefined );
expect( config.data.bar ).toBe( undefined );
});
test( 'validate config v0.2.0', () => {
const config = new Config(JSON.parse(JSON.stringify(configV020)));
config.data.foo = "bar";
config.data.bar = "foo";
config.validate();
expect( config.data.foo ).toBe( undefined );
expect( config.data.bar ).toBe( undefined );
});
test( 'validate config v0.2.2', () => {
const config = new Config(JSON.parse(JSON.stringify(configV022)));
config.data.foo = "bar";
config.data.bar = "foo";
config.validate();
expect( config.data.foo ).toBe( undefined );
expect( config.data.bar ).toBe( undefined );
});
test( 'generateMigrationPathToLatest from 0.1.0', async () => {
const config = new Config();
const path = config.generateMigrationPathToLatest('0.1.0');
expect( path ).toEqual( [config.migrate_0_1_0_to_0_2_0, config.migrate_0_2_0_to_0_2_2] );
});
test( 'generateMigrationPathToLatest from 0.2.0', async () => {
const config = new Config();
const path = config.generateMigrationPathToLatest('0.2.0');
expect( path ).toEqual( [config.migrate_0_2_0_to_0_2_2] );
});
test( 'generateMigrationPathToLatest from 0.2.2', async () => {
const config = new Config();
const path = config.generateMigrationPathToLatest('0.2.2');
expect( path ).toBe( undefined );
});
test( 'migrate 0.1.0 -> 0.2.0', async () => {
const config = new Config();
config.setData( JSON.parse(JSON.stringify(configV010)) );
// deep clone gatekeeper_keys
await config.migrate_0_1_0_to_0_2_0();
expect020( config.data );
});
test( 'migrate 0.2.0 -> 0.2.2', async () => {
const config = new Config();
config.setData( JSON.parse(JSON.stringify(configV020)) );
await config.migrate_0_2_0_to_0_2_2();
expect022(config.data);
});
test( 'migrateFrom 0.1.0', async () => {
const config = new Config();
config.setData( JSON.parse(JSON.stringify(configV010)) );
await config.migrateFrom('0.1.0');
config.validate();
expect020(config.data);
expect022(config.data);
expect( config.data.traefik_http_port ).toEqual( 80 );
expect( config.data.traefik_https_port ).toEqual( 443 );
});
test( 'serialise', async () => {
const config = new Config();
config.setData( JSON.parse(JSON.stringify(configV022)) )
const success = await config.serialize(configFileName,'test123' );
const exists = await existsAsync(configFileName);
expect( success ).toEqual( true );
expect( exists ).toEqual( true );
});
test( 'deserialise', async () => {
const config = new Config( {
setup_version: 'setup_version'
} );
await config.deserialize(configFileName,'test123' );
expect( config.data ).toEqual( configV022 );
});

View File

@@ -0,0 +1,55 @@
{
"features": [
"lightning",
"otsclient"
],
"net": "testnet",
"use_xpub": true,
"installer_mode": "docker",
"run_as_different_user": true,
"username": "cyphernode",
"docker_mode": "compose",
"bitcoin_rpcuser": "bitcoin",
"bitcoin_rpcpassword": "test123",
"bitcoin_uacomment": "",
"bitcoin_prune": false,
"bitcoin_prune_size": 550,
"bitcoin_node_ip": "123.123.123.123",
"bitcoin_datapath": "/Users/jash/.cyphernode/bitcoin",
"bitcoin_mode": "internal",
"bitcoin_expose": false,
"lightning_expose": true,
"gatekeeper_apiproperties": "GKAP",
"gatekeeper_keys": {
"configEntries": [
"kapi_id=\"001\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"002\";kapi_key=\"fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d\";kapi_groups=\"watcher,spender\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"003\";kapi_key=\"f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417\";kapi_groups=\"watcher,spender,admin\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}"
],
"clientInformation": [
"001=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"002=fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d",
"003=f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417"
]
},
"gatekeeper_sslcert": "-----BEGIN CERTIFICATE-----\nMIIE/jCCAuagAwIBAgIJAIBv4aiI2NRtMA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNV\nBAMME2Rpc2swYm9vay5mcml0ei5ib3gwHhcNMTkwMTE3MTcwMDA5WhcNMjkwMTE0\nMTcwMDA5WjAeMRwwGgYDVQQDDBNkaXNrMGJvb2suZnJpdHouYm94MIICIjANBgkq\nhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyVKNTGlPfd4QX9HaDc9a6prbc9il4jtR\niChSlMf3/6UfAMcS+xVgR2iR8FK/DQuFzxn+6BybpoiD333rjDr7zR7y9px3Xph2\nbmsjZy0hv9SIBbx0DJvvwODTlWTAH8qgU2DN6xWc7vjgeGi5uTpnmwWrkH6BjtVr\nwoBkF0JmfH7KiLS/QjWqPKeI6o/GpvCP9meD131Sq/ReoOTrJ4F5aNdhAril4nU5\n6e7Y+Iyp35DZSLuU+pDJAhxEvkYGas1ted5RRxlho8ukaoABCbmaTeNmgsJxK2SC\nABjfUc38aAlNLuMbMMR7Q85Z84OTJiUqanVczwdSj1QHlNCWZK1McBPhj2m2Wdge\ngedrq5XcjQGChzTEozcFntU0qzY3ja1+DOE8UaMaTrDH4saUXCMZk3W1m5mmiZW3\nmcB0cKGdeg6K6USg1BwBTU9qolUusxz5T0tNxjcMlXU93P17d4s5IXfliXhMNr/6\n4fl78Ey3FNprTix4alW7hBAp/eA/LhS55s3jwdoVzJl4RELC0284pahj5exYQwU6\nzjLedMxzC+7veQYwWfZOs9jVCTP0YStuT0j9xD3ausLZyB1Egbsajyy71IeoYOf1\n9S6dFIXE5LHAw2j3D3bh5wb019I8V5szGbeemdBpb3m+bzT8qjLSNranuT41CIHd\nIYjq85vDEJsCAwEAAaM/MD0wOwYDVR0RBDQwMoITZGlzazBib29rLmZyaXR6LmJv\neIIJbG9jYWxob3N0ggpnYXRla2VlcGVyhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IC\nAQBrE4bJsIMwSRPng94PcqR5F6Cux0bkwezALJCHpjHTuqok/wHHE5dZsAXcSsYc\n5givuBESih6CpY5h21Od0TBugyv3FCRY8OoaBXtlO6FYlEnVeJ8AOexJTb3qcbBS\nHU8MBWEydUh5HFA3PRKAG0Y4cvUK4WXJZ42Et3td0NkGFOv6bxdtVGB4Vz7FGn+3\nqd9fpmFCdQYDp6RSZDDz4B8XLsVuTeTES5GbUMSQAGanP7jxMr04wQ3MuoZrRODN\nFatifOJfq0fZddsBjJbrTLxArIqaPh3J4xzwiNE5du4CQDQrbbHXG22kuvbr5foA\ncixLnuyWMq0a5a70mSNS6TZ3nq4ATXNNa0cZ8fBxHqHGTLM8gQisW8vTaZfIFh/i\nhnFcGxtpo1ryi7JG9HCWsh0x20677iag5MuZfv2s4TbK71Ol6WV4FravCqU0qgbn\nTTl+BnYw3H67FO/a6RD4ISlFWK+8EVEQdMgvPoRuw323YznT0Nd8Q/Gq8raYF2wa\nz9T9OXu6TcVGtfPAgX+AM/+hDqWGxyiFR9ZtLpGOHGP8f+TZA5uCawc8Zry4yN6L\nE0yPIx96pJz59T3k8XbRHTQCaPsSUGRAZIY9LpJj0fIG7zCr9eCBpp2qyzmpyNfx\negN3ILYy1Y8JbJj73HWyP0F3Am7i76tkCWB7tQeFOb5FMg==\n-----END CERTIFICATE-----\n",
"gatekeeper_sslkey": "-----BEGIN PRIVATE KEY-----\n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\nmPN+iWirFH2RN5KkSaTzEtrXAgbLvic9\n-----END PRIVATE KEY-----\n",
"gatekeeper_cns": "disk0book.fritz.box",
"proxy_datapath": "/Users/jash/.cyphernode/proxy",
"lightning_implementation": "c-lightning",
"lightning_datapath": "/Users/jash/.cyphernode/lightning",
"lightning_nodename": "🚀 Disciplined Dormouse 🚀",
"lightning_nodecolor": "ff0000",
"otsclient_datapath": "/Users/jash/.cyphernode/otsclient",
"adminhash": "BsJFlh7q4JmwI\n",
"use_xpub": true,
"xpub": "abc",
"derivation_path": "da",
"gatekeeper_clientkeyspassword": "test123",
"gatekeeper_clientkeyspassword_c": "test123",
"gatekeeper_statuspw": "dasfdssad",
"gatekeeper_datapath": "/Users/jash/.cyphernode/gatekeeper",
"traefik_datapath": "foo",
"lightning_external_ip": "123.123.123.123"
}

View File

@@ -0,0 +1,52 @@
{
"schema_version": "0.2.0",
"features": [
"lightning",
"otsclient"
],
"net": "testnet",
"use_xpub": true,
"installer_mode": "docker",
"run_as_different_user": true,
"username": "cyphernode",
"docker_mode": "compose",
"bitcoin_rpcuser": "bitcoin",
"bitcoin_rpcpassword": "test123",
"bitcoin_uacomment": "",
"bitcoin_prune": false,
"bitcoin_prune_size": 550,
"bitcoin_datapath": "/Users/jash/.cyphernode/bitcoin",
"bitcoin_mode": "internal",
"bitcoin_expose": false,
"lightning_expose": true,
"gatekeeper_keys": {
"configEntries": [
"kapi_id=\"000\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"001\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"002\";kapi_key=\"fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d\";kapi_groups=\"watcher,spender\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"003\";kapi_key=\"f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417\";kapi_groups=\"watcher,spender,admin\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}"
],
"clientInformation": [
"000=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"001=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"002=fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d",
"003=f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417"
]
},
"gatekeeper_sslcert": "-----BEGIN CERTIFICATE-----\nMIIE/jCCAuagAwIBAgIJAIBv4aiI2NRtMA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNV\nBAMME2Rpc2swYm9vay5mcml0ei5ib3gwHhcNMTkwMTE3MTcwMDA5WhcNMjkwMTE0\nMTcwMDA5WjAeMRwwGgYDVQQDDBNkaXNrMGJvb2suZnJpdHouYm94MIICIjANBgkq\nhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyVKNTGlPfd4QX9HaDc9a6prbc9il4jtR\niChSlMf3/6UfAMcS+xVgR2iR8FK/DQuFzxn+6BybpoiD333rjDr7zR7y9px3Xph2\nbmsjZy0hv9SIBbx0DJvvwODTlWTAH8qgU2DN6xWc7vjgeGi5uTpnmwWrkH6BjtVr\nwoBkF0JmfH7KiLS/QjWqPKeI6o/GpvCP9meD131Sq/ReoOTrJ4F5aNdhAril4nU5\n6e7Y+Iyp35DZSLuU+pDJAhxEvkYGas1ted5RRxlho8ukaoABCbmaTeNmgsJxK2SC\nABjfUc38aAlNLuMbMMR7Q85Z84OTJiUqanVczwdSj1QHlNCWZK1McBPhj2m2Wdge\ngedrq5XcjQGChzTEozcFntU0qzY3ja1+DOE8UaMaTrDH4saUXCMZk3W1m5mmiZW3\nmcB0cKGdeg6K6USg1BwBTU9qolUusxz5T0tNxjcMlXU93P17d4s5IXfliXhMNr/6\n4fl78Ey3FNprTix4alW7hBAp/eA/LhS55s3jwdoVzJl4RELC0284pahj5exYQwU6\nzjLedMxzC+7veQYwWfZOs9jVCTP0YStuT0j9xD3ausLZyB1Egbsajyy71IeoYOf1\n9S6dFIXE5LHAw2j3D3bh5wb019I8V5szGbeemdBpb3m+bzT8qjLSNranuT41CIHd\nIYjq85vDEJsCAwEAAaM/MD0wOwYDVR0RBDQwMoITZGlzazBib29rLmZyaXR6LmJv\neIIJbG9jYWxob3N0ggpnYXRla2VlcGVyhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IC\nAQBrE4bJsIMwSRPng94PcqR5F6Cux0bkwezALJCHpjHTuqok/wHHE5dZsAXcSsYc\n5givuBESih6CpY5h21Od0TBugyv3FCRY8OoaBXtlO6FYlEnVeJ8AOexJTb3qcbBS\nHU8MBWEydUh5HFA3PRKAG0Y4cvUK4WXJZ42Et3td0NkGFOv6bxdtVGB4Vz7FGn+3\nqd9fpmFCdQYDp6RSZDDz4B8XLsVuTeTES5GbUMSQAGanP7jxMr04wQ3MuoZrRODN\nFatifOJfq0fZddsBjJbrTLxArIqaPh3J4xzwiNE5du4CQDQrbbHXG22kuvbr5foA\ncixLnuyWMq0a5a70mSNS6TZ3nq4ATXNNa0cZ8fBxHqHGTLM8gQisW8vTaZfIFh/i\nhnFcGxtpo1ryi7JG9HCWsh0x20677iag5MuZfv2s4TbK71Ol6WV4FravCqU0qgbn\nTTl+BnYw3H67FO/a6RD4ISlFWK+8EVEQdMgvPoRuw323YznT0Nd8Q/Gq8raYF2wa\nz9T9OXu6TcVGtfPAgX+AM/+hDqWGxyiFR9ZtLpGOHGP8f+TZA5uCawc8Zry4yN6L\nE0yPIx96pJz59T3k8XbRHTQCaPsSUGRAZIY9LpJj0fIG7zCr9eCBpp2qyzmpyNfx\negN3ILYy1Y8JbJj73HWyP0F3Am7i76tkCWB7tQeFOb5FMg==\n-----END CERTIFICATE-----\n",
"gatekeeper_sslkey": "-----BEGIN PRIVATE KEY-----\n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\nmPN+iWirFH2RN5KkSaTzEtrXAgbLvic9\n-----END PRIVATE KEY-----\n",
"gatekeeper_cns": "disk0book.fritz.box",
"proxy_datapath": "/Users/jash/.cyphernode/proxy",
"lightning_implementation": "c-lightning",
"lightning_datapath": "/Users/jash/.cyphernode/lightning",
"lightning_nodename": "🚀 Disciplined Dormouse 🚀",
"lightning_nodecolor": "ff0000",
"otsclient_datapath": "/Users/jash/.cyphernode/otsclient",
"adminhash": "BsJFlh7q4JmwI\n",
"use_xpub": true,
"xpub": "abc",
"derivation_path": "da",
"gatekeeper_clientkeyspassword": "test123",
"gatekeeper_datapath": "/Users/jash/.cyphernode/gatekeeper",
"traefik_datapath": "foo"
}

View File

@@ -0,0 +1,59 @@
{
"schema_version": "0.2.2",
"setup_version": "setup_version",
"docker_versions": {},
"features": [
"lightning",
"otsclient"
],
"net": "testnet",
"use_xpub": true,
"installer_mode": "docker",
"run_as_different_user": true,
"username": "cyphernode",
"docker_mode": "compose",
"bitcoin_rpcuser": "bitcoin",
"bitcoin_rpcpassword": "test123",
"bitcoin_uacomment": "",
"bitcoin_prune": false,
"bitcoin_prune_size": 550,
"bitcoin_datapath": "/Users/jash/.cyphernode/bitcoin",
"bitcoin_mode": "internal",
"bitcoin_expose": false,
"lightning_expose": true,
"gatekeeper_port": 2009,
"gatekeeper_keys": {
"configEntries": [
"kapi_id=\"000\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"001\";kapi_key=\"a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a\";kapi_groups=\"watcher\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"002\";kapi_key=\"fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d\";kapi_groups=\"watcher,spender\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}",
"kapi_id=\"003\";kapi_key=\"f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417\";kapi_groups=\"watcher,spender,admin\";eval ugroups_${kapi_id}=${kapi_groups};eval ukey_${kapi_id}=${kapi_key}"
],
"clientInformation": [
"000=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"001=a27f9e73fdde6a5005879c273c9aea5e8d917eec77bbdfd73272c0af9b4c6b7a",
"002=fe58ddbb66d7302a7087af3242a98b6326c51a257f5eab1c06bb8cc02e25890d",
"003=f0b8bb52f4c7007938757bcdfc73b452d6ce08cc0c660ce57c5464ae95f35417"
]
},
"gatekeeper_sslcert": "-----BEGIN CERTIFICATE-----\nMIIE/jCCAuagAwIBAgIJAIBv4aiI2NRtMA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNV\nBAMME2Rpc2swYm9vay5mcml0ei5ib3gwHhcNMTkwMTE3MTcwMDA5WhcNMjkwMTE0\nMTcwMDA5WjAeMRwwGgYDVQQDDBNkaXNrMGJvb2suZnJpdHouYm94MIICIjANBgkq\nhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyVKNTGlPfd4QX9HaDc9a6prbc9il4jtR\niChSlMf3/6UfAMcS+xVgR2iR8FK/DQuFzxn+6BybpoiD333rjDr7zR7y9px3Xph2\nbmsjZy0hv9SIBbx0DJvvwODTlWTAH8qgU2DN6xWc7vjgeGi5uTpnmwWrkH6BjtVr\nwoBkF0JmfH7KiLS/QjWqPKeI6o/GpvCP9meD131Sq/ReoOTrJ4F5aNdhAril4nU5\n6e7Y+Iyp35DZSLuU+pDJAhxEvkYGas1ted5RRxlho8ukaoABCbmaTeNmgsJxK2SC\nABjfUc38aAlNLuMbMMR7Q85Z84OTJiUqanVczwdSj1QHlNCWZK1McBPhj2m2Wdge\ngedrq5XcjQGChzTEozcFntU0qzY3ja1+DOE8UaMaTrDH4saUXCMZk3W1m5mmiZW3\nmcB0cKGdeg6K6USg1BwBTU9qolUusxz5T0tNxjcMlXU93P17d4s5IXfliXhMNr/6\n4fl78Ey3FNprTix4alW7hBAp/eA/LhS55s3jwdoVzJl4RELC0284pahj5exYQwU6\nzjLedMxzC+7veQYwWfZOs9jVCTP0YStuT0j9xD3ausLZyB1Egbsajyy71IeoYOf1\n9S6dFIXE5LHAw2j3D3bh5wb019I8V5szGbeemdBpb3m+bzT8qjLSNranuT41CIHd\nIYjq85vDEJsCAwEAAaM/MD0wOwYDVR0RBDQwMoITZGlzazBib29rLmZyaXR6LmJv\neIIJbG9jYWxob3N0ggpnYXRla2VlcGVyhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IC\nAQBrE4bJsIMwSRPng94PcqR5F6Cux0bkwezALJCHpjHTuqok/wHHE5dZsAXcSsYc\n5givuBESih6CpY5h21Od0TBugyv3FCRY8OoaBXtlO6FYlEnVeJ8AOexJTb3qcbBS\nHU8MBWEydUh5HFA3PRKAG0Y4cvUK4WXJZ42Et3td0NkGFOv6bxdtVGB4Vz7FGn+3\nqd9fpmFCdQYDp6RSZDDz4B8XLsVuTeTES5GbUMSQAGanP7jxMr04wQ3MuoZrRODN\nFatifOJfq0fZddsBjJbrTLxArIqaPh3J4xzwiNE5du4CQDQrbbHXG22kuvbr5foA\ncixLnuyWMq0a5a70mSNS6TZ3nq4ATXNNa0cZ8fBxHqHGTLM8gQisW8vTaZfIFh/i\nhnFcGxtpo1ryi7JG9HCWsh0x20677iag5MuZfv2s4TbK71Ol6WV4FravCqU0qgbn\nTTl+BnYw3H67FO/a6RD4ISlFWK+8EVEQdMgvPoRuw323YznT0Nd8Q/Gq8raYF2wa\nz9T9OXu6TcVGtfPAgX+AM/+hDqWGxyiFR9ZtLpGOHGP8f+TZA5uCawc8Zry4yN6L\nE0yPIx96pJz59T3k8XbRHTQCaPsSUGRAZIY9LpJj0fIG7zCr9eCBpp2qyzmpyNfx\negN3ILYy1Y8JbJj73HWyP0F3Am7i76tkCWB7tQeFOb5FMg==\n-----END CERTIFICATE-----\n",
"gatekeeper_sslkey": "-----BEGIN PRIVATE KEY-----\n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\nmPN+iWirFH2RN5KkSaTzEtrXAgbLvic9\n-----END PRIVATE KEY-----\n",
"gatekeeper_cns": "disk0book.fritz.box",
"proxy_datapath": "/Users/jash/.cyphernode/proxy",
"lightning_implementation": "c-lightning",
"lightning_datapath": "/Users/jash/.cyphernode/lightning",
"lightning_nodename": "🚀 Disciplined Dormouse 🚀",
"lightning_nodecolor": "ff0000",
"otsclient_datapath": "/Users/jash/.cyphernode/otsclient",
"adminhash": "BsJFlh7q4JmwI\n",
"use_xpub": true,
"xpub": "abc",
"derivation_path": "da",
"gatekeeper_clientkeyspassword": "test123",
"gatekeeper_datapath": "/Users/jash/.cyphernode/gatekeeper",
"traefik_datapath": "foo",
"lightning_announce": true,
"gatekeeper_expose": false,
"traefik_http_port": 80,
"traefik_https_port": 443
}

View File

@@ -0,0 +1,6 @@
const htpasswd = require('../lib/htpasswd.js');
test( 'generate htpasswd', async ()=>{
const pw = await htpasswd( 'test123' );
expect( pw ).not.toBe( undefined );
});

View File

@@ -0,0 +1,7 @@
const name = require('../lib/name.js');
test( 'Create new random name', ()=>{
const n = name.generate();
expect( n ).not.toBe( undefined )
});

View File

@@ -1,26 +0,0 @@
{
"derivation_path": "0/n",
"installer": "docker",
"features": [
"lightning",
"otsclient",
"electrum"
],
"net": "testnet",
"xpub": "upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb",
"bitcoin_mode": "internal",
"bitcoin_rpcuser": "user",
"bitcoin_rpcpassword": "password",
"bitcoin_prune": false,
"bitcoin_uacomment": "",
"lightning_implementation": "c-lightning",
"lightning_nodename": "SatoshiPortal",
"lightning_nodecolor": "ff00ff",
"electrum_implementation": "eps",
"installer_mode": "docker",
"proxy_datapath": "/tmp/p",
"bitcoin_datapath": "/tmp/b",
"lightning_datapath": "/tmp/l",
"bitcoin_expose": false,
"devmode": true
}

241
dist/setup.sh vendored
View File

@@ -110,7 +110,7 @@ sudo_if_required() {
}
modify_permissions() {
local directories=("installer" "gatekeeper" "lightning" "bitcoin" "docker-compose.yaml" "$BITCOIN_DATAPATH" "$LIGHTNING_DATAPATH" "$PROXY_DATAPATH" "$GATEKEEPER_DATAPATH" "$OTSCLIENT_DATAPATH")
local directories=("installer" "gatekeeper" "lightning" "bitcoin" "docker-compose.yaml" "traefik" "$BITCOIN_DATAPATH" "$LIGHTNING_DATAPATH" "$PROXY_DATAPATH" "$GATEKEEPER_DATAPATH" "$OTSCLIENT_DATAPATH" "$TRAEFIK_DATAPATH")
for d in "${directories[@]}"
do
if [[ -e $d ]]; then
@@ -122,7 +122,7 @@ modify_permissions() {
}
modify_owner() {
local directories=("$BITCOIN_DATAPATH" "$LIGHTNING_DATAPATH" "$PROXY_DATAPATH" "$GATEKEEPER_DATAPATH" "$OTSCLIENT_DATAPATH")
local directories=("$BITCOIN_DATAPATH" "$LIGHTNING_DATAPATH" "$PROXY_DATAPATH" "$GATEKEEPER_DATAPATH" "$OTSCLIENT_DATAPATH" "$TRAEFIK_DATAPATH")
local user=$(id -u $RUN_AS_USER):$(id -g $RUN_AS_USER)
for d in "${directories[@]}"
do
@@ -139,7 +139,7 @@ configure() {
local recreate=""
if [[ $1 == 1 ]]; then
recreate="recreate"
recreate=" recreate"
fi
@@ -151,8 +151,6 @@ configure() {
if [[ -t 1 ]]; then
interactive=' -it'
else
gen_options=' --force 2'
fi
if [[ $CFG_PASSWORD ]]; then
@@ -183,21 +181,24 @@ configure() {
# configure features of cyphernode
docker run -v $current_path:/data \
-e DEFAULT_USER=$USER \
-e DEFAULT_DATADIR_BASE=$HOME \
-e SETUP_DIR=$SETUP_DIR \
-e DEFAULT_CERT_HOSTNAME=$(hostname) \
-e VERSION_OVERRIDE=$VERSION_OVERRIDE \
-e GATEKEEPER_VERSION=$GATEKEEPER_VERSION \
-e PROXY_VERSION=$PROXY_VERSION \
-e NOTIFIER_VERSION=$NOTIFIER_VERSION \
-e PROXYCRON_VERSION=$PROXYCRON_VERSION \
-e OTSCLIENT_VERSION=$OTSCLIENT_VERSION \
-e PYCOIN_VERSION=$PYCOIN_VERSION \
-e BITCOIN_VERSION=$BITCOIN_VERSION \
-e LIGHTNING_VERSION=$LIGHTNING_VERSION \
-e SETUP_VERSION=$SETUP_VERSION \
--log-driver=none$pw_env \
--network none \
--rm$interactive cyphernode/cyphernodeconf:$CONF_VERSION $user yo --no-insight cyphernode$gen_options $recreate
if [[ -f $current_path/exitStatus.sh ]]; then
. $current_path/exitStatus.sh
rm $current_path/exitStatus.sh
--rm$interactive cyphernode/cyphernodeconf:$CONF_VERSION $user node index.js$recreate
if [[ -f $cyphernodeconf_filepath/exitStatus.sh ]]; then
. $cyphernodeconf_filepath/exitStatus.sh
rm $cyphernodeconf_filepath/exitStatus.sh
fi
if [[ ! $EXIT_STATUS == 0 ]]; then
@@ -348,7 +349,6 @@ compare_bitcoinconf() {
}
install_docker() {
local archpath=$(uname -m)
# compat mode for SatoshiPortal repo
@@ -363,36 +363,48 @@ install_docker() {
next
fi
if [ -d $GATEKEEPER_DATAPATH ]; then
if [[ ! -f $GATEKEEPER_DATAPATH/installation.json ]]; then
# prevent mounting installation.json as a directory
sudo_if_required touch $GATEKEEPER_DATAPATH/installation.json
fi
if [[ ! -d $GATEKEEPER_DATAPATH/certs ]]; then
sudo_if_required mkdir -p $GATEKEEPER_DATAPATH/certs > /dev/null 2>&1
fi
if [[ ! -d $GATEKEEPER_DATAPATH/private ]]; then
sudo_if_required mkdir -p $GATEKEEPER_DATAPATH/private > /dev/null 2>&1
fi
copy_file $current_path/gatekeeper/api.properties $GATEKEEPER_DATAPATH/api.properties 1 $SUDO_REQUIRED
copy_file $current_path/gatekeeper/keys.properties $GATEKEEPER_DATAPATH/keys.properties 1 $SUDO_REQUIRED
copy_file $current_path/config.7z $GATEKEEPER_DATAPATH/config.7z 1 $SUDO_REQUIRED
copy_file $current_path/client.7z $GATEKEEPER_DATAPATH/client.7z 1 $SUDO_REQUIRED
copy_file $current_path/gatekeeper/cert.pem $GATEKEEPER_DATAPATH/certs/cert.pem 1 $SUDO_REQUIRED
copy_file $current_path/gatekeeper/key.pem $GATEKEEPER_DATAPATH/private/key.pem 1 $SUDO_REQUIRED
copy_file $current_path/gatekeeper/htpasswd $GATEKEEPER_DATAPATH/htpasswd 1 $SUDO_REQUIRED
if [[ ! -f $GATEKEEPER_DATAPATH/installation.json ]]; then
# prevent mounting installation.json as a directory
sudo_if_required touch $GATEKEEPER_DATAPATH/installation.json
fi
if [[ ! -d $GATEKEEPER_DATAPATH/certs ]]; then
sudo_if_required mkdir -p $GATEKEEPER_DATAPATH/certs > /dev/null 2>&1
fi
if [[ ! -d $GATEKEEPER_DATAPATH/private ]]; then
sudo_if_required mkdir -p $GATEKEEPER_DATAPATH/private > /dev/null 2>&1
fi
copy_file $cyphernodeconf_filepath/gatekeeper/default.conf $GATEKEEPER_DATAPATH/default.conf 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/gatekeeper/api.properties $GATEKEEPER_DATAPATH/api.properties 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/gatekeeper/keys.properties $GATEKEEPER_DATAPATH/keys.properties 1 $SUDO_REQUIRED
copy_file $current_path/config.7z $GATEKEEPER_DATAPATH/config.7z 1 $SUDO_REQUIRED
copy_file $current_path/client.7z $GATEKEEPER_DATAPATH/client.7z 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/gatekeeper/cert.pem $GATEKEEPER_DATAPATH/certs/cert.pem 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/gatekeeper/key.pem $GATEKEEPER_DATAPATH/private/key.pem 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/traefik/htpasswd $GATEKEEPER_DATAPATH/htpasswd 1 $SUDO_REQUIRED
if [ ! -d $TRAEFIK_DATAPATH ]; then
step " create $TRAEFIK_DATAPATH"
sudo_if_required mkdir -p $TRAEFIK_DATAPATH
next
fi
copy_file $cyphernodeconf_filepath/traefik/acme.json $TRAEFIK_DATAPATH/acme.json 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/traefik/traefik.toml $TRAEFIK_DATAPATH/traefik.toml 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/traefik/htpasswd $TRAEFIK_DATAPATH/htpasswd 1 $SUDO_REQUIRED
if [ ! -d $PROXY_DATAPATH ]; then
step " create $PROXY_DATAPATH"
sudo_if_required mkdir -p $PROXY_DATAPATH
next
fi
copy_file $current_path/installer/config.sh $PROXY_DATAPATH/config.sh 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/installer/config.sh $PROXY_DATAPATH/config.sh 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/cyphernode/info.json $PROXY_DATAPATH/info.json 1 $SUDO_REQUIRED
if [[ $BITCOIN_INTERNAL == true ]]; then
if [ ! -d $BITCOIN_DATAPATH ]; then
@@ -402,18 +414,22 @@ install_docker() {
fi
if [ -d $BITCOIN_DATAPATH ]; then
local cmpStatus=$(compare_bitcoinconf $current_path/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf)
local cmpStatus=$(compare_bitcoinconf $cyphernodeconf_filepath/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf)
if [[ $cmpStatus == 'dataloss' ]]; then
if [[ $ALWAYSYES == 1 ]]; then
copy_file $current_path/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/bitcoin/bitcoin-client.conf $BITCOIN_DATAPATH/bitcoin-client.conf 1 $SUDO_REQUIRED
else
while true; do
echo " Really copy bitcoin.conf with pruning option?"
read -p " This will discard some blockchain data. (yn) " yn
case $yn in
[Yy]* ) copy_file $current_path/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf 1 $SUDO_REQUIRED; break;;
[Nn]* ) copy_file $current_path/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf.cyphernode 0 $SUDO_REQUIRED
[Yy]* ) copy_file $cyphernodeconf_filepath/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/bitcoin/bitcoin-client.conf $BITCOIN_DATAPATH/bitcoin-client.conf 1 $SUDO_REQUIRED
break;;
[Nn]* ) copy_file $cyphernodeconf_filepath/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf.cyphernode 0 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/bitcoin/bitcoin-client.conf $BITCOIN_DATAPATH/bitcoin-client.conf.cyphernode 0 $SUDO_REQUIRED
echo " Your cyphernode installation is most likely broken."
echo " Please check bitcoin.conf.cyphernode on how to repair it manually.";
break;;
@@ -422,7 +438,8 @@ install_docker() {
done
fi
elif [[ $cmpStatus == 'incompatible' ]]; then
copy_file $current_path/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf.cyphernode 0 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf.cyphernode 0 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/bitcoin/bitcoin-client.conf $BITCOIN_DATAPATH/bitcoin-client.conf.cyphernode 0 $SUDO_REQUIRED
echo " Blockchain data is not compatible, due to misconfigured nets."
echo " Your cyphernode installation is most likely broken."
echo " Please check bitcoin.conf.cyphernode on how to repair it manually."
@@ -430,7 +447,8 @@ install_docker() {
if [[ $cmpStatus == 'reindex' ]]; then
echo " Warning Reindexing will take some time."
fi
copy_file $current_path/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/bitcoin/bitcoin.conf $BITCOIN_DATAPATH/bitcoin.conf 1 $SUDO_REQUIRED
copy_file $cyphernodeconf_filepath/bitcoin/bitcoin-client.conf $BITCOIN_DATAPATH/bitcoin-client.conf 1 $SUDO_REQUIRED
fi
fi
fi
@@ -441,15 +459,15 @@ install_docker() {
if [[ $archpath == "rpi" ]]; then
dockerfile="Dockerfile-alpine"
fi
if [ ! -d $LIGHTNING_DATAPATH ]; then
step " create $LIGHTNING_DATAPATH"
sudo_if_required mkdir -p $LIGHTNING_DATAPATH
next
fi
if [ -d $LIGHTNING_DATAPATH ]; then
copy_file $current_path/lightning/c-lightning/config $LIGHTNING_DATAPATH/config 1 $SUDO_REQUIRED
copy_file $current_path/lightning/c-lightning/bitcoin.conf $LIGHTNING_DATAPATH/bitcoin.conf 1 $SUDO_REQUIRED
fi
copy_file $cyphernodeconf_filepath/lightning/c-lightning/config $LIGHTNING_DATAPATH/config 1 $SUDO_REQUIRED
fi
fi
@@ -496,10 +514,37 @@ install_docker() {
fi
fi
copy_file $current_path/installer/docker/docker-compose.yaml $current_path/docker-compose.yaml
copy_file $current_path/installer/testfeatures.sh $current_path/testfeatures.sh 0
copy_file $current_path/installer/start.sh $current_path/start.sh 0
copy_file $current_path/installer/stop.sh $current_path/stop.sh 0
local appsnet_entry=$(docker network ls | grep cyphernodeappsnet);
if [[ $appsnet_entry =~ 'cyphernodeappsnet' ]]; then
if [[ $appsnet_entry =~ 'local' && $DOCKER_MODE == 'swarm' ]]; then
step " recreate cyphernode apps network"
try docker network rm cyphernodeappsnet > /dev/null 2>&1
try docker network create -d overlay --attachable --opt encrypted cyphernodeappsnet > /dev/null 2>&1
next
elif [[ $appsnet_entry =~ 'swarm' && $DOCKER_MODE == 'compose' ]]; then
step " recreate cyphernode apps network"
try docker network rm cyphernodeappsnet > /dev/null 2>&1
try docker network create cyphernodeappsnet > /dev/null 2>&1
next
fi
else
if [[ $DOCKER_MODE == 'swarm' ]]; then
step " create cyphernode apps network"
try docker network create -d overlay --attachable --opt encrypted cyphernodeappsnet > /dev/null 2>&1
next
elif [[ $DOCKER_MODE == 'compose' ]]; then
step " create cyphernode apps network"
try docker network create cyphernodeappsnet > /dev/null 2>&1
next
fi
fi
copy_file $cyphernodeconf_filepath/installer/docker/docker-compose.yaml $current_path/docker-compose.yaml
copy_file $cyphernodeconf_filepath/installer/testfeatures.sh $current_path/testfeatures.sh 0
copy_file $cyphernodeconf_filepath/installer/start.sh $current_path/start.sh 0
copy_file $cyphernodeconf_filepath/installer/stop.sh $current_path/stop.sh 0
copy_file $cyphernodeconf_filepath/installer/testdeployment.sh $current_path/testdeployment.sh 0
if [[ ! -x $current_path/start.sh ]]; then
step " make start.sh executable"
@@ -518,14 +563,24 @@ install_docker() {
try chmod +x $current_path/testfeatures.sh
next
fi
if [[ ! -x $current_path/testdeployment.sh ]]; then
step " make testdeployment.sh executable"
try chmod +x $current_path/testdeployment.sh
next
fi
}
check_directory_owner() {
# if one directory does not have access rights for $RUN_AS_USER, we echo 1, else we echo 0
local directories=("$BITCOIN_DATAPATH" "$LIGHTNING_DATAPATH" "$PROXY_DATAPATH" "$GATEKEEPER_DATAPATH")
local directories=("$BITCOIN_DATAPATH" "$LIGHTNING_DATAPATH" "$PROXY_DATAPATH" "$GATEKEEPER_DATAPATH" "$TRAEFIK_DATAPATH")
local status=0
for d in "${directories[@]}"
do
if [[ ''$d == '' ]]; then
continue
fi
d=$(realpath $d)
if [[ -e $d ]]; then
# is it mine and does it have rw ?
# don't care about group rights
@@ -551,19 +606,38 @@ check_bitcoind() {
echo 0
}
sanity_checks() {
realpath() {
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
echo " check requirements."
check_docker() {
if ! [ -x "$(command -v docker)" ]; then
echo " docker is not installed on your system. Please check https://www.docker.com/get-started."
exit
fi
}
if [[ $DOCKER_MODE == 'compose' && ! -x "$(command -v docker-compose)" ]]; then
check_docker_compose() {
if ! [ -x "$(command -v docker-compose)" ]; then
echo " docker-compose is not installed on your system. Please check https://docs.docker.com/compose/install/."
exit
fi
}
sanity_checks_pre_config() {
echo " check requirements for configuration step."
check_docker
}
sanity_checks_pre_install() {
echo " check requirements for installation step."
check_docker
if [[ $DOCKER_MODE == 'compose' ]]; then
check_docker_compose
fi
local OS=$(uname -s)
@@ -603,7 +677,7 @@ sanity_checks() {
if [[ $sudo_reason == 'directories' ]]; then
echo " or check your data volumes if they have the right owner."
echo " The owner of the following folders should be '$RUN_AS_USER':"
local directories=("$BITCOIN_DATAPATH" "$LIGHTNING_DATAPATH" "$PROXY_DATAPATH" "$GATEKEEPER_DATAPATH")
local directories=("$BITCOIN_DATAPATH" "$LIGHTNING_DATAPATH" "$PROXY_DATAPATH" "$GATEKEEPER_DATAPATH" "$TRAEFIK_DATAPATH")
local status=0
for d in "${directories[@]}"
do
@@ -622,6 +696,14 @@ sanity_checks() {
fi
}
install_apps() {
if [ ! -d "$current_path/apps" ]; then
local apps_repo="https://github.com/SatoshiPortal/cypherapps.git"
echo " clone $apps_repo into apps"
docker run --rm -v "$current_path":/git --entrypoint git cyphernode/cyphernodeconf:$CONF_VERSION clone --single-branch -b ${CYPHERAPPS_VERSION} "$apps_repo" /git/apps > /dev/null 2>&1
fi
}
install() {
if [[ ''$INSTALLER_MODE == 'none' ]]; then
echo "Skipping installation phase"
@@ -638,16 +720,20 @@ ALWAYSYES=0
SUDO_REQUIRED=0
AUTOSTART=0
# CYPHERNODE VERSION "v0.1.0-rc.2"
VERSION_OVERRIDE="true"
CONF_VERSION="v0.1-rc.2"
GATEKEEPER_VERSION="v0.1-rc.2"
PROXY_VERSION="v0.1-rc.2"
PROXYCRON_VERSION="v0.1-rc.2"
OTSCLIENT_VERSION="v0.1-rc.2"
PYCOIN_VERSION="v0.1-rc.2"
BITCOIN_VERSION="v0.17.0"
LIGHTNING_VERSION="v0.6.2"
# CYPHERNODE VERSION "v0.2.4"
SETUP_VERSION="v0.2.4"
CONF_VERSION="v0.2.4"
GATEKEEPER_VERSION="v0.2.4"
PROXY_VERSION="v0.2.4"
NOTIFIER_VERSION="v0.2.4"
PROXYCRON_VERSION="v0.2.4"
OTSCLIENT_VERSION="v0.2.4"
PYCOIN_VERSION="v0.2.4"
CYPHERAPPS_VERSION="v0.2.2"
BITCOIN_VERSION="v0.18.0"
LIGHTNING_VERSION="v0.7.1"
SETUP_DIR=$(dirname $(realpath $0))
# trap ctrl-c and call ctrl_c()
trap ctrl_c INT
@@ -658,6 +744,7 @@ function ctrl_c() {
}
export current_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
export cyphernodeconf_filepath="$current_path/.cyphernodeconf"
while getopts ":cirhys" opt; do
case $opt in
@@ -690,17 +777,34 @@ while getopts ":cirhys" opt; do
esac
done
nbbuiltimgs=$(docker images --filter=reference='cyphernode/*:*-local' | wc -l)
if [[ $nbbuiltimgs -gt 1 ]]; then
read -p "Locally built Cyphernode images found! Do you want to use them? [yn] " -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]; then
CONF_VERSION="$CONF_VERSION-local"
GATEKEEPER_VERSION="$GATEKEEPER_VERSION-local"
PROXY_VERSION="$PROXY_VERSION-local"
NOTIFIER_VERSION="$NOTIFIER_VERSION-local"
PROXYCRON_VERSION="$PROXYCRON_VERSION-local"
OTSCLIENT_VERSION="$OTSCLIENT_VERSION-local"
PYCOIN_VERSION="$PYCOIN_VERSION-local"
fi
fi
if [[ $CONFIGURE == 0 && $INSTALL == 0 && $RECREATE == 0 ]]; then
CONFIGURE=1
INSTALL=1
fi
if [[ $CONFIGURE == 1 ]]; then
sanity_checks_pre_config
configure $RECREATE
fi
if [[ -f $current_path/installer/config.sh ]]; then
. $current_path/installer/config.sh
if [[ -f "$cyphernodeconf_filepath/installer/config.sh" ]]; then
. "$cyphernodeconf_filepath/installer/config.sh"
fi
if [[ $CLEANUP == 'true' && $(docker image ls | grep cyphernodeconf) =~ cyphernodeconf ]]; then
@@ -710,15 +814,18 @@ if [[ $CLEANUP == 'true' && $(docker image ls | grep cyphernodeconf) =~ cypherno
fi
if [[ $INSTALL == 1 ]]; then
sanity_checks
sanity_checks_pre_install
create_user
install
modify_owner
modify_permissions
install_apps
if [[ ! $AUTOSTART == 1 ]]; then
cowsay
fi
fi
if [[ $AUTOSTART == 1 ]]; then
exec $current_path/start.sh
else
cowsay
fi

View File

@@ -71,6 +71,169 @@ Proxy response:
}
```
### Watch a Bitcoin xpub/ypub/zpub/tpub/upub/vpub extended public key (called by application)
Used to watch the transactions related to an xpub. It will first derive 100 addresses using the provided xpub, derivation path and index information. It will add those addresses to the watching DB table and add those addresses to the Watching-by-xpub wallet. The watching process will take care of calling the provided callbacks when a transaction occurs. When a transaction is seen, Cyphernode will derive and start watching new addresses related to the xpub, keeping a 100 address gap between the last used address in a transaction and the last watched address of that xpub. The label can be used later, instead of the whole xpub, with unwatchxpub* and and getactivewatchesby*.
```http
POST http://cyphernode:8888/watchxpub
with body...
{"label":"4421","pub32":"upub57Wa4MvRPNyAhxr578mQUdPr6MHwpg3Su875hj8K75AeUVZLXtFeiP52BrhNqDg93gjALU1MMh5UPRiiQPrwiTiuBBBRHzeyBMgrbwkmmkq","path":"0/1/n","nstart":109,"unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf","confirmedCallbackURL":"192.168.111.233:1111/callback1conf"}
```
Proxy response:
```json
{
"id":"5",
"event":"watchxpub",
"pub32":"upub57Wa4MvRPNyAhxr578mQUdPr6MHwpg3Su875hj8K75AeUVZLXtFeiP52BrhNqDg93gjALU1MMh5UPRiiQPrwiTiuBBBRHzeyBMgrbwkmmkq",
"label":"2219",
"path":"0/1/n",
"nstart":"109",
"unconfirmedCallbackURL":"192.168.111.233:1111/callback0conf",
"confirmedCallbackURL":"192.168.111.233:1111/callback1conf"
}
```
### Un-watch a previously watched Bitcoin xpub by providing the xpub (called by application)
Updates the watched address rows in DB so that callbacks won't be called on tx confirmations for the provided xpub and related addresses.
```http
GET http://cyphernode:8888/unwatchxpubbyxpub/upub57Wa4MvRPNyAhxr578mQUdPr6MHwpg3Su875hj8K75AeUVZLXtFeiP52BrhNqDg93gjALU1MMh5UPRiiQPrwiTiuBBBRHzeyBMgrbwkmmkq
```
Proxy response:
```json
{
"event":"unwatchxpubbyxpub",
"pub32":"upub57Wa4MvRPNyAhxr578mQUdPr6MHwpg3Su875hj8K75AeUVZLXtFeiP52BrhNqDg93gjALU1MMh5UPRiiQPrwiTiuBBBRHzeyBMgrbwkmmkq"
}
```
### Un-watch a previously watched Bitcoin xpub by providing the label (called by application)
Updates the watched address rows in DB so that callbacks won't be called on tx confirmations for the provided xpub and related addresses.
```http
GET http://cyphernode:8888/unwatchxpubbylabel/4421
```
Proxy response:
```json
{
"event":"unwatchxpubbylabel",
"label":"4421"
}
```
### Watch a TXID (called by application)
Used to watch a transaction. Will call the 1-conf callback url after the transaction has been mined. Will call the x-conf callback url after the transaction has x confirmations.
```http
POST http://cyphernode:8888/watchtxid
with body...
{"txid":"b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387","confirmedCallbackURL":"192.168.111.233:1111/callback1conf","xconfCallbackURL":"192.168.111.233:1111/callbackXconf","nbxconf":6}
```
Proxy response:
```json
{
"id":"5",
"event":"watchtxid",
"inserted":"1",
"txid":"b081ca7724386f549cf0c16f71db6affeb52ff7a0d9b606fb2e5c43faffd3387",
"confirmedCallbackURL":"192.168.111.233:1111/callback1conf",
"xconfCallbackURL":"192.168.111.233:1111/callbackXconf",
"nbxconf":6
}
```
### Get a list of Bitcoin xpub being watched (called by application)
Returns the list of currently watched xpub and callback information.
```http
GET http://cyphernode:8888/getactivexpubwatches
```
Proxy response:
```json
{
"watches": [
{
"id":"291",
"pub32":"upub57Wa4MvRPNyAhxr578mQUdPr6MHwpg3Su875hj8K75AeUVZLXtFeiP52BrhNqDg93gjALU1MMh5UPRiiQPrwiTiuBBBRHzeyBMgrbwkmmkq",
"label":"2217",
"derivation_path":"1/3/n",
"last_imported_n":"121",
"unconfirmedCallbackURL":"192.168.133.233:1111/callback0conf",
"confirmedCallbackURL":"192.168.133.233:1111/callback1conf",
"watching_since":"2018-09-06 21:14:03"}
]
}
```
### Get a list of Bitcoin addresses being watched by provided xpub (called by application)
Returns the list of currently watched addresses related to the provided xpub and callback information.
```http
GET http://cyphernode:8888/getactivewatchesbyxpub/tpubD6NzVbkrYhZ4YR3QK2tyfMMvBghAvqtNaNK1LTyDWcRHLcMUm3ZN2cGm5BS3MhCRCeCkXQkTXXjiJgqxpqXK7PeUSp86DTTgkLpcjMtpKWk
```
Proxy response:
```json
{
"watches": [
{
"id":"291",
"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp",
"imported":"1",
"unconfirmedCallbackURL":"192.168.133.233:1111/callback0conf",
"confirmedCallbackURL":"192.168.133.233:1111/callback1conf",
"watching_since":"2018-09-06 21:14:03",
"derivation_path":"1/0/n",
"pub32_index":"44"}
]
}
```
### Get a list of Bitcoin addresses being watched by provided xpub label (called by application)
Returns the list of currently watched addresses related to the provided xpub label and callback information.
```http
GET http://cyphernode:8888/getactivewatchesbylabel/2219
```
Proxy response:
```json
{
"watches": [
{
"id":"291",
"address":"2N8DcqzfkYi8CkYzvNNS5amoq3SbAcQNXKp",
"imported":"1",
"unconfirmedCallbackURL":"192.168.133.233:1111/callback0conf",
"confirmedCallbackURL":"192.168.133.233:1111/callback1conf",
"watching_since":"2018-09-06 21:14:03",
"derivation_path":"1/0/n",
"pub32_index":"44"}
]
}
```
### Confirm a Transaction on Watched Address (called by Bitcoin node on transaction confirmations)
Confirms a transaction on an imported address. The Watching Bitcoin node will notify Cyphernode (thanks to walletnotify in bitcoin.conf) by calling this endpoint with txid when a tx is new or updated on an address. If address is still being watched (flag in DB), the corresponding callbacks will be called.
@@ -129,6 +292,108 @@ When cyphernode receives a transaction confirmation (/conf endpoint) on a watche
}
```
### Get mempool information
Returns the mempool information of the Bitcoin node.
```http
GET http://cyphernode:8888/getmempoolinfo
```
Proxy response:
```json
{
"size": 25,
"bytes": 5462,
"usage": 34736,
"maxmempool": 64000000,
"mempoolminfee": 1e-05,
"minrelaytxfee": 1e-05
}
```
### Get the blockchain information (called by application)
Returns the blockchain information of the Bitcoin node. Used for example by the welcome app to get syncing progression.
```http
GET http://cyphernode:8888/getblockchaininfo
```
Proxy response:
```json
{
"chain": "test",
"blocks": 1486864,
"headers": 1486864,
"bestblockhash": "000000000000002fb99d683e64bbfc2b7ad16f9a425cf7be77b481fb1afa363b",
"difficulty": 13971064.71015782,
"mediantime": 1554149114,
"verificationprogress": 0.9999994536561675,
"initialblockdownload": false,
"chainwork": "000000000000000000000000000000000000000000000103ceb57a5896f347ce",
"size_on_disk": 23647567017,
"pruned": false,
"softforks": [
{
"id": "bip34",
"version": 2,
"reject": {
"status": true
}
},
{
"id": "bip66",
"version": 3,
"reject": {
"status": true
}
},
{
"id": "bip65",
"version": 4,
"reject": {
"status": true
}
}
],
"bip9_softforks": {
"csv": {
"status": "active",
"startTime": 1456790400,
"timeout": 1493596800,
"since": 770112
},
"segwit": {
"status": "active",
"startTime": 1462060800,
"timeout": 1493596800,
"since": 834624
}
},
"warnings": "Warning: unknown new rules activated (versionbit 28)"
}
```
### Get the Block Hash from Height (called by application)
Returns the best block hash matching height provided.
```http
GET http://cyphernode:8888/getblockhash/593104
```
Proxy response:
```json
{
"result":"00000000000000000005dc459f0575b17413dbe7685e3e0fd382ed521f1be68b",
"error":null,
"id":null
}
```
### Get the Best Block Hash (called by application)
Returns the best block hash of the watching Bitcoin node.
@@ -325,12 +590,17 @@ Proxy response:
}
```
### Get a new Bitcoin address from spending wallet (called by application)
Calls getnewaddress RPC on the spending wallet. Used to refill the spending wallet from cold wallet (ie Trezor).
Calls getnewaddress RPC on the spending wallet. Used to refill the spending wallet from cold wallet (ie Trezor). Will derive the default address type (set in your bitcoin.conf file, p2sh-segwit if not specified) or you can supply the address type like the following examples.
```http
GET http://cyphernode:8888/getnewaddress
GET http://cyphernode:8888/getnewaddress/bech32
GET http://cyphernode:8888/getnewaddress/legacy
GET http://cyphernode:8888/getnewaddress/p2sh-segwit
```
Proxy response:
@@ -341,6 +611,12 @@ Proxy response:
}
```
```json
{
"address":"tb1ql7yvh3lmajxmaljsnsu3w8lhwczu963tvjfzpj"
}
```
### Spend coins from spending wallet (called by application)
Calls sendtoaddress RPC on the spending wallet with supplied info.
@@ -360,6 +636,29 @@ Proxy response:
}
```
### Bump transaction's fees (called by application)
Calls bumpfee RPC on the spending wallet with supplied info.
```http
POST http://cyphernode:8888/bumpfee
with body...
{"txid":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648","confTarget":4}
or...
{"txid":"af867c86000da76df7ddb1054b273ca9e034e8c89d049b5b2795f9f590f67648"}
```
Proxy response:
```json
{
"txid": "7c048f43af90315e201ff4dada8796605a2505c7ab54054ba0e9e05cadd9079b",
"origfee": 0.00041221,
"fee": 0.00068112,
"errors": [ "Blabla don't do that" ]
}
```
### Add an output to the next batched transaction (called by application)
Inserts output information in the DB. Used when batchspend is called later.
@@ -555,3 +854,231 @@ Proxy response:
"address": "tb1q9n8jfwe9qvlgczfxa5n4pe7haarqflzerqfhk9"
}
```
### Get your Lightning Network Node connection string, for others to connect to you
Returns a string containing your LN node connection information.
```http
GET http://cyphernode:8888/ln_getconnectionstring
```
Proxy response:
```json
{
"connectstring": "02ffb6242a744143d427cf0962fce182d426609b80034a297ea5879c8c64d326ab@24.25.26.27:9735"
}
```
### Connect to a LN node and fund a channel with it
First, it will connect your LN node to the supplied LN node. Then, it will fund a channel of the provided amount between you two. Cyphernode will call the supplied callback URL when the channel is ready to be used.
```http
POST http://cyphernode:8888/ln_connectfund
with body...
{"peer":"nodeId@ip:port","msatoshi":"100000","callbackUrl":"https://callbackUrl/?channelReady=f3y2c3cvm4uzg2gq"}
```
Proxy response:
```json
{
"result": "success",
"txid": "85b8e69733202e126620e7745be9e23a6b544b758145d86848f3e513e6e1ca42",
"channel_id": "a459352219deb8e1b6bdc4a3515888569adad8a3023f8b57edeb0bc4d1f77b74"
}
```
```json
{
"result": "failed",
"message": "Failed at watching txid"
}
```
### Get a previously created Lightning Network invoice by its label
Returns the invoice corresponding to the supplied label.
```http
GET http://cyphernode:8888/ln_getinvoice/label
GET http://cyphernode:8888/ln_getinvoice/koNCcrSvhX3dmyFhW
```
Proxy response:
```json
{
"invoices": [
{
"label": "koNCcrSvhX3dmyFhW",
"bolt11": "lntb10n1pw92fk9pp56p6g7nnuhcj63j6wpyquy67wc7xfanhc20d49ta2dzrge2mj3s5qdq9vscnzcqp2rzjqvdcvlvavcc6zdfvcrehhn4ff024s75dfaqyzmzvuxsj2yd3u684v93ylqqqq0sqqqqqqqqpqqqqqzsqqcu6jamf7du64nxtj99x5t6hvy4hlfv8fc8m5j39g8kyzpk5r89s28f93x5jsfnzl8mhtkhqvx2qxehns4ltw7w5h8h7ppdcw8t0uz0wcptztsqg",
"payment_hash": "d0748f4e7cbe25a8cb4e0901c26bcec78c9ecef853db52afaa68868cab728c28",
"msatoshi": 1000,
"status": "paid",
"pay_index": 10,
"msatoshi_received": 1002,
"paid_at": 1549084373,
"description": "d11",
"expires_at": 1549087957
}
]
}
```
### Delete a previously created Lightning Network invoice by its label
Deletes the invoice corresponding to the supplied label if status is unpaid, so that no payment comes in. Returns the invoice corresponding to the supplied label.
```http
GET http://cyphernode:8888/ln_delinvoice/label
GET http://cyphernode:8888/ln_delinvoice/koNCcrSvhX3dmyFhW
```
Proxy response:
```json
{
"label": "koNCcrSvhX3dmyFhW",
"bolt11": "lntb10n1pw92fk9pp56p6g7nnuhcj63j6wpyquy67wc7xfanhc20d49ta2dzrge2mj3s5qdq9vscnzcqp2rzjqvdcvlvavcc6zdfvcrehhn4ff024s75dfaqyzmzvuxsj2yd3u684v93ylqqqq0sqqqqqqqqpqqqqqzsqqcu6jamf7du64nxtj99x5t6hvy4hlfv8fc8m5j39g8kyzpk5r89s28f93x5jsfnzl8mhtkhqvx2qxehns4ltw7w5h8h7ppdcw8t0uz0wcptztsqg",
"payment_hash": "d0748f4e7cbe25a8cb4e0901c26bcec78c9ecef853db52afaa68868cab728c28",
"msatoshi": 1000,
"status": "unpaid",
"description": "d11",
"expires_at": 1549087957
}
```
### Decodes the BOLT11 string of a Lightning Network invoice
Returns the detailed information of a BOLT11 string of a Lightning Network invoice.
```http
GET http://cyphernode:8888/ln_decodebolt11/bolt11
GET http://cyphernode:8888/ln_decodebolt11/lntb1pdca82tpp5gv8mn5jqlj6xztpnt4r472zcyrwf3y2c3cvm4uzg2gqcnj90f83qdp2gf5hgcm0d9hzqnm4w3kx2apqdaexgetjyq3nwvpcxgcqp2g3d86wwdfvyxcz7kce7d3n26d2rw3wf5tzpm2m5fl2z3mm8msa3xk8nv2y32gmzlhwjved980mcmkgq83u9wafq9n4w28amnmwzujgqpmapcr3
```
Proxy response:
```json
{
"currency": "tb",
"created_at": 1536073035,
"expiry": 3600,
"payee": "03bb990f43e6a6eccb223288d32fcb91209b12370c0a8bf5cdf4ad7bc11e33f253",
"description": "Bitcoin Outlet order #7082",
"min_final_cltv_expiry": 10,
"payment_hash": "430fb9d240fcb4612c335d475f285820dc9891588e19baf048520189c8af49e2",
"signature": "30440220445a7d39cd4b086c0bd6c67cd8cd5a6a86e8b9345883b56e89fa851decfb876202206b1e6c5122a46c5fbba4ccb4a77ef1bb20078f0aeea4059d5ca3f773db85c920"
}
```
### Stamp a hash on the Bitcoin blockchain using OTS (called by application)
Will stamp the supplied hash to the Bitcoin blockchain using OTS. Cyphernode will curl the callback when the OTS stamping is complete.
```http
POST http://cyphernode:8888/ots_stamp
with body...
{"hash":"1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7","callbackUrl":"192.168.111.233:1111/otscallback?id=1234567"}
```
Proxy response:
```json
{
"method": "ots_stamp",
"hash": "1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7",
"id": "422",
"result": "success"
}
```
```json
{
"method": "ots_stamp",
"hash": "1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7",
"id": "422",
"result": "error",
"error": "Error message from OTS client."
}
```
### Get the OTS file of the supplied hash
Returns the binary OTS file of the supplied hash. If stamp is complete, will return a complete OTS file, or an incomplete OTS file otherwise.
```http
GET http://cyphernode:8888/ots_getfile/1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7
```
Proxy response:
Binary application/octet-stream content type.
### Verify an OTS file
Will verify the supplied OTS file, or verify the local OTS file named after the supplied hash suffixed with .ots.
```http
POST http://cyphernode:8888/ots_verify
with body...
{"hash":"1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7"}
or
{"hash":"1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7","base64otsfile":"AE9wZW5UaW1lc3RhbXBzAABQcm9vZ...gABYiWDXPXGQEDxNch"}
```
Proxy response:
```json
{
"method": "ots_verify",
"hash": "1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7",
"result": "success",
"message": "Message from OTS client."
}
```
```json
{
"method": "ots_verify",
"hash": "1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7",
"result": "pending",
"message": "Message from OTS client."
}
```
```json
{
"method": "ots_verify",
"hash": "1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7",
"result": "error",
"message": "Error message from OTS client."
}
```
### Get info an OTS file
Will return the base64 string of the detailed information of the supplied OTS file, or of the local OTS file named after the supplied hash suffixed with .ots.
```http
POST http://cyphernode:8888/ots_info
with body...
{"hash":"1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7"}
or
{"base64otsfile":"AE9wZW5UaW1lc3RhbXBzAABQcm9vZ...gABYiWDXPXGQEDxNch"}
or
{"hash":"1ddfb769eb0b8876bc570e25580e6a53afcf973362ee1ee4b54a807da2e5eed7","base64otsfile":"AE9wZW5UaW1lc3RhbXBzAABQcm9vZ...gABYiWDXPXGQEDxNch"}
```
Proxy response:
```json
{
"method": "ots_info",
"result": "success",
"message": "Base64 string of the information text"
}
```

179
doc/API.v1.md Normal file
View File

@@ -0,0 +1,179 @@
# Cyphernode
## API v1 (RESTful)
### Collections
#### watchedAddresses
| Request | Descripton |
|---------|------------|
| `POST /v1/watchedAddresses` | Create new address watch |
| `GET /v1/watchedAddresses` | Get list of watched addresses |
| `GET /v1/watchedAddresses/<address>` | Get details of watched address |
| `DELETE /v1/watchedAddresses/<address>` | Remove watched address |
##### POST /v1/watchedAddresses
Request body
```
{
"address": <string: address>,
"callback": <string: url>
}
```
Response body - 200 - OK
```
{
"id": <int>,
"address": <string: address>,
"callback": <string: url>",
"estimatesmartfee2blocks": <float: bitcoin>,
"estimatesmartfee6blocks": <float: bitcoin>,
"estimatesmartfee36blocks": <float: bitcoin>,
"estimatesmartfee144blocks": <float: bitcoin>
}
```
Response body - 503 - Resource temporarily unavailable
```
{
"reason": <string: reason>
}
```
Response body - 403 - Forbidden
```
{
}
```
##### GET /v1/watchedAddresses
Response body - 200 - OK
```
[
{
"id": <int>,
"address": <string: address>,
"imported": <bool>,
"callback": <string: url>,
"watching_since": <datetime: ISO8601-UTC>
},
...
]
```
Response body - 503 - Resource temporarily unavailable
```
{
"reason": <string: reason>
}
```
Response body - 403 - Forbidden
```
{
}
```
##### GET /v1/watchedAddresses/\<address\>
Response body - 200 - OK
```
{
"id": <int>,
"address": <string: address>,
"imported": <bool>,
"callback": <string: url>,
"watching_since": <datetime: ISO8601-UTC>
}
```
Response body - 503 - Resource temporarily unavailable
```
{
"reason": <string: reason>
}
```
Response body - 403 - Forbidden
```
{
}
```
Response body - 404 - Not found
```
{
}
```
##### DELETE /v1/watchedAddresses/\<address\>
Response body - 200 - OK
```
{
"address": "<address>",
"imported": <bool>,
"callback": <string: url>,
"watching_since": <datetime: ISO8601-UTC>
}
```
Response body - 503 - Resource temporarily unavailable
```
{
"reason": <string: reason>
}
```
Response body - 403 - Forbidden
```
{
}
```
Response body - 404 - Not found
```
{
}
```
##### Asynchronous callbacks
Request body
```
{
"id": <int> ,
"address": <string: address>,
"hash": <string: hash>,
"vout_n": <int>,
"sent_amount": <float: bitcoin>,
"confirmations": <int>,
"received": <datetime: ISO8601-UTC>,
"size": <int: bytes>,
"vsize": <int: bytes>,
"fees": <float: bitcoin>,
"is_replaceable": <bool>,
"blockhash": <string: hash>,
"blocktime": <int>,
"blockheight": <int>
}
```
Response body - 200 - OK
```
{
}
```
Response body - 503 - Resource temporarily unavailable
```
{
"reason": <string: reason>
}
```

BIN
doc/CN-Arch.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

17
doc/CYPHERAPPS.md Normal file
View File

@@ -0,0 +1,17 @@
# Cyphernode Apps
We are providing one default Cyphernode application: The Cyphernode Welcome App. It is a simple Golang application that uses the Cyphernode API to get some information about it: the Bitcoin Core syncing progression, the installed components, a link to download the encrypted config file, a link to download the encrypted API ID/keys file and a link to Spark Wallet, if LN is installed.
We are also providing Spark Wallet as a Cyphernode application. It is a hybrid application, directly using the c-lightning directory instead of only using the Cyphernode API.
## Concept
As you already know it, we want Cyphernode to be modular and decoupled. That's why we created a completely separated repository for the Cyphernode Apps: https://github.com/SatoshiPortal/cypherapps
Cypherapps acts as an indirection layer between Cyphernode and the actual applications. The repo is cloned into the Cyphernode directory during setup, depending on the selected optional features. The corresponding docker images are taken from the Docker hub repositories.
Separating Cypherapps from Cyphernode allows us to add applications without changing Cyphernode.
## Examples
Welcome App: https://github.com/SatoshiPortal/cyphernode_welcome

View File

@@ -1,6 +1,6 @@
# This README file can be used if you want to install manually. This is the old documentation before there was the installer.
# Here are the exact steps I did to install cyphernode on a debian server running on x86 arch, as user debian.
# Here are the exact steps I did to install cyphernode on a Debian GNU/Linux server running on x86 arch, as user debian.
## Update server and install git
@@ -11,10 +11,10 @@ sudo apt-get update ; sudo apt-get upgrade ; sudo apt-get install git
## Docker installation: https://docs.docker.com/install/linux/docker-ce/debian/
```shell
sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common
sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
sudo apt-key fingerprint 0EBFCD88
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian \
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian \
$(lsb_release -cs) \
stable"
sudo apt-get update
@@ -142,9 +142,9 @@ sudo find ~/btcdata -type d -exec chmod 2775 {} \; ; sudo find ~/btcdata -type f
## Test the deployment
```shell
id="001";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1/getbestblockhash
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1/getbalance
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Content-Type: application/json" -d '{"hash":"123","callbackUrl":"http://callback"}' -H "Authorization: Bearer $token" -k https://127.0.0.1/ots_stamp
id="001";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/getbestblockhash
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/getbalance
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Content-Type: application/json" -d '{"hash":"123","callbackUrl":"http://callback"}' -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/ots_stamp
```
If you need the authorization header to copy/paste in another tool:
@@ -157,5 +157,5 @@ id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(ech
echo "GET /getbestblockinfo" | docker run --rm -i --network=cyphernodenet alpine nc proxy:8888 -
echo "GET /getbalance" | docker run --rm -i --network=cyphernodenet alpine nc proxy:8888 -
echo "GET /ln_getinfo" | docker run --rm -i --network=cyphernodenet alpine nc proxy:8888 -
docker exec -it `docker ps -q -f name=cyphernodestack_cyphernode` curl -H "Content-Type: application/json" -d "{\"pub32\":\"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb\",\"path\":\"0/25-30\"}" proxy:8888/derivepubpath
docker exec -it `docker ps -q -f name=cyphernode_proxy` curl -H "Content-Type: application/json" -d "{\"pub32\":\"upub5GtUcgGed1aGH4HKQ3vMYrsmLXwmHhS1AeX33ZvDgZiyvkGhNTvGd2TA5Lr4v239Fzjj4ZY48t6wTtXUy2yRgapf37QHgt6KWEZ6bgsCLpb\",\"path\":\"0/25-30\"}" proxy:8888/derivepubpath
```

View File

@@ -124,9 +124,9 @@ pi@SP-BTC01:~ $ docker network connect cyphernodenet btcnode
## Test deployment from outside of the Swarm
```shell
id="001";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1/getbestblockhash
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1/getbalance
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Content-Type: application/json" -d '{"hash":"123","callbackUrl":"http://callback"}' -H "Authorization: Bearer $token" -k https://127.0.0.1/ots_stamp
id="001";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/getbestblockhash
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/getbalance
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Content-Type: application/json" -d '{"hash":"123","callbackUrl":"http://callback"}' -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/ots_stamp
```
If you need the authorization header to copy/paste in another tool:

View File

@@ -20,8 +20,28 @@ Or you can simply run this magic command to start setup and installation:
curl -fsSL https://raw.githubusercontent.com/SatoshiPortal/cyphernode/master/dist/setup.sh -o setup_cyphernode.sh && chmod +x setup_cyphernode.sh && ./setup_cyphernode.sh
```
Note that you can replace "master" in the URL by "dev" or any existing git branch/tag you actually want to install.
### Build cyphernode yourself
You can build cyphernode images yourself. The images will have the same name than the ones in the docker hub, with the suffix -local.
```shell
git clone https://github.com/SatoshiPortal/cyphernode.git
cd cyphernode
./build.sh
cd dist
./setup.sh
```
`setup.sh` will detect locally built images (with suffix `-local`) and ask you if you want to use them when installing cyphernode.
For full paranoia mode, you can also build yourself all images used by cyphernode but in external repositories by using the `build.sh` script in each of the repo. You can see a list of images cyphernode uses here: https://cloud.docker.com/u/cyphernode/repository/list
## Upgrading
To upgrade to the most recent version, just get and run the most recent version of the setup.sh file as described in the previous section. Migration should be taken care by the script.
Your proxy's database won't be lost. Migration scripts are taking care of automatically migrating the database when starting the proxy.
```
@@ -39,9 +59,9 @@ id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(ech
Directly using curl on command line, put your API ID (id=) and API key (k=) in the following commands:
```shell
id="001";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1/getbestblockhash
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1/getbalance
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Content-Type: application/json" -d '{"hash":"123","callbackUrl":"http://callback"}' -H "Authorization: Bearer $token" -k https://127.0.0.1/ots_stamp
id="001";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/getbestblockhash
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/getbalance
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Content-Type: application/json" -d '{"hash":"123","callbackUrl":"http://callback"}' -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/ots_stamp
```
## Manually test your installation directly on the Proxy:

View File

@@ -25,14 +25,11 @@ We are providing an installer to help you setup Cyphernode.
#### See [Instructions for installation](INSTALL.md) for automatic install instructions
All the Docker images used by Cyphernode have been prebuilt for x86 and ARM (RPi) architectures and are hosted on the Docker hub public registry, Cyphernode repository (https://hub.docker.com/u/cyphernode/).
All the Docker images used by Cyphernode have been prebuilt for x86, ARM (RPi) and aarch64 (pine64/NODL) architectures and are hosted on the Docker hub public registry, Cyphernode repository (https://hub.docker.com/u/cyphernode/).
### Build from sources
However, it is possible for you to build from sources. In that case, please refer to the files INSTALL-MANUALLY.md and INSTALL-MANUAL-STEPS.md.
#### See [Instructions for manual installation](INSTALL-MANUALLY.md) for manual build and install instructions
#### See [Step-by-step detailed instructions](INSTALL-MANUAL-STEPS.md) for real-world copy-paste standard install instructions
However, it is possible for you to build from sources. In that case, please refer to the `build.sh` scripts in each of the repositories used by cyphernode (https://cloud.docker.com/u/cyphernode/repository/list). See [Instructions for installation](INSTALL.md).
# For Your Information
@@ -42,6 +39,8 @@ Current components in Cyphernode:
- Proxy: request handler. Well dispatch authenticated and authorized requests to the right component. Use a SQLite3 database for its tasks.
- Proxy Cron: scheduler. Can call the proxy on regular interval for asynchronous tasks like payment notifications on watches, callbacks when OTS files are ready, etc.
- Pycoin: Bitcoin keys and addresses tool. Used by Cyphernode to derive addresses from an xPub and a derivation path.
- notifier: Handling callbacks used by watchers as well as OTS stamping.
- broker: pub/sub mechanism is taken care by the broker to which all subscribers and publishers should register.
- Bitcoin: Bitcoin Core node. Cyphernode uses a watching wallet for watchers (no funds) and a spending wallet for spending. Mandatory component, but optionally part of Cyphernode installation, as we can use an already running Bitcoin Core node.
- Lightning: optional. C-Lightning node. The LN node will use the Bitcoin node for its tasks.
- OTSclient: optional. Used to stamp hashes on the Bitcoin blockchain.
@@ -61,18 +60,18 @@ If you decide to have a prune Bitcoin Core node, the fee calculation on incoming
## Lightning Network
Currently, the LN functionalities of Cyphernode are very limited. Maybe even hard to use. You can:
Currently, basic LN functionalities is offered by Cyphernode. You can:
- Get information on your LN node: ln_getinfo
- Get a Bitcoin address where to send your funds to be used by your LN node: ln_newaddr
- Create an invoice, so people can send you payment; the burden of creating a channel/route to you is on the payer: ln_create_invoice
- Pay an invoice. You have to have the invoice and your LN node must already be connected to the network: ln_pay
Basic and crucial functionalities that's missing (you have to manually use lightning-cli on your LN node):
- Decode a BOLT11 string.
- Delete a created invoice to make sure cancelled payments are not accepted.
- Get a previously created invoice.
- Connect + fund: connects to a peer and fund a channel, all in one call. A callback can be provided to let you know when the channel is ready to use.
- Get connection string: to let your user know how to connect to your LN node.
- Be notified when a LN payment is received
- Connect your node to the LN network
- Open/close channels
## Manually test your installation through the Gatekeeper
@@ -85,9 +84,9 @@ id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(ech
Directly using curl on command line, put your API ID (id=) and API key (k=) in the following commands:
```shell
id="001";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1/getbestblockhash
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1/getbalance
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Content-Type: application/json" -d '{"hash":"123","callbackUrl":"http://callback"}' -H "Authorization: Bearer $token" -k https://127.0.0.1/ots_stamp
id="001";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="2df1eeea370eacdc5cf7e96c2d82140d1568079a5d4d87006ec8718a98883b36";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/getbestblockhash
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/getbalance
id="003";h64=$(echo -n "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" | base64);p64=$(echo -n "{\"id\":\"$id\",\"exp\":$((`date +"%s"`+10))}" | base64);k="b9b8d527a1a27af2ad1697db3521f883760c342fc386dbc42c4efbb1a4d5e0af";s=$(echo -n "$h64.$p64" | openssl dgst -hmac "$k" -sha256 -r | cut -sd ' ' -f1);token="$h64.$p64.$s";curl -v -H "Content-Type: application/json" -d '{"hash":"123","callbackUrl":"http://callback"}' -H "Authorization: Bearer $token" -k https://127.0.0.1:2009/v0/ots_stamp
```

29
doc/UPGRADE.md Normal file
View File

@@ -0,0 +1,29 @@
# Upgrade notes from 0.1 to 0.2, to upgrade manually
Usually no need to do this since it will be done during setup.sh v0.2.
1. cd currentInstallation, where setup.sh is located
2. ./stop.sh current running cyphernode
3. Execute:
```
docker run --rm -it -v "$PWD:/conf" alpine:3.8
apk add --no-cache --update jq curl p7zip
cd conf
7z e config.7z
```
<enter your password>
```
k=$(dd if=/dev/urandom bs=32 count=1 2> /dev/null | xxd -pc 32) && l="kapi_id=\\\"000\\\";kapi_key=\\\"$k\\\";kapi_groups=\\\"stats\\\";eval ugroups_\${kapi_id}=\${kapi_groups};eval ukey_\${kapi_id}=\${kapi_key}" && cat config.json | sed 's/kapi_groups=\\"/kapi_groups=\\"stats,/g' | jq ".gatekeeper_keys.configEntries = [\"$l\"] + .gatekeeper_keys.configEntries" | jq ".gatekeeper_keys.clientInformation = [\"000=$k\"] + .gatekeeper_keys.clientInformation" | jq ".gatekeeper_apiproperties = \"$(curl -fsSL https://raw.githubusercontent.com/SatoshiPortal/cyphernode/v0.2.0/api_auth_docker/api-sample.properties | paste -s -d '\n')\"" > config.json
7z u config.7z config.json
```
<enter your password>
<CTRL-D>
```
curl -fsSL https://raw.githubusercontent.com/SatoshiPortal/cyphernode/v0.2.0/dist/setup.sh -o setup_cyphernode.sh && chmod +x setup_cyphernode.sh && ./setup_cyphernode.sh
```

View File

@@ -0,0 +1,117 @@
#!/usr/bin/env bash
generators="ada \
ada-server \
android \
apache2 \
apex \
aspnetcore \
bash \
c \
clojure \
cwiki \
cpp-qt5-client \
cpp-qt5-qhttpengine-server \
cpp-pistache-server \
cpp-restbed-server \
cpp-restsdk \
cpp-tizen \
csharp \
csharp-refactor \
csharp-dotnet2 \
csharp-nancyfx \
dart \
dart-jaguar \
eiffel \
elixir \
elm \
erlang-client \
erlang-proper \
erlang-server \
flash \
scala-finch \
go \
go-server \
go-gin-server \
graphql-schema \
graphql-nodejs-express-server \
groovy \
kotlin \
kotlin-server \
kotlin-spring \
haskell-http-client \
haskell \
java \
jaxrs-cxf-client \
java-inflector \
java-msf4j \
java-pkmst \
java-play-framework \
java-undertow-server \
java-vertx \
jaxrs-cxf \
jaxrs-cxf-cdi \
jaxrs-jersey \
jaxrs-resteasy \
jaxrs-resteasy-eap \
jaxrs-spec \
javascript \
javascript-flowtyped \
javascript-closure-angular \
jmeter \
lua \
mysql-schema \
nodejs-server \
objc \
openapi \
openapi-yaml \
perl \
php \
php-laravel \
php-lumen \
php-slim \
php-silex \
php-symfony \
php-ze-ph \
powershell \
python \
python-flask \
python-aiohttp \
r \
ruby \
ruby-on-rails \
ruby-sinatra \
rust \
rust-server \
scalatra \
scala-akka \
scala-httpclient \
scala-gatling \
scala-lagom-server \
scalaz \
spring \
dynamic-html \
html \
html2 \
swift2-deprecated \
swift3-deprecated \
swift4 \
typescript-angular \
typescript-angularjs \
typescript-aurelia \
typescript-axios \
typescript-fetch \
typescript-inversify \
typescript-jquery \
typescript-node \
typescript-rxjs"
[[ $generators =~ (^|[[:space:]])$2($|[[:space:]]) ]] && echo 'Generator found' || (echo "No such generator" && exit)
if [[ ! -e $1 ]]; then
mkdir `pwd`/$2
fi
docker run --rm -v `pwd`:/generator openapitools/openapi-generator-cli generate -o /generator/$2 -i /generator/$1 -g $2

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More