From 6e2584d3789ac1978c17f7dd9e5ed33a87ce690a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loun=C3=A8s=20Ksouri?= Date: Fri, 12 Feb 2021 20:34:34 +0100 Subject: [PATCH] Add samourai-server app (#461) Co-authored-by: Mayank Co-authored-by: Luke Childs --- apps/samourai-server/docker-compose.yml | 137 +++++++ apps/samourai-server/mysql/data/.gitkeep | 0 .../samourai-server/mysql/db-scripts/1_db.sql | 203 ++++++++++ apps/samourai-server/mysql/mysql-dojo.cnf | 2 + apps/samourai-server/mysql/update-db.sh | 14 + .../nginx/connect/css/normalize.css | 349 ++++++++++++++++++ .../nginx/connect/css/style.css | 123 ++++++ .../nginx/connect/img/icon.svg | 19 + apps/samourai-server/nginx/connect/index.html | 92 +++++ .../nginx/connect/js/conf.template.js | 5 + .../nginx/connect/js/qrcode.min.js | 2 + .../nginx/connect/js/script.js | 37 ++ apps/samourai-server/nginx/mainnet.conf | 74 ++++ apps/samourai-server/nginx/nginx.conf | 45 +++ apps/samourai-server/nginx/testnet.conf | 79 ++++ apps/samourai-server/nginx/wait-for | 79 ++++ apps/samourai-server/whirlpool/.gitkeep | 0 scripts/app | 10 + scripts/configure | 12 + templates/.env-sample | 6 + templates/torrc-sample | 8 + 21 files changed, 1296 insertions(+) create mode 100644 apps/samourai-server/docker-compose.yml create mode 100644 apps/samourai-server/mysql/data/.gitkeep create mode 100755 apps/samourai-server/mysql/db-scripts/1_db.sql create mode 100644 apps/samourai-server/mysql/mysql-dojo.cnf create mode 100755 apps/samourai-server/mysql/update-db.sh create mode 100644 apps/samourai-server/nginx/connect/css/normalize.css create mode 100644 apps/samourai-server/nginx/connect/css/style.css create mode 100644 apps/samourai-server/nginx/connect/img/icon.svg create mode 100644 apps/samourai-server/nginx/connect/index.html create mode 100644 apps/samourai-server/nginx/connect/js/conf.template.js create mode 100644 apps/samourai-server/nginx/connect/js/qrcode.min.js create mode 100644 apps/samourai-server/nginx/connect/js/script.js create mode 100644 apps/samourai-server/nginx/mainnet.conf create mode 100644 apps/samourai-server/nginx/nginx.conf create mode 100644 apps/samourai-server/nginx/testnet.conf create mode 100755 apps/samourai-server/nginx/wait-for create mode 100755 apps/samourai-server/whirlpool/.gitkeep diff --git a/apps/samourai-server/docker-compose.yml b/apps/samourai-server/docker-compose.yml new file mode 100644 index 0000000..cfb92fb --- /dev/null +++ b/apps/samourai-server/docker-compose.yml @@ -0,0 +1,137 @@ +version: "3.7" + +x-logging: + &default-logging + driver: journald + options: + tag: "umbrel-app {{.Name}}" + +services: + db: + image: mariadb:10.5.8@sha256:8040983db146f729749081c6b216a19d52e0973134e2e34c0b4fd87f48bc15b0 + init: true + logging: *default-logging + restart: on-failure + stop_grace_period: 5m + user: "1000:1000" + environment: + MYSQL_DATABASE: samourai-main + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_USER: samourai + MYSQL_PASSWORD: password + volumes: + - ${APP_DATA_DIR}/mysql/data:/var/lib/mysql + - ${APP_DATA_DIR}/mysql/db-scripts:/docker-entrypoint-initdb.d + - ${APP_DATA_DIR}/mysql/mysql-dojo.cnf:/etc/mysql/conf.d/mysql-dojo.cnf + - ${APP_DATA_DIR}/mysql/update-db.sh:/update-db.sh + networks: + default: + ipv4_address: $APP_SAMOURAI_SERVER_DB_IP + + node: + image: louneskmt/dojo-nodejs:1.8.0@sha256:6643de76267e3a2cfd4f6d593d560a8dd001a54a7e5f9cc8df77d4cab5f1e1bf + init: true + logging: *default-logging + restart: on-failure + command: "/home/node/app/wait-for-it.sh db:3306 --timeout=720 --strict -- /home/node/app/restart.sh" + user: "1000:1000" + environment: + # GLOBAL + COMMON_BTC_NETWORK: $BITCOIN_NETWORK + DOJO_NODEJS_VERSION_TAG: 1.8.0 + TOR_PROXY_IP: $TOR_PROXY_IP + TOR_PROXY_PORT: $TOR_PROXY_PORT + + # MYSQL + MYSQL_DATABASE: samourai-main + MYSQL_USER: samourai + MYSQL_PASSWORD: password + + # NODEJS + NODE_GAP_EXTERNAL: 100 + NODE_GAP_INTERNAL: 100 + NODE_ADDR_FILTER_THRESHOLD: 1000 + NODE_ADDR_DERIVATION_MIN_CHILD: 2 + NODE_ADDR_DERIVATION_MAX_CHILD: 2 + NODE_ADDR_DERIVATION_THRESHOLD: 10 + NODE_TXS_SCHED_MAX_ENTRIES: 10 + NODE_TXS_SCHED_MAX_DELTA_HEIGHT: 18 + NODE_JWT_ACCESS_EXPIRES: 900 + NODE_JWT_REFRESH_EXPIRES: 7200 + NODE_PREFIX_STATUS: status + NODE_PREFIX_SUPPORT: support + NODE_PREFIX_STATUS_PUSHTX: status + NODE_TRACKER_MEMPOOL_PERIOD: 10000 + NODE_TRACKER_UNCONF_TXS_PERIOD: 300000 + NODE_ACTIVE_INDEXER: local_indexer + NODE_FEE_TYPE: ECONOMICAL + + # SECURITY + NODE_API_KEY: $SAMOURAI_SERVER_NODE_API_KEY + NODE_ADMIN_KEY: $SAMOURAI_SERVER_NODE_ADMIN_KEY + NODE_JWT_SECRET: $SAMOURAI_SERVER_NODE_JWT_SECRET + + # BITCOIN + BITCOIND_IP: $BITCOIN_IP + BITCOIND_RPC_PORT: $BITCOIN_RPC_PORT + BITCOIND_RPC_USER: $BITCOIN_RPC_USER + BITCOIND_RPC_PASSWORD: $BITCOIN_RPC_PASS + BITCOIND_ZMQ_RAWTXS: $BITCOIN_ZMQ_RAWTX_PORT + BITCOIND_ZMQ_BLK_HASH: $BITCOIN_ZMQ_HASHBLOCK_PORT + + # EXPLORER + EXPLORER_INSTALL: "off" + + # INDEXER + INDEXER_IP: $ELECTRUM_IP + INDEXER_RPC_PORT: $ELECTRUM_PORT + INDEXER_BATCH_SUPPORT: inactive # 'active' for ElectrumX, 'inactive' otherwise + depends_on: + - db + networks: + default: + ipv4_address: $APP_SAMOURAI_SERVER_NODE_IP + + whirlpool: + image: louneskmt/dojo-whirlpool:1.2.1@sha256:8674bca0d901e8d65d49e5cf38c597c37bf1d99168114a58b63b242dd1b38d05 + init: true + logging: *default-logging + restart: on-failure + command: /restart.sh + user: "1000:1000" + environment: + COMMON_BTC_NETWORK: $BITCOIN_NETWORK + WHIRLPOOL_RESYNC: "on" + WHIRLPOOL_DEBUG: "off" + WHIRLPOOL_DEBUG_CLIENT: "off" + NGINX_IP: $APP_SAMOURAI_SERVER_IP + volumes: + - ${APP_DATA_DIR}/whirlpool:/home/whirlpool/.whirlpool-cli + networks: + default: + ipv4_address: $APP_SAMOURAI_SERVER_WHIRLPOOL_IP + + nginx: + image: nginx:1.19-alpine@sha256:c2ce58e024275728b00a554ac25628af25c54782865b3487b11c21cafb7fabda + init: true + logging: *default-logging + restart: on-failure + command: /bin/sh -c "envsubst < /var/www/connect/js/conf.template.js > /var/www/connect/js/conf.js && /wait-for node:8080 --timeout=720 -- nginx" + volumes: + - ${APP_DATA_DIR}/nginx/wait-for:/wait-for + - ${APP_DATA_DIR}/nginx/nginx.conf:/etc/nginx/nginx.conf + - ${APP_DATA_DIR}/nginx/${BITCOIN_NETWORK}.conf:/etc/nginx/sites-enabled/dojo.conf + - ${APP_DATA_DIR}/nginx/connect:/var/www/connect + environment: + COMMON_BTC_NETWORK: $BITCOIN_NETWORK + DOJO_HIDDEN_SERVICE: $APP_HIDDEN_SERVICE + WHIRLPOOL_HIDDEN_SERVICE: $SAMOURAI_SERVER_WHIRLPOOL_HIDDEN_SERVICE + NODE_PREFIX_SUPPORT: support + NODE_ADMIN_KEY: $SAMOURAI_SERVER_NODE_ADMIN_KEY + ports: + - "$APP_SAMOURAI_SERVER_PORT:80" + depends_on: + - node + networks: + default: + ipv4_address: $APP_SAMOURAI_SERVER_IP diff --git a/apps/samourai-server/mysql/data/.gitkeep b/apps/samourai-server/mysql/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/samourai-server/mysql/db-scripts/1_db.sql b/apps/samourai-server/mysql/db-scripts/1_db.sql new file mode 100755 index 0000000..36a00ee --- /dev/null +++ b/apps/samourai-server/mysql/db-scripts/1_db.sql @@ -0,0 +1,203 @@ +# Database tables + +# Copyright © 2019 – Katana Cryptographic Ltd. All Rights Reserved. + + +# Naming conventions +# 1. Table names are lowercase plural +# 2. Join table names are snake_case plural +# 3. Column names have a table prefix +# 4. Foreign key names match primary key of foreign table + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `addresses` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `addresses` ( + `addrID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `addrAddress` varchar(74) DEFAULT NULL, + PRIMARY KEY (`addrID`), + UNIQUE KEY `addrAddress` (`addrAddress`) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `banned_addresses` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `banned_addresses` ( + `bannedAddressId` int(11) NOT NULL AUTO_INCREMENT, + `addrAddress` varchar(35) NOT NULL, + PRIMARY KEY (`bannedAddressId`), + UNIQUE KEY `banned_addresses_addresses` (`addrAddress`) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `blocks` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `blocks` ( + `blockID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `blockHash` char(64) NOT NULL DEFAULT '', + `blockParent` int(10) unsigned DEFAULT NULL, + `blockHeight` int(10) unsigned NOT NULL DEFAULT '0', + `blockTime` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`blockID`), + UNIQUE KEY `blockHash` (`blockHash`), + KEY `blockParent` (`blockParent`), + KEY `blockHeight` (`blockHeight`), + CONSTRAINT `blocks_ibfk_1` FOREIGN KEY (`blockParent`) REFERENCES `blocks` (`blockID`) ON DELETE SET NULL ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `hd` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `hd` ( + `hdID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `hdXpub` char(112) DEFAULT NULL, + `hdCreated` int(10) unsigned NOT NULL DEFAULT '0', + `hdType` smallint(5) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`hdID`), + UNIQUE KEY `hdXpub` (`hdXpub`), + KEY `hdCreated` (`hdCreated`) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `hd_addresses` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `hd_addresses` ( + `hdAddrID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `hdID` int(10) unsigned NOT NULL DEFAULT '0', + `addrID` int(10) unsigned NOT NULL DEFAULT '0', + `hdAddrChain` smallint(5) unsigned NOT NULL DEFAULT '0', + `hdAddrIndex` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`hdAddrID`), + UNIQUE KEY `hdID_2` (`hdID`,`addrID`), + KEY `hdID` (`hdID`), + KEY `addrID` (`addrID`), + CONSTRAINT `hd_addresses_ibfk_1` FOREIGN KEY (`hdID`) REFERENCES `hd` (`hdID`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `hd_addresses_ibfk_2` FOREIGN KEY (`addrID`) REFERENCES `addresses` (`addrID`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `inputs` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `inputs` ( + `inID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `outID` int(10) unsigned NOT NULL DEFAULT '0', + `txnID` int(10) unsigned NOT NULL DEFAULT '0', + `inIndex` int(10) unsigned NOT NULL DEFAULT '0', + `inSequence` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`inID`), + UNIQUE KEY `txnID_2` (`txnID`,`inIndex`), + KEY `outID` (`outID`), + KEY `txnID` (`txnID`), + CONSTRAINT `inputs_ibfk_1` FOREIGN KEY (`txnID`) REFERENCES `transactions` (`txnID`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `inputs_ibfk_2` FOREIGN KEY (`outID`) REFERENCES `outputs` (`outID`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `outputs` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `outputs` ( + `outID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `txnID` int(10) unsigned NOT NULL DEFAULT '0', + `addrID` int(10) unsigned NOT NULL DEFAULT '0', + `outIndex` int(10) unsigned NOT NULL DEFAULT '0', + `outAmount` bigint(20) unsigned NOT NULL DEFAULT '0', + `outScript` varchar(20000) NOT NULL DEFAULT '', + PRIMARY KEY (`outID`), + UNIQUE KEY `txnID_2` (`txnID`,`addrID`,`outIndex`), + KEY `txnID` (`txnID`), + KEY `addrID` (`addrID`), + CONSTRAINT `outputs_ibfk_1` FOREIGN KEY (`txnID`) REFERENCES `transactions` (`txnID`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `outputs_ibfk_2` FOREIGN KEY (`addrID`) REFERENCES `addresses` (`addrID`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `transactions` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `transactions` ( + `txnID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `txnTxid` char(64) DEFAULT NULL, + `txnCreated` int(10) unsigned NOT NULL DEFAULT '0', + `txnVersion` int(10) unsigned NOT NULL DEFAULT '0', + `txnLocktime` int(10) unsigned NOT NULL DEFAULT '0', + `blockID` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`txnID`), + UNIQUE KEY `txnTxid` (`txnTxid`), + KEY `txnCreated` (`txnCreated`), + KEY `blockID` (`blockID`), + CONSTRAINT `transactions_ibfk_1` FOREIGN KEY (`blockID`) REFERENCES `blocks` (`blockID`) ON DELETE SET NULL ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `scheduled_transactions` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `scheduled_transactions` ( + `schID` int(10) unsigned NOT NULL AUTO_INCREMENT, + `schTxid` char(64) NOT NULL DEFAULT '', + `schCreated` int(10) unsigned NOT NULL DEFAULT '0', + `schRaw` varchar(50000) NOT NULL DEFAULT '', + `schParentID` int(10) unsigned DEFAULT NULL, + `schParentTxid` char(64) DEFAULT '', + `schDelay` int(10) unsigned NOT NULL DEFAULT '0', + `schTrigger` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`schID`), + UNIQUE KEY `schTxid` (`schTxid`), + KEY `schParentID` (`schParentID`), + CONSTRAINT `scheduled_transactions_ibfk_1` FOREIGN KEY (`schParentID`) REFERENCES `scheduled_transactions` (`schID`) ON DELETE SET NULL ON UPDATE NO ACTION +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/apps/samourai-server/mysql/mysql-dojo.cnf b/apps/samourai-server/mysql/mysql-dojo.cnf new file mode 100644 index 0000000..1b697f5 --- /dev/null +++ b/apps/samourai-server/mysql/mysql-dojo.cnf @@ -0,0 +1,2 @@ +[mysqld] +sql_mode="NO_ENGINE_SUBSTITUTION" \ No newline at end of file diff --git a/apps/samourai-server/mysql/update-db.sh b/apps/samourai-server/mysql/update-db.sh new file mode 100755 index 0000000..001d2d9 --- /dev/null +++ b/apps/samourai-server/mysql/update-db.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +for i in {30..0}; do + if echo "SELECT 1" | mysql -h"db" -u"root" -p"$MYSQL_ROOT_PASSWORD" &> /dev/null; then + break + fi + echo "MySQL init process in progress..." + sleep 1 +done + +if [ -f /docker-entrypoint-initdb.d/2_update.sql ]; then + mysql -h"db" -u"root" -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < /docker-entrypoint-initdb.d/2_update.sql + echo "Updated database with 2_update.sql" +fi diff --git a/apps/samourai-server/nginx/connect/css/normalize.css b/apps/samourai-server/nginx/connect/css/normalize.css new file mode 100644 index 0000000..192eb9c --- /dev/null +++ b/apps/samourai-server/nginx/connect/css/normalize.css @@ -0,0 +1,349 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} diff --git a/apps/samourai-server/nginx/connect/css/style.css b/apps/samourai-server/nginx/connect/css/style.css new file mode 100644 index 0000000..360868b --- /dev/null +++ b/apps/samourai-server/nginx/connect/css/style.css @@ -0,0 +1,123 @@ +body { + background-color: #1D1B1B; + font-family: system-ui,-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,Segoe UI,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji; + color: #fff; +} + +*, h1, h2, h3, h4, h5, h6, p, span { + color: #fff; + font-size: 20px; + font-weight: normal; +} + +.success { + color: #63FB72; +} + +.text-muted { + opacity: 0.8; +} + +hr { + width: 100%; + height: 2px; + background: #2F2C2C; + border: none; +} + +.container { + padding: 40px; + max-width: 1440px; + margin: auto; +} + +.app-icon { + border-radius: 20px; +} + +.app { + display: flex; + margin: 20px 0 40px 0; +} + +.app > .app-icon { + flex-shrink: 0; + height: 140px; + width: 140px; + box-shadow: 0 0 40px 0 rgba(0,0,0,0.95); + margin-right: 24px; +} + +.app > .app-details > .app-status { + display: block; + font-size: 20px; + margin: 10px 0 0 0; +} + + + +.app > .app-details > .app-name { + font-size: 52px; + line-height: 52px; + font-weight: bold; + margin: 10px 0 0 0; +} + +.heading { + display: flex; +} + +.heading > .number { + flex-shrink: 0; + background: #C12525; + height: 66px; + width: 66px; + border-radius: 100%; + line-height: 66px; + text-align: center; + font-size: 36px; + font-weight: bold; + box-shadow: 0 0 20px 0 rgba(0,0,0,0.8); +} +.heading > .text { + font-size: 52px; + line-height: 52px; + font-weight: bold; + display: inline-block; + margin: 5px 0 0 20px; +} + +.steps { + margin: 40px 0 0 9px; +} + +.steps > .step { + margin-bottom: 20px; + font-size: 20px; + font-weight: normal; +} + +.qr { + position: relative; + width: 260px; + height: 260px; + margin: 20px 0; +} + +.qr > .icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate3d(-50%, -50%, 0); + height: 66px; + width: 66px; + background: #ffffff; +} + +.qr > .icon > img { + display: block; + width: 60px; + height: 60px; + margin: 3px 0 0 3px; + border-radius: 15%; +} \ No newline at end of file diff --git a/apps/samourai-server/nginx/connect/img/icon.svg b/apps/samourai-server/nginx/connect/img/icon.svg new file mode 100644 index 0000000..2c93b5d --- /dev/null +++ b/apps/samourai-server/nginx/connect/img/icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/samourai-server/nginx/connect/index.html b/apps/samourai-server/nginx/connect/index.html new file mode 100644 index 0000000..8445240 --- /dev/null +++ b/apps/samourai-server/nginx/connect/index.html @@ -0,0 +1,92 @@ + + + + + + + + Samourai Server + + + + + + + + +
+
+ +
+ ● Running +

Samourai Server

+
+
+

Follow the instructions below to pair Dojo and Whirlpool running on your Umbrel to your + Samourai Wallet. +

+
+
+
+
+ 1 +

How to connect Dojo

+
+
    +
  1. Open the Samourai Wallet app on your phone.
  2. +
  3. If you already have an existing wallet on it, send all of your funds to a different wallet + that you + control and erase your existing wallet from Settings > Wallet > Secure Erase Wallet. If you don’t + have a wallet setup, skip this step.
  4. +
  5. Select Mainnet.
  6. +
  7. Tap the 3-dot menu and select “Connect to existing Dojo”.
  8. +
  9. Tap “Connect to existing Dojo” and scan this QR Code: +
    +
    +
    + +
    +
    + + +
  10. +
  11. Tap "Start New Wallet" and finish the wallet creation process.
  12. +
  13. Congratulations! Your Samourai Wallet is now backed by the Dojo server running on your + Umbrel. Open + Network Options by tapping the WiFi-like icon on the top to verify if “Dojo Full Node” is successfully + enabled (it should display a green dot).
  14. +
+
+
+
+
+ 2 +

How to connect Whirlpool

+
+
    +
  1. Install Tor on your computer.
  2. +
  3. Download and install Whirlpool GUI.
  4. +
  5. Select: Advanced: remote CLI.
  6. +
  7. Enter "" (without quotes) in “CLI + address”.
  8. +
  9. Tor proxy should now auto enable and set itself to “socks5://127.0.0.1:9050”.
  10. +
  11. Click connect.
  12. +
  13. Click QR code icon to scan a QR code from Samourai Wallet on your phone.
  14. +
  15. Open Samourai Wallet on your phone.
  16. +
  17. Go Settings > Transactions > Experimental > Pair to Whirlpool GUI. Show the QR code on your + phone to your desktop's webcam to scan it.
  18. +
  19. Click “Initialize GUI”.
  20. +
  21. Enter your Samourai Wallet’s passphrase (BIP39 passphrase set in Samourai wallet).
  22. +
  23. Choose a number of mixes for a UTXO.
  24. +
  25. Click “Mix”.
  26. +
  27. Congratulations! Whirlpool is now mixing your UTXOs on your Umbrel!
  28. +
+

Note: You'll need to open Whirlpool GUI and re-enter your password to continue mixing after restarting or updating your Umbrel.

+
+ + + + + + diff --git a/apps/samourai-server/nginx/connect/js/conf.template.js b/apps/samourai-server/nginx/connect/js/conf.template.js new file mode 100644 index 0000000..0bf6c36 --- /dev/null +++ b/apps/samourai-server/nginx/connect/js/conf.template.js @@ -0,0 +1,5 @@ +var dojoHiddenService = "$DOJO_HIDDEN_SERVICE"; +var whirlpoolHiddenService = "http://$WHIRLPOOL_HIDDEN_SERVICE"; +var bitcoinNetwork = "$COMMON_BTC_NETWORK"; +var apiKey = "$NODE_ADMIN_KEY"; +var supportPrefix = "$NODE_PREFIX_SUPPORT"; diff --git a/apps/samourai-server/nginx/connect/js/qrcode.min.js b/apps/samourai-server/nginx/connect/js/qrcode.min.js new file mode 100644 index 0000000..5d45a13 --- /dev/null +++ b/apps/samourai-server/nginx/connect/js/qrcode.min.js @@ -0,0 +1,2 @@ +/*! qrcode-svg v1.1.0 | https://github.com/papnkukn/qrcode-svg | MIT license */ +function QR8bitByte(t){this.mode=QRMode.MODE_8BIT_BYTE,this.data=t,this.parsedData=[];for(var e=0,r=this.data.length;e65536?(o[0]=240|(1835008&n)>>>18,o[1]=128|(258048&n)>>>12,o[2]=128|(4032&n)>>>6,o[3]=128|63&n):n>2048?(o[0]=224|(61440&n)>>>12,o[1]=128|(4032&n)>>>6,o[2]=128|63&n):n>128?(o[0]=192|(1984&n)>>>6,o[1]=128|63&n):o[0]=n,this.parsedData.push(o)}this.parsedData=Array.prototype.concat.apply([],this.parsedData),this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function QRCodeModel(t,e){this.typeNumber=t,this.errorCorrectLevel=e,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}QR8bitByte.prototype={getLength:function(t){return this.parsedData.length},write:function(t){for(var e=0,r=this.parsedData.length;e=7&&this.setupTypeNumber(t),null==this.dataCache&&(this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,e)},setupPositionProbePattern:function(t,e){for(var r=-1;r<=7;r++)if(!(t+r<=-1||this.moduleCount<=t+r))for(var o=-1;o<=7;o++)e+o<=-1||this.moduleCount<=e+o||(this.modules[t+r][e+o]=0<=r&&r<=6&&(0==o||6==o)||0<=o&&o<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=o&&o<=4)},getBestMaskPattern:function(){for(var t=0,e=0,r=0;r<8;r++){this.makeImpl(!0,r);var o=QRUtil.getLostPoint(this);(0==r||t>o)&&(t=o,e=r)}return e},createMovieClip:function(t,e,r){var o=t.createEmptyMovieClip(e,r);this.make();for(var n=0;n>r&1);this.modules[Math.floor(r/3)][r%3+this.moduleCount-8-3]=o}for(r=0;r<18;r++){o=!t&&1==(e>>r&1);this.modules[r%3+this.moduleCount-8-3][Math.floor(r/3)]=o}},setupTypeInfo:function(t,e){for(var r=this.errorCorrectLevel<<3|e,o=QRUtil.getBCHTypeInfo(r),n=0;n<15;n++){var i=!t&&1==(o>>n&1);n<6?this.modules[n][8]=i:n<8?this.modules[n+1][8]=i:this.modules[this.moduleCount-15+n][8]=i}for(n=0;n<15;n++){i=!t&&1==(o>>n&1);n<8?this.modules[8][this.moduleCount-n-1]=i:n<9?this.modules[8][15-n-1+1]=i:this.modules[8][15-n-1]=i}this.modules[this.moduleCount-8][8]=!t},mapData:function(t,e){for(var r=-1,o=this.moduleCount-1,n=7,i=0,a=this.moduleCount-1;a>0;a-=2)for(6==a&&a--;;){for(var s=0;s<2;s++)if(null==this.modules[o][a-s]){var h=!1;i>>n&1)),QRUtil.getMask(e,o,a-s)&&(h=!h),this.modules[o][a-s]=h,-1==--n&&(i++,n=7)}if((o+=r)<0||this.moduleCount<=o){o-=r,r=-r;break}}}},QRCodeModel.PAD0=236,QRCodeModel.PAD1=17,QRCodeModel.createData=function(t,e,r){for(var o=QRRSBlock.getRSBlocks(t,e),n=new QRBitBuffer,i=0;i8*s)throw new Error("code length overflow. ("+n.getLengthInBits()+">"+8*s+")");for(n.getLengthInBits()+4<=8*s&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*s||(n.put(QRCodeModel.PAD0,8),n.getLengthInBits()>=8*s));)n.put(QRCodeModel.PAD1,8);return QRCodeModel.createBytes(n,o)},QRCodeModel.createBytes=function(t,e){for(var r=0,o=0,n=0,i=new Array(e.length),a=new Array(e.length),s=0;s=0?d.get(f):0}}var c=0;for(u=0;u=0;)e^=QRUtil.G15<=0;)e^=QRUtil.G18<>>=1;return e},getPatternPosition:function(t){return QRUtil.PATTERN_POSITION_TABLE[t-1]},getMask:function(t,e,r){switch(t){case QRMaskPattern.PATTERN000:return(e+r)%2==0;case QRMaskPattern.PATTERN001:return e%2==0;case QRMaskPattern.PATTERN010:return r%3==0;case QRMaskPattern.PATTERN011:return(e+r)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(e/2)+Math.floor(r/3))%2==0;case QRMaskPattern.PATTERN101:return e*r%2+e*r%3==0;case QRMaskPattern.PATTERN110:return(e*r%2+e*r%3)%2==0;case QRMaskPattern.PATTERN111:return(e*r%3+(e+r)%2)%2==0;default:throw new Error("bad maskPattern:"+t)}},getErrorCorrectPolynomial:function(t){for(var e=new QRPolynomial([1],0),r=0;r5&&(r+=3+i-5)}for(o=0;o=256;)t-=255;return QRMath.EXP_TABLE[t]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},i=0;i<8;i++)QRMath.EXP_TABLE[i]=1<>>7-t%8&1)},put:function(t,e){for(var r=0;r>>e-r-1&1))},getLengthInBits:function(){return this.length},putBit:function(t){var e=Math.floor(this.length/8);this.buffer.length<=e&&this.buffer.push(0),t&&(this.buffer[e]|=128>>>this.length%8),this.length++}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];function QRCode(t){if(this.options={padding:4,width:256,height:256,typeNumber:4,color:"#000000",background:"#ffffff",ecl:"M"},"string"==typeof t&&(t={content:t}),t)for(var e in t)this.options[e]=t[e];if("string"!=typeof this.options.content)throw new Error("Expected 'content' as string!");if(0===this.options.content.length)throw new Error("Expected 'content' to be non-empty!");if(!(this.options.padding>=0))throw new Error("Expected 'padding' value to be non-negative!");if(!(this.options.width>0&&this.options.height>0))throw new Error("Expected 'width' or 'height' value to be higher than zero!");var r=this.options.content,o=function(t,e){for(var r=function(t){var e=encodeURI(t).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return e.length+(e.length!=t?3:0)}(t),o=1,n=0,i=0,a=QRCodeLimitLength.length;i<=a;i++){var s=QRCodeLimitLength[i];if(!s)throw new Error("Content too long: expected "+n+" but got "+r);switch(e){case"L":n=s[0];break;case"M":n=s[1];break;case"Q":n=s[2];break;case"H":n=s[3];break;default:throw new Error("Unknwon error correction level: "+e)}if(r<=n)break;o++}if(o>QRCodeLimitLength.length)throw new Error("Content too long");return o}(r,this.options.ecl),n=function(t){switch(t){case"L":return QRErrorCorrectLevel.L;case"M":return QRErrorCorrectLevel.M;case"Q":return QRErrorCorrectLevel.Q;case"H":return QRErrorCorrectLevel.H;default:throw new Error("Unknwon error correction level: "+t)}}(this.options.ecl);this.qrcode=new QRCodeModel(o,n),this.qrcode.addData(r),this.qrcode.make()}QRCode.prototype.svg=function(t){var e=this.options||{},r=this.qrcode.modules;void 0===t&&(t={container:e.container||"svg"});for(var o=void 0===e.pretty||!!e.pretty,n=o?" ":"",i=o?"\r\n":"",a=e.width,s=e.height,h=r.length,l=a/(h+2*e.padding),u=s/(h+2*e.padding),g=void 0!==e.join&&!!e.join,d=void 0!==e.swap&&!!e.swap,f=void 0===e.xmlDeclaration||!!e.xmlDeclaration,c=void 0!==e.predefined&&!!e.predefined,R=c?n+''+i:"",p=n+''+i,m="",Q="",v=0;v'+i:n+''+i}}g&&(m=n+'');var T="";switch(t.container){case"svg":f&&(T+=''+i),T+=''+i,T+=R+p+m,T+="";break;case"svg-viewbox":f&&(T+=''+i),T+=''+i,T+=R+p+m,T+="";break;case"g":T+=''+i,T+=R+p+m,T+="";break;default:T+=(R+p+m).replace(/^\s+/,"")}return T},QRCode.prototype.save=function(t,e){var r=this.svg();"function"!=typeof e&&(e=function(t,e){});try{require("fs").writeFile(t,r,e)}catch(t){e(t)}},"undefined"!=typeof module&&(module.exports=QRCode); diff --git a/apps/samourai-server/nginx/connect/js/script.js b/apps/samourai-server/nginx/connect/js/script.js new file mode 100644 index 0000000..ba406ec --- /dev/null +++ b/apps/samourai-server/nginx/connect/js/script.js @@ -0,0 +1,37 @@ +var baseRoute = bitcoinNetwork == "testnet" ? "test/v2" : "v2"; + +fetch(`http://${window.location.host}/${baseRoute}/auth/login`, { + method: 'POST', + headers: new Headers({ + 'Content-Type': 'application/x-www-form-urlencoded' + }), + body: `apikey=${apiKey}` + }) + .then(response => response.json()) + .then(data => { + fetch(`http://${window.location.host}/${baseRoute}/${supportPrefix}/pairing`, { + method: 'GET', + headers: new Headers({ + 'Authorization': 'Bearer ' + data.authorizations.access_token, + 'Content-Type': 'application/json' + }) + }) + .then(response => response.json()) + .then(data => { + var pairingInfo = data; + pairingInfo.pairing.url = `http://${dojoHiddenService}/${baseRoute}`; + + const qrcodeSvg = new QRCode({ + content: JSON.stringify(pairingInfo), + join: true, + container: "svg-viewbox", + padding: 3, + color: "#000000", + background: "#ffffff", + ecl: "M", + }).svg(); + document.querySelector('.qr-contents').innerHTML = qrcodeSvg; + + document.getElementById('whirlpool-hidden-service').innerText = `${whirlpoolHiddenService}`; + }); + }); diff --git a/apps/samourai-server/nginx/mainnet.conf b/apps/samourai-server/nginx/mainnet.conf new file mode 100644 index 0000000..ba2ae1b --- /dev/null +++ b/apps/samourai-server/nginx/mainnet.conf @@ -0,0 +1,74 @@ +# Proxy WebSockets +# https://www.nginx.com/blog/websocket-nginx/ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +# WebSocket server listening here +upstream websocket { + server node:8080; +} + +# Site Configuration +server { + listen 80; + server_name _; + + # Set proxy timeouts for the application + proxy_connect_timeout 600; + proxy_read_timeout 600; + proxy_send_timeout 600; + send_timeout 600; + + # Connection details page + location /connect { + alias /var/www/connect; + index index.html; + try_files $uri $uri/ =404; + } + + # Proxy WebSocket connections first + location /v2/inv { + proxy_pass http://websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + + # PushTX server is separate, so proxy first + location /v2/pushtx/ { + proxy_pass http://node:8081/; + } + + # Tracker server is separate, so proxy first + location /v2/tracker/ { + proxy_pass http://node:8082/; + } + + # Proxy requests to maintenance tool + location = /admin/conf/index.js { + proxy_pass http://node:8080/static/admin/conf/index-mainnet.js; + } + + location /admin/ { + proxy_pass http://node:8080/static/admin/; + } + + # Proxy all other v2 requests to the accounts server + location /v2/ { + proxy_pass http://node:8080/; + } + + # Redirect onion address to maintenance tool + location = / { + return 301 /admin; + } + + # Serve remaining requests + location / { + return 200 '{"status":"ok"}'; + add_header Content-Type application/json; + } +} + diff --git a/apps/samourai-server/nginx/nginx.conf b/apps/samourai-server/nginx/nginx.conf new file mode 100644 index 0000000..0261baf --- /dev/null +++ b/apps/samourai-server/nginx/nginx.conf @@ -0,0 +1,45 @@ +user nginx; +worker_processes auto; +daemon off; + +# Log critical errors and higher to stderr +# (see https://github.com/nginxinc/docker-nginx/blob/594ce7a8bc26c85af88495ac94d5cd0096b306f7/mainline/alpine/Dockerfile#L104) +error_log /var/log/nginx/error.log crit; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Disable activity logging for privacy. + access_log off; + + # Do not reveal the version of server + server_tokens off; + + sendfile on; + + keepalive_timeout 95; + + # Enable response compression + gzip on; + # Compression level: 1-9 + gzip_comp_level 1; + # Disable gzip compression for older IE + gzip_disable msie6; + # Minimum length of response before gzip kicks in + gzip_min_length 128; + # Compress these MIME types in addition to text/html + gzip_types application/json; + # Help with proxying by adding the Vary: Accept-Encoding response + gzip_vary on; + + include /etc/nginx/sites-enabled/*.conf; +} + diff --git a/apps/samourai-server/nginx/testnet.conf b/apps/samourai-server/nginx/testnet.conf new file mode 100644 index 0000000..7bd968a --- /dev/null +++ b/apps/samourai-server/nginx/testnet.conf @@ -0,0 +1,79 @@ +# Proxy WebSockets +# https://www.nginx.com/blog/websocket-nginx/ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +# WebSocket server listening here +upstream websocket { + server node:8080; +} + +# Site Configuration +server { + listen 80; + server_name _; + + # Set proxy timeouts for the application + proxy_connect_timeout 600; + proxy_read_timeout 600; + proxy_send_timeout 600; + send_timeout 600; + + # Connection details page + location /connect { + alias /var/www/connect; + index index.html; + try_files $uri $uri/ =404; + } + + # Proxy WebSocket connections first + location /test/v2/inv { + proxy_pass http://websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + + # PushTX server is separate, so proxy first + location /test/v2/pushtx/ { + proxy_pass http://node:8081/; + } + + # Tracker server is separate, so proxy first + location /test/v2/tracker/ { + proxy_pass http://node:8082/; + } + + # Proxy requests to maintenance tool + location = /admin/conf/index.js { + proxy_pass http://node:8080/static/admin/conf/index-testnet.js; + } + + location /admin/ { + proxy_pass http://node:8080/static/admin/; + } + + # Proxy all other v2 requests to the accounts server + location /test/v2/ { + proxy_pass http://node:8080/; + } + + # Redirect onion address to maintenance tool + location = / { + return 301 /admin; + } + + # Serve remaining requests + location / { + return 200 '{"status":"ok"}'; + add_header Content-Type application/json; + } + + location /test/ { + return 200 '{"status":"ok"}'; + add_header Content-Type application/json; + } +} + diff --git a/apps/samourai-server/nginx/wait-for b/apps/samourai-server/nginx/wait-for new file mode 100755 index 0000000..539a01d --- /dev/null +++ b/apps/samourai-server/nginx/wait-for @@ -0,0 +1,79 @@ +#!/bin/sh + +TIMEOUT=15 +QUIET=0 + +echoerr() { + if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi +} + +usage() { + exitcode="$1" + cat << USAGE >&2 +Usage: + $cmdname host:port [-t timeout] [-- command args] + -q | --quiet Do not output any status messages + -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit "$exitcode" +} + +wait_for() { + for i in `seq $TIMEOUT` ; do + nc -z "$HOST" "$PORT" > /dev/null 2>&1 + + result=$? + if [ $result -eq 0 ] ; then + if [ $# -gt 0 ] ; then + exec "$@" + fi + exit 0 + fi + sleep 1 + done + echo "Operation timed out" >&2 + exit 1 +} + +while [ $# -gt 0 ] +do + case "$1" in + *:* ) + HOST=$(printf "%s\n" "$1"| cut -d : -f 1) + PORT=$(printf "%s\n" "$1"| cut -d : -f 2) + shift 1 + ;; + -q | --quiet) + QUIET=1 + shift 1 + ;; + -t) + TIMEOUT="$2" + if [ "$TIMEOUT" = "" ]; then break; fi + shift 2 + ;; + --timeout=*) + TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + break + ;; + --help) + usage 0 + ;; + *) + echoerr "Unknown argument: $1" + usage 1 + ;; + esac +done + +if [ "$HOST" = "" -o "$PORT" = "" ]; then + echoerr "Error: you need to provide a host and port to test." + usage 2 +fi + +wait_for "$@" diff --git a/apps/samourai-server/whirlpool/.gitkeep b/apps/samourai-server/whirlpool/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/scripts/app b/scripts/app index 68b6f20..037763f 100755 --- a/scripts/app +++ b/scripts/app @@ -106,6 +106,16 @@ compose() { export APP_DATA_DIR="${app_data_dir}" export APP_HIDDEN_SERVICE="$(cat "${app_hidden_servive_file}" 2>/dev/null || echo "notyetset.onion")" export APP_SEED=$(derive_entropy "${app_entropy_identifier}") + + # App specific env vars + # Note: Hardcoding app specific env vars is a short term solution. Long term + # these values will be registered in an apps manifest and generated dynamically. + local whirlpool_hidden_service_file="${UMBREL_ROOT}/tor/data/app-${app}-whirlpool/hostname" + export SAMOURAI_SERVER_WHIRLPOOL_HIDDEN_SERVICE="$(cat "${whirlpool_hidden_service_file}" 2>/dev/null || echo "notyetset.onion")" + export SAMOURAI_SERVER_NODE_API_KEY=$(derive_entropy "env-${app_entropy_identifier}-NODE_API_KEY") + export SAMOURAI_SERVER_NODE_ADMIN_KEY=$(derive_entropy "env-${app_entropy_identifier}-NODE_ADMIN_KEY") + export SAMOURAI_SERVER_NODE_JWT_SECRET=$(derive_entropy "env-${app_entropy_identifier}-NODE_JWT_SECRET") + docker-compose \ --env-file "${env_file}" \ --project-name "${app}" \ diff --git a/scripts/configure b/scripts/configure index cfeb784..31a7e3b 100755 --- a/scripts/configure +++ b/scripts/configure @@ -147,6 +147,12 @@ APP_MEMPOOL_IP="10.21.21.26" APP_MEMPOOL_PORT="3006" APP_MEMPOOL_API_IP="10.21.21.27" APP_MEMPOOL_DB_IP="10.21.21.28" +APP_SAMOURAI_SERVER_IP="10.21.21.22" +APP_SAMOURAI_SERVER_PORT="3005" +APP_SAMOURAI_SERVER_WHIRLPOOL_IP="10.21.21.23" +APP_SAMOURAI_SERVER_WHIRLPOOL_PORT="8898" +APP_SAMOURAI_SERVER_DB_IP="10.21.21.24" +APP_SAMOURAI_SERVER_NODE_IP="10.21.21.25" # Generate RPC credentials if [[ -z ${BITCOIN_RPC_USER+x} ]] || [[ -z ${BITCOIN_RPC_PASS+x} ]] || [[ -z ${BITCOIN_RPC_AUTH+x} ]]; then @@ -292,6 +298,12 @@ for template in "${NGINX_CONF_FILE}" "${BITCOIN_CONF_FILE}" "${LND_CONF_FILE}" " sed -i "s//${APP_MEMPOOL_PORT}/g" "${template}" sed -i "s//${APP_MEMPOOL_DB_IP}/g" "${template}" sed -i "s//${APP_MEMPOOL_API_IP}/g" "${template}" + sed -i "s//${APP_SAMOURAI_SERVER_IP}/g" "${template}" + sed -i "s//${APP_SAMOURAI_SERVER_PORT}/g" "${template}" + sed -i "s//${APP_SAMOURAI_SERVER_WHIRLPOOL_IP}/g" "${template}" + sed -i "s//${APP_SAMOURAI_SERVER_WHIRLPOOL_PORT}/g" "${template}" + sed -i "s//${APP_SAMOURAI_SERVER_DB_IP}/g" "${template}" + sed -i "s//${APP_SAMOURAI_SERVER_NODE_IP}/g" "${template}" done diff --git a/templates/.env-sample b/templates/.env-sample index d2509a0..dee05f1 100644 --- a/templates/.env-sample +++ b/templates/.env-sample @@ -50,3 +50,9 @@ APP_MEMPOOL_IP= APP_MEMPOOL_PORT= APP_MEMPOOL_DB_IP= APP_MEMPOOL_API_IP= +APP_SAMOURAI_SERVER_IP= +APP_SAMOURAI_SERVER_PORT= +APP_SAMOURAI_SERVER_WHIRLPOOL_IP= +APP_SAMOURAI_SERVER_WHIRLPOOL_PORT= +APP_SAMOURAI_SERVER_DB_IP= +APP_SAMOURAI_SERVER_NODE_IP= diff --git a/templates/torrc-sample b/templates/torrc-sample index 27a8c7d..138d096 100644 --- a/templates/torrc-sample +++ b/templates/torrc-sample @@ -65,4 +65,12 @@ HiddenServicePort 80 : HiddenServiceDir /var/lib/tor/app-mempool HiddenServicePort 80 : +# samourai-server Hidden Service +HiddenServiceDir /var/lib/tor/app-samourai-server +HiddenServicePort 80 :80 + +# samourai-server whirlpool Hidden Service +HiddenServiceDir /var/lib/tor/app-samourai-server-whirlpool +HiddenServicePort 80 : + HashedControlPassword