├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── base └── Dockerfile ├── docs └── img │ └── 21_banner.png ├── payments ├── Dockerfile ├── server.py ├── setup.py └── utils │ └── login.py ├── router ├── Dockerfile └── files │ └── nginx.conf └── service-ping ├── Dockerfile ├── manifest.yaml ├── ping-entrypoint.sh ├── server.py ├── setup.py └── utils ├── login.py ├── publish.py └── update_manifest.py /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Pull requests 2 | 3 | ## Code style 4 | 5 | > Programs must be written for people to read, and only incidentally for 6 | > machines to execute. 7 | > 8 | > -- Harold Abelson, Structure and Interpretation of Computer Programs 9 | 10 | ## Commit message format 11 | 12 | Commit your changes using the following commit message format (with the same 13 | capitalization, spacing, and punctuation): 14 | 15 | ``` 16 | logging: Move function to module level 17 | ``` 18 | 19 | The first part of the message is the scope and the second part is a description 20 | of the change, written in the imperative tense. 21 | 22 | ## Commit history 23 | 24 | Commits should be organized into logical units. Keep your git history clean by 25 | [rewriting](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) it as 26 | necessary using `git rebase -i` (interactive rebase) and `git push -f` (force 27 | push). Commits addressing review comments and test failures should be squashed 28 | if necessary. 29 | 30 | # Opening issues 31 | 32 | ## Bug reports 33 | 34 | Bug reports should include clear instructions to reproduce the bug. Include a 35 | stack trace if applicable. 36 | 37 | ## Security issues 38 | 39 | Critical security bugs should be sent via email to security@21.co and should not 40 | be publicly disclosed. Eligible security disclosures are eligible, at our sole 41 | discretion, for a monetary bounty of up to $1000 based on severity. 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017, 21 Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | The views and conclusions contained in the software and documentation are those 25 | of the authors and should not be interpreted as representing official policies, 26 | either expressed or implied, of 21 Inc. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 21: Bitcoin Micropayments over HTTP 2 | 3 | ![21 logo](docs/img/21_banner.png "21") 4 | 5 | # What is `two1`? 6 | 7 | `two1` consists of a command line tool and python3 library that enable you to build the [machine-payable web](https://21.co). Learn more at [https://21.co](https://21.co). 8 | 9 | # Quickstart 10 | 11 | ## Try it 12 | 13 | #### Pull the latest base image with `two1` installed: 14 | ``` bash 15 | $ docker pull 21dotco/two1 16 | ``` 17 | 18 | #### Run the image: 19 | ``` bash 20 | $ docker run -it 21dotco/two1 sh 21 | ``` 22 | 23 | #### Get started: 24 | Run `21 help` to see the help menu, or `21 status` to get started. You can also use the [Kitematic](https://kitematic.com/) GUI to launch the `two1` images. Search for `two1` and click to launch! 25 | 26 | ## Sell microservices for bitcoin 27 | 28 | The images provided in this repository are designed to work with the `21 sell` service manager that is included with your installation of **21**. [Sign up](https://21.co) to install `21` and learn more about the `21 sell` tool [here](https://21.co/learn/21-sell). 29 | 30 | # Supported tags 31 | 32 | ## `base` 33 | 34 | This is a base [Alpine Linux](http://www.alpinelinux.org/) image with the `two1` python3 libraries installed via pip. 35 | 36 | ## `router` 37 | 38 | This is an Alpine Linux image with `nginx` added and used for routing to the individual microservices and payments server. 39 | 40 | ## `payments` 41 | 42 | This is the payments server that manages the [payment channels](https://21.co/learn/intro-to-micropayment-channels) for all microservices in a `21 sell` deployment. 43 | 44 | ## `services-*` 45 | 46 | All machine-payable microservices available for you to start selling are prefixed with the string `service-`. For example, the `ping` microservice image tag `service-ping`. 47 | 48 | # Supported Docker versions 49 | 50 | These images are supported on Docker version `1.11.0` and up. 51 | 52 | ## Community 53 | 54 | Join our [global development community](https://slack.21.co) to chat with other users or to get in touch with support. 55 | 56 | # Licensing 57 | 58 | `two1` is licensed under the FreeBSD License. See [LICENSE](https://github.com/21dotco/two1-python/blob/master/LICENSE) for the full license text. Please see our Terms of Use [here](https://21.co/terms-of-use). 59 | 60 | # Issues 61 | 62 | If you have any problems with or questions about these images, please contact 63 | [support@21.co](mailto: support@21.co). 64 | -------------------------------------------------------------------------------- /base/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.5-alpine 2 | MAINTAINER 21.co 3 | 4 | RUN apk upgrade -U --available 5 | RUN apk add --no-cache gcc musl-dev 6 | RUN pip3 install two1 7 | -------------------------------------------------------------------------------- /docs/img/21_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21dotco/two1-docker/12cbdf350ac870687ad5323922c88d65a1c78b70/docs/img/21_banner.png -------------------------------------------------------------------------------- /payments/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 21dotco/two1:base 2 | MAINTAINER 21.co 3 | 4 | RUN apk add --no-cache linux-headers 5 | 6 | WORKDIR /usr/src/app 7 | COPY . ./ 8 | 9 | RUN pip3 install -e . -U 10 | CMD sh -c "python3 utils/login.py && sleep 2 && python3 server.py" 11 | -------------------------------------------------------------------------------- /payments/server.py: -------------------------------------------------------------------------------- 1 | """ Payment channels processing server. 2 | """ 3 | # 3rd party imports 4 | from flask import Flask 5 | 6 | # two1 imports 7 | from two1.bitserv.flask import Payment 8 | from two1.wallet.two1_wallet import Wallet 9 | 10 | app = Flask(__name__) 11 | wallet = Wallet() 12 | payment = Payment(app, wallet, endpoint='/payment', db_dir="/usr/src/db") 13 | 14 | if __name__ == '__main__': 15 | app.run(host='0.0.0.0', port=5000) 16 | -------------------------------------------------------------------------------- /payments/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | name='payments', 5 | version='0.0.1', 6 | packages=setuptools.find_packages(), 7 | install_requires=[ 8 | 'Flask==0.10.1', 9 | 'gunicorn==19.4.5', 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /payments/utils/login.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | 4 | import two1 5 | from two1.commands.util.config import Config 6 | from two1.server import machine_auth_wallet 7 | from two1.wallet.two1_wallet import Two1Wallet 8 | from two1.blockchain import TwentyOneProvider 9 | from two1.server import rest_client as _rest_client 10 | 11 | 12 | def login_21(): 13 | """ Restore wallet to disk and log in to 21. 14 | """ 15 | mnemonic = os.environ["TWO1_WALLET_MNEMONIC"] 16 | 17 | provider = TwentyOneProvider() 18 | wallet = Two1Wallet.import_from_mnemonic(provider, mnemonic) 19 | 20 | if not os.path.exists(os.path.dirname(Two1Wallet.DEFAULT_WALLET_PATH)): 21 | os.makedirs(os.path.dirname(Two1Wallet.DEFAULT_WALLET_PATH)) 22 | wallet.to_file(Two1Wallet.DEFAULT_WALLET_PATH) 23 | 24 | # login 25 | config = Config() 26 | machine_auth = machine_auth_wallet.MachineAuthWallet(wallet) 27 | 28 | username = os.environ["TWO1_USERNAME"] 29 | password = os.environ["TWO1_PASSWORD"] 30 | 31 | rest_client = _rest_client.TwentyOneRestClient(two1.TWO1_HOST, machine_auth, username) 32 | 33 | machine_auth_pubkey_b64 = base64.b64encode(machine_auth.public_key.compressed_bytes).decode() 34 | payout_address = machine_auth.wallet.current_address 35 | 36 | rest_client.login(payout_address=payout_address, password=password) 37 | 38 | config.set("username", username) 39 | config.set("mining_auth_pubkey", machine_auth_pubkey_b64) 40 | config.save() 41 | 42 | 43 | if __name__ == '__main__': 44 | login_21() 45 | -------------------------------------------------------------------------------- /router/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.3 2 | MAINTAINER 21.co 3 | 4 | RUN apk upgrade -U --available 5 | RUN apk add --no-cache nginx curl 6 | 7 | RUN rm /etc/nginx/nginx.conf 8 | RUN rm /etc/nginx/nginx.conf.default 9 | 10 | COPY files/nginx.conf /etc/nginx/nginx.conf 11 | CMD nginx -g "daemon off;" 12 | -------------------------------------------------------------------------------- /router/files/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 4; 2 | pid /run/nginx.pid; 3 | 4 | events { 5 | worker_connections 768; 6 | } 7 | 8 | http { 9 | 10 | ## 11 | # Basic Settings 12 | ## 13 | 14 | sendfile on; 15 | tcp_nopush on; 16 | tcp_nodelay on; 17 | keepalive_timeout 65; 18 | types_hash_max_size 2048; 19 | 20 | include /etc/nginx/mime.types; 21 | default_type application/octet-stream; 22 | 23 | ## 24 | # SSL Settings 25 | ## 26 | 27 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE 28 | ssl_prefer_server_ciphers on; 29 | 30 | ## 31 | # Logging Settings 32 | ## 33 | 34 | access_log /var/log/nginx/access.log; 35 | error_log /var/log/nginx/error.log; 36 | 37 | ## 38 | # Gzip Settings 39 | ## 40 | 41 | gzip on; 42 | gzip_disable "msie6"; 43 | 44 | ## 45 | # Virtual Host Configs 46 | ## 47 | 48 | include /etc/nginx/sites-enabled/*; 49 | } 50 | -------------------------------------------------------------------------------- /service-ping/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 21dotco/two1:base 2 | MAINTAINER 21.co 3 | 4 | RUN apk add --no-cache linux-headers 5 | 6 | WORKDIR /usr/src/app 7 | COPY . ./ 8 | 9 | RUN pip3 install -e . -U 10 | 11 | COPY ping-entrypoint.sh /usr/local/bin/ 12 | RUN chmod +x /usr/local/bin/ping-entrypoint.sh 13 | ENTRYPOINT ["ping-entrypoint.sh"] 14 | -------------------------------------------------------------------------------- /service-ping/manifest.yaml: -------------------------------------------------------------------------------- 1 | basePath: / 2 | definitions: 3 | Server: 4 | properties: 5 | city: {type: string} 6 | provider: {type: string} 7 | public_ip: {type: string} 8 | state: {type: string} 9 | zip_code: {type: string} 10 | type: object 11 | host: "" 12 | info: 13 | version: 1.0.0 14 | x-21-implements: ['31c7a5f5cb76ad33771180fbca39dd20376c3439'] 15 | contact: {email: nakamoto@nakamoto.com, name: Satoshi Nakamoto} 16 | description: Run a ping on demand for bitcoin. 17 | title: ping21 18 | x-21-category: utilities 19 | x-21-github-project-url: https://github.com/21dotco/ping21 20 | x-21-keywords: [ping, network] 21 | x-21-quick-buy: "$ 21 buy http://%s:%s/%s/?uri=21.co" 22 | x-21-total-price: {max: 3000, min: 3000} 23 | paths: 24 | /: 25 | get: 26 | consumes: [application/x-www-form-urlencoded] 27 | produces: [application/json] 28 | responses: 29 | 200: 30 | description: Ping statistics and information on server location. 31 | schema: 32 | properties: 33 | ping: 34 | items: {type: string} 35 | type: array 36 | server: {$ref: '#/definitions/Server'} 37 | type: object 38 | summary: Return ping statistics between this device and a given domain 39 | or IP. 40 | schemes: [http] 41 | swagger: '2.0' 42 | x-21-manifest-path: /manifest 43 | -------------------------------------------------------------------------------- /service-ping/ping-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | python3 utils/login.py && sleep 2 && python3 utils/update_manifest.py && sleep 2 && python3 server.py 4 | -------------------------------------------------------------------------------- /service-ping/server.py: -------------------------------------------------------------------------------- 1 | import ipaddress 2 | import logging 3 | import os 4 | import subprocess 5 | 6 | import click 7 | import re 8 | import yaml 9 | from flask import Flask 10 | from flask import request, jsonify 11 | from two1.bitserv.flask import Payment 12 | from two1.wallet.two1_wallet import Wallet 13 | import requests 14 | 15 | 16 | from two1.sell.util.decorators import track_requests 17 | from two1.sell.util.decorators import DEFAULT_PRICE 18 | 19 | app = Flask(__name__) 20 | 21 | # setup wallet 22 | wallet = Wallet() 23 | payment = Payment(app, wallet, db_dir="/usr/src/db") 24 | 25 | # hide logging 26 | log = logging.getLogger('werkzeug') 27 | log.setLevel(logging.ERROR) 28 | 29 | 30 | def get_server_info(): 31 | """Gets network metadata for the machine calling the function. 32 | 33 | see http://ipinfo.io for more info. 34 | Returns: 35 | dict: A dictionary with keys ip, hostname, city, region, country, loc, org, postal if sucessful, 36 | or a dictionary with key "error" with the error code as the corresponding value 37 | 38 | """ 39 | r = requests.get('http://ipinfo.io') 40 | try: 41 | r.raise_for_status() 42 | except requests.HTTPError: 43 | return {"error": r.status_code} 44 | else: 45 | try: 46 | return r.json() 47 | except Exception as e: 48 | return {"error": e} 49 | 50 | 51 | def is_valid_hostname(hostname): 52 | # http://stackoverflow.com/a/2532344 53 | if len(hostname) > 255: 54 | return False 55 | if hostname[-1] == ".": 56 | hostname = hostname[:-1] # strip exactly one dot from the right, if present 57 | allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?