|
|
|
# py-push-server
|
|
|
|
|
|
|
|
## Docker Build & Deploy
|
|
|
|
|
|
|
|
```
|
|
|
|
export PUSH_SERVER_VERSION=0.1
|
|
|
|
|
|
|
|
# This command is also in build.sh
|
|
|
|
docker build --tag py-push-server:amd-$PUSH_SERVER_VERSION --platform linux/amd64 .
|
|
|
|
|
|
|
|
docker save -o ~/dl/py-push-server-amd-$PUSH_SERVER_VERSION.tar py-push-server:amd-$PUSH_SERVER_VERSION
|
|
|
|
|
|
|
|
bzip2 ~/dl/py-push-server-amd-$PUSH_SERVER_VERSION.tar
|
|
|
|
```
|
|
|
|
|
|
|
|
... and then on the server after transferring that file:
|
|
|
|
|
|
|
|
```
|
|
|
|
export PUSH_SERVER_VERSION=0.1
|
|
|
|
|
|
|
|
bzip2 -d py-push-server-amd-$PUSH_SERVER_VERSION.tar.bz2
|
|
|
|
|
|
|
|
sudo docker load -i py-push-server-amd-$PUSH_SERVER_VERSION.tar
|
|
|
|
|
|
|
|
sudo docker run -d -p 8900:3000 -v ~/py-push-server-db:/srv/py-push-server/data --name py-push-server-$PUSH_SERVER_VERSION py-push-server:amd-$PUSH_SERVER_VERSION
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Docker Compose & HAProxy Setup
|
|
|
|
|
|
|
|
|
|
|
|
On first run you need to:
|
|
|
|
|
|
|
|
`docker network create phoenix-network`
|
|
|
|
|
|
|
|
|
|
|
|
### HAProxy setup
|
|
|
|
|
|
|
|
... in docker-compose.yml ...
|
|
|
|
|
|
|
|
```
|
|
|
|
default-backend:
|
|
|
|
container_name: 'default-backend'
|
|
|
|
image: nginx:1.22.0-alpine
|
|
|
|
volumes:
|
|
|
|
- /docker-volumes/haproxy-config/core/nginx/html:/usr/share/nginx/html
|
|
|
|
restart: always
|
|
|
|
networks:
|
|
|
|
- phoenix-network
|
|
|
|
|
|
|
|
rsyslog:
|
|
|
|
container_name: 'rsyslog'
|
|
|
|
hostname: 'rsyslog'
|
|
|
|
image: alpine-rsyslog
|
|
|
|
build:
|
|
|
|
context: ./alpine-rsyslog
|
|
|
|
volumes:
|
|
|
|
- $PWD/haproxy-config/core/haproxy.conf:/etc/rsyslog.d/haproxy.conf
|
|
|
|
- $PWD/haproxy-config/log:/var/log
|
|
|
|
- $PWD/haproxy-config/spool:/var/spool
|
|
|
|
- $PWD/rsyslog/rsyslog.conf:/etc/rsyslog.conf
|
|
|
|
ports:
|
|
|
|
- '127.0.0.1:514:514'
|
|
|
|
networks:
|
|
|
|
- phoenix-network
|
|
|
|
|
|
|
|
|
|
|
|
haproxy:
|
|
|
|
container_name: 'haproxy'
|
|
|
|
hostname: 'haproxy'
|
|
|
|
image: haproxytech/haproxy-alpine:latest
|
|
|
|
ports:
|
|
|
|
- 443:443
|
|
|
|
- 80:80
|
|
|
|
depends_on:
|
|
|
|
- default-backend
|
|
|
|
- rsyslog
|
|
|
|
volumes:
|
|
|
|
- $PWD/haproxy-config/log:/var/log
|
|
|
|
- $PWD/haproxy-config/certs:/usr/local/etc/haproxy/certs:ro
|
|
|
|
- $PWD/haproxy-config/core:/usr/local/etc/haproxy/core:ro
|
|
|
|
- $PWD/haproxy-config/maps:/usr/local/etc/haproxy/maps:ro
|
|
|
|
- $PWD/haproxy-config/sites:/usr/local/etc/haproxy/sites:ro
|
|
|
|
command: "haproxy -f /usr/local/etc/haproxy/core/haproxy.cfg -f /usr/local/etc/haproxy/sites/"
|
|
|
|
networks:
|
|
|
|
- phoenix-network
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
in `haproxy-config/core/haproxy.conf`:
|
|
|
|
|
|
|
|
```
|
|
|
|
$UDPServerAddress 127.0.0.1
|
|
|
|
$UDPServerRun 514
|
|
|
|
local0.* /var/log/haproxy.log
|
|
|
|
& ~
|
|
|
|
```
|
|
|
|
|
|
|
|
in `haproxy-config/core/haproxy.cfg`:
|
|
|
|
|
|
|
|
```
|
|
|
|
global
|
|
|
|
tune.ssl.default-dh-param 2048
|
|
|
|
log rsyslog:514 local0
|
|
|
|
maxconn 4096
|
|
|
|
|
|
|
|
defaults
|
|
|
|
option httplog
|
|
|
|
option forwardfor except 127.0.0.1
|
|
|
|
option forwardfor header X-Real-IP
|
|
|
|
option http-no-delay
|
|
|
|
log global
|
|
|
|
mode http
|
|
|
|
|
|
|
|
retries 10
|
|
|
|
option redispatch
|
|
|
|
timeout connect 4000
|
|
|
|
timeout client 600000
|
|
|
|
timeout server 600000
|
|
|
|
timeout queue 10s
|
|
|
|
|
|
|
|
frontend default_frontend
|
|
|
|
mode http
|
|
|
|
|
|
|
|
bind *:80
|
|
|
|
bind *:443 ssl crt /usr/local/etc/haproxy/certs alpn h2,http/1.1
|
|
|
|
|
|
|
|
# redirect non-www
|
|
|
|
|
|
|
|
# http-request redirect prefix https://%[hdr(Host),regsub(^www\.,,i)] code 301 if { hdr_beg(host) -i www. }
|
|
|
|
|
|
|
|
# Make a rule that the server cannot be directly accessed by IP address
|
|
|
|
|
|
|
|
acl has_domain hdr(Host),map_str(/usr/local/etc/haproxy/maps/domains.map) -m found
|
|
|
|
http-request deny if !has_domain
|
|
|
|
|
|
|
|
# ACME challenge rule?
|
|
|
|
|
|
|
|
acl letsencrypt-acl path_beg /.well-known/acme-challenge/
|
|
|
|
redirect code 301 scheme https if !{ ssl_fc && letsencrypt-acl }
|
|
|
|
compression algo gzip
|
|
|
|
compression type text/css text/html text/javascript application/javascript text/plain text/xml application/json image/svg+xml
|
|
|
|
acl is_content_type_html res.hdr(Content-Type) -i text/html
|
|
|
|
http-response set-header Content-Type text/html;\ charset=UTF-8 if is_content_type_html
|
|
|
|
http-response set-header Cache-Control no-cache,\ max-age=31536000
|
|
|
|
http-response set-header Expires %[date(3600),http_date]
|
|
|
|
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
|
|
|
http-response set-header X-XSS-Protection "1; mode=block"
|
|
|
|
http-response set-header X-Content-Type-Options "nosniff"
|
|
|
|
http-response set-header Referrer-Policy "strict-origin-when-cross-origin"
|
|
|
|
use_backend %[base,lower,regsub(^www\.,,i),map_beg(/usr/local/etc/haproxy/maps/sites.map,default_backend)]
|
|
|
|
|
|
|
|
|
|
|
|
listen stats
|
|
|
|
bind *:9999
|
|
|
|
mode http
|
|
|
|
log global
|
|
|
|
stats enable
|
|
|
|
stats realm Haproxy\ Statistics
|
|
|
|
stats uri /haproxy_stats
|
|
|
|
stats hide-version
|
|
|
|
|
|
|
|
|
|
|
|
backend haproxy_stats_backend
|
|
|
|
http-request auth realm haproxy-stats unless { http_auth_group(basic-auth-list) is-haproxy-stats }
|
|
|
|
mode http
|
|
|
|
compression algo gzip
|
|
|
|
compression offload
|
|
|
|
server server_nginx localhost:9999
|
|
|
|
|
|
|
|
|
|
|
|
userlist basic-auth-list
|
|
|
|
group is-guest
|
|
|
|
group is-haproxy-stats
|
|
|
|
|
|
|
|
user guest password $5$N7CpS0mo$FyJtlwQOwzAi5HnCISumyBKWyPu6DhBO7eGzUUyWoJ7 groups is-guest
|
|
|
|
```
|
|
|
|
|
|
|
|
... in `haproxy-config/sites/web-push.anomalistlabs.com.cfg` ...
|
|
|
|
|
|
|
|
define an HAProxy backend and map it to the Docker host and port
|
|
|
|
NOTE: this also turned off CORS origin rule
|
|
|
|
```
|
|
|
|
backend web_push_backend
|
|
|
|
mode http
|
|
|
|
compression algo gzip
|
|
|
|
compression offload
|
|
|
|
http-response set-header Access-Control-Allow-Origin "*"
|
|
|
|
server server_nginx endorser-push-server:3000
|
|
|
|
```
|
|
|
|
|
|
|
|
... in `haproxy-config/maps/domains.map` ...
|
|
|
|
|
|
|
|
add a domain that will be used as a base
|
|
|
|
|
|
|
|
```
|
|
|
|
timesafari.anomalistlabs.com
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
... in `haproxy-config/maps/sites.map` ...
|
|
|
|
|
|
|
|
map the `/web-push` path to the `web_push_backend`
|
|
|
|
NOTE: `timesafari-pwa.anomalistlabs.com` PWA sits on the root
|
|
|
|
|
|
|
|
```
|
|
|
|
timesafari-pwa.anomalistlabs.com/web-push/ web_push_backend
|
|
|
|
```
|
|
|
|
|
|
|
|
### The rest ..
|
|
|
|
|
|
|
|
`docker-compose up -d` should just work :-)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Run the server outside Docker
|
|
|
|
|
|
|
|
Run the app:
|
|
|
|
|
|
|
|
```commandline
|
|
|
|
sh <(curl https://pkgx.sh) +python.org +virtualenv.pypa.io sh
|
|
|
|
|
|
|
|
pip install gunicorn
|
|
|
|
|
|
|
|
source venv/bin/activate
|
|
|
|
|
|
|
|
gunicorn -b 0.0.0.0:3000 --log-level=debug --workers=3 app:app
|
|
|
|
|
|
|
|
# ... and see the results in a browser: http://localhost:3000/web-push/vapid
|
|
|
|
|
|
|
|
# See Troubleshooting below if that doesn't work out of the box.
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Run a test:
|
|
|
|
|
|
|
|
```commandline
|
|
|
|
python webpush.py
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Run haproxy (on a Mac):
|
|
|
|
|
|
|
|
* Create "haproxy-config" directory for those files above, eg. /usr/local/etc/haproxy
|
|
|
|
|
|
|
|
* Comment out the `log rsyslog` and `bind *:443` lines in /usr/local/etc/haproxy/haproxy.cfg and then run:
|
|
|
|
|
|
|
|
`haproxy -f /usr/local/etc/haproxy/haproxy.cfg`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Troubleshooting
|
|
|
|
|
|
|
|
* If you get "no such table: vapid_key" then your file pointers are probably wrong.
|
|
|
|
Check that the "docker run" mounted volume matches the SQLALCHEMY_DATABASE_URI in the app.py file.
|
|
|
|
|
|
|
|
* If you get "unable to open database file", you can provide the app.py with
|
|
|
|
SQLALCHEMY_DATABASE_URI with `sqlite:////...` with the full path to the data/webpush.db file.
|
|
|
|
(Why does the relative path of `sqlite:///...` not work for a relative path?)
|