diff --git a/default.nix b/default.nix index b83484f..139848c 100644 --- a/default.nix +++ b/default.nix @@ -17,6 +17,7 @@ let inherit prometheus_client; inherit pyjwt; inherit pytest; + inherit pyserial; inherit qrcode; inherit raspberrypi-tools; inherit requests; @@ -106,19 +107,11 @@ in with upstream; let propagatedBuildInputs = [ raspberrypi-tools ]; }; - cygpio = buildPythonPackage { - pname = "cygpio"; - version = "1.0.0"; - src = ./cygpio; - propagatedBuildInputs = [ pigpio cython ]; - }; - in buildPythonPackage rec { name = "bitvend"; src = ./.; doCheck = false; propagatedBuildInputs = [ - cygpio flask flask_sqlalchemy websocket_client @@ -127,5 +120,6 @@ in buildPythonPackage rec { prometheus_client spaceauth qrcode + pyserial ]; } diff --git a/lego-hook.sh b/lego-hook.sh new file mode 100755 index 0000000..6bcacab --- /dev/null +++ b/lego-hook.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +# lego generates proper absolute fqdn with trailing dot. API expects one without it +FQDN="${2::-1}" +CHALLENGE="$3" + +case $1 in + present) + curl "$API_URL/api/1/add?token=$API_TOKEN&record=$FQDN:TXT&value=\"$CHALLENGE\"" ;; + cleanup) + curl "$API_URL/api/1/delete?token=$API_TOKEN&record=$FQDN:TXT" ;; + timeout) + echo '{"timeout": 240, "interval": 5}' ;; +esac diff --git a/mdb/backend.py b/mdb/backend.py index 1fc686e..06b402a 100644 --- a/mdb/backend.py +++ b/mdb/backend.py @@ -87,10 +87,21 @@ class SerialBackend(Backend): import serial self.ser = serial.Serial(device) + # FIXME clear buffer + self.ser.timeout = 0.2 + while self.ser.read(1): + pass + self.ser.timeout = None + def read(self): buf = b'' while len(buf) < 2: buf += self.ser.read(1) + + # FIXME drop if 9th bit byte is invalid (desync) + if len(buf) >= 2 and buf[1] not in [0, 1]: + buf = buf[1:] + return buf def write(self, data): diff --git a/mdb/device.py b/mdb/device.py index f05144a..0fa97a0 100644 --- a/mdb/device.py +++ b/mdb/device.py @@ -10,7 +10,6 @@ except ImportError: try: import cygpio except ImportError: - raise cygpio = None from mdb.utils import compute_checksum, compute_chk, bcd_decode @@ -219,7 +218,7 @@ class CashlessMDBDevice(MDBDevice): self.logger.info('VEND: request %r', req) value, product_bcd = struct.unpack('>xhhx', req.data) product = bcd_decode(product_bcd) - self.logger.info('VEND: requested %d for %d', product, value) + self.logger.info('VEND: requested %d (%04x) for %d', product, product_bcd, value) if self.vend_request(product, value): # accept. two latter bytes are value subtracted from balance # displayed after purchase FIXME diff --git a/mdb/utils.py b/mdb/utils.py index 37459fa..902bd2c 100644 --- a/mdb/utils.py +++ b/mdb/utils.py @@ -7,4 +7,8 @@ def compute_checksum(cmd, data): return compute_chk(bytearray([cmd]) + data) def bcd_decode(b): - return 10 * ((b & 0xf0) >> 4) + (b & 0x0f) + return \ + 1000 * ((b & 0xf000) >> 12) + \ + 100 * ((b & 0xf00) >> 8) + \ + 10 * ((b & 0xf0) >> 4) + \ + (b & 0x0f) diff --git a/module.nix b/module.nix index b8149f5..a698a69 100644 --- a/module.nix +++ b/module.nix @@ -17,7 +17,12 @@ let BLOCKCYPHER_TOKEN = '${cfg.blockcypherToken}' SECRET_KEY = '${cfg.secretKey}' ''; - + legoHook = pkgs.runCommand "lego-hook-wrapped" { + buildInputs = [ pkgs.makeWrapper ]; + } '' + makeWrapper ${./lego-hook.sh} $out \ + --prefix PATH : ${lib.makeBinPath [ pkgs.curl pkgs.bash ]} + ''; in { options.services.bitvend = { @@ -56,6 +61,11 @@ in { default = "vending.waw.hackerspace.pl"; description = "hostname"; }; + acmeToken = mkOption { + type = types.str; + default = ""; + description = "Let's Encrypt proxy API authentication token"; + }; }; config = mkIf cfg.enable { ids.uids.bitvend = 2137; @@ -67,6 +77,7 @@ in { uid = config.ids.uids.bitvend; description = "Bitvend daemon user"; home = cfg.stateDir; + extraGroups = [ "dialout" ]; }; users.groups.bitvend = { name = bitvendGroup; @@ -88,12 +99,29 @@ in { "d '${cfg.stateDir}' 0750 '${bitvendUser}' '${bitvendGroup}' - -" ]; networking.firewall.allowedTCPPorts = [ 80 443 ]; + + security.acme.acceptTerms = true; + security.acme.email = "informatic@hackerspace.pl"; + security.acme.certs."${cfg.hostName}" = { + dnsProvider = "exec"; + dnsPropagationCheck = false; + webroot = lib.mkForce null; + credentialsFile = pkgs.writeText "acme-creds" '' +EXEC_PATH=${legoHook} +API_URL=https://ns1-waw.hackerspace.pl +API_TOKEN=${cfg.acmeToken} + ''; + }; + services.nginx = { enable = true; + recommendedProxySettings = true; appendHttpConfig = '' proxy_cache_path /tmp/nginx-cache levels=1:2 keys_zone=qrcode_cache:10m max_size=50m inactive=60m; ''; virtualHosts."${cfg.hostName}" = { + forceSSL = true; + enableACME = true; locations."/" = { proxyPass = "http://127.0.0.1:5000"; };