Renamed + remote

This commit is contained in:
Yohan Boniface 2019-03-24 15:45:58 +01:00
parent a009948e08
commit 3216aeac11
53 changed files with 353 additions and 24 deletions

View file

@ -52,7 +52,7 @@ class Roll(Roll):
env = Environment(
loader=PackageLoader("kaba", "templates"), autoescape=select_autoescape(["kaba"])
loader=PackageLoader("copanier", "templates"), autoescape=select_autoescape(["copanier"])
)
@ -116,6 +116,7 @@ async def attach_request(request, response):
@app.listen("startup")
async def on_startup():
configure()
Delivery.init_fs()
@app.route("/sésame", methods=["GET"])
@ -340,7 +341,7 @@ def cli_wrapper():
def serve(reload=False):
"""Run a web server (for development only)."""
if reload:
hupper.start_reloader("kaba.serve")
hupper.start_reloader("copanier.serve")
traceback(app)
static(app, root=Path(__file__).parent / "static")
simple_server(app, port=2244)

View file

@ -16,7 +16,7 @@ FROM_EMAIL = "contact@epinamap.org"
def init():
for key, value in globals().items():
if key.isupper():
env_key = "KABA_" + key
env_key = "COPANIER_" + key
typ = type(value)
if env_key in os.environ:
globals()[key] = typ(os.environ[env_key])

View file

@ -139,6 +139,10 @@ class Delivery(Base):
def is_open(self):
return datetime.now().date() <= self.order_before.date()
@classmethod
def init_fs(cls):
cls.get_root().mkdir(parents=True, exist_ok=True)
@classmethod
def get_root(cls):
return Path(config.DATA_ROOT) / cls.__root__

View file

Before

Width:  |  Height:  |  Size: 758 KiB

After

Width:  |  Height:  |  Size: 758 KiB

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -17,7 +17,7 @@
<section class="notification {{ message[1] }}"><i class="icon-megaphone"></i> {{ message[0] }}</section>
{% endif %}
<section class="menu">
<h1><a href="/">Panio</a> <small>Les paniers piano d'Épinamap</small></h1>
<h1><a href="/">Copanier</a> <small>Les paniers piano d'Épinamap</small></h1>
<nav>
<a href="/livraison">Nouvelle livraison</a>
{% if request["user"] %}

199
remote/__main__.py Normal file
View file

@ -0,0 +1,199 @@
import os
import sys
from io import StringIO
import minicli
from usine import chown, config, connect, env, exists, mkdir, put, run, sudo, template
@minicli.cli
def pip(*command):
"""Run a pip command on the remote server.
"""
with sudo(user="copanier"):
run(f"/srv/copanier/venv/bin/pip {' '.join(command)}")
@minicli.cli
def system():
"""Setup the system."""
with sudo():
run("apt update")
run(
"apt install -y nginx git software-properties-common gcc "
"python3.7 python3.7-dev python3.7-venv pkg-config"
)
mkdir("/srv/copanier/logs")
run("useradd -N copanier -d /srv/copanier/ || exit 0")
chown("copanier:users", "/srv/copanier/")
run("chsh -s /bin/bash copanier")
@minicli.cli
def venv():
"""Setup the python virtualenv."""
path = "/srv/copanier/venv/"
if not exists(path):
with sudo(user="copanier"):
run(f"python3.7 -m venv {path}")
pip("install pip -U")
@minicli.cli
def http():
"""Configure Nginx and letsencrypt."""
# When we'll have a domain.
put("remote/nginx-snippet.conf", "/etc/nginx/snippets/copanier.conf")
put("remote/letsencrypt.conf", "/etc/nginx/snippets/letsencrypt.conf")
put("remote/ssl.conf", "/etc/nginx/snippets/ssl.conf")
domain = config.domains[0]
pempath = f"/etc/letsencrypt/live/{domain}/fullchain.pem"
with sudo():
if exists(pempath):
print(f"{pempath} found, using https configuration")
conf = template(
"remote/nginx-https.conf",
domains=" ".join(config.domains),
domain=domain,
)
else:
print(f"{pempath} not found, using http configuration")
# Before letsencrypt.
conf = template(
"remote/nginx-http.conf",
domains=" ".join(config.domains),
domain=domain,
)
put(conf, "/etc/nginx/sites-enabled/copanier.conf")
restart("nginx")
@minicli.cli
def letsencrypt():
"""Configure letsencrypt."""
with sudo():
run("add-apt-repository --yes ppa:certbot/certbot")
run("apt update")
run("apt install -y certbot")
mkdir("/var/www/letsencrypt/.well-known/acme-challenge")
domains = ",".join(list(config.domains))
certbot_conf = template("remote/certbot.ini", domains=domains)
put(certbot_conf, "/var/www/certbot.ini")
run("certbot certonly -c /var/www/certbot.ini --non-interactive " "--agree-tos")
@minicli.cli
def bootstrap():
"""Bootstrap the system."""
system()
venv()
service()
http()
@minicli.cli
def cli(command):
"""Run the copanier executable on the remote server.
"""
with sudo(user="copanier"), env(COPANIER_DATA_ROOT="/srv/copanier/data"):
run(f"/srv/copanier/venv/bin/copanier {command}")
@minicli.cli
def service():
"""Deploy/update the copanier systemd service."""
with sudo():
put("remote/copanier.service", "/etc/systemd/system/copanier.service")
systemctl("enable copanier.service")
@minicli.cli
def deploy():
"""Deploy/update the copanier code base."""
with sudo(user="copanier"):
put("remote/gunicorn.conf", "/srv/copanier/gunicorn.conf")
pip("install gunicorn")
base = "https://framagit.org/ybon/copanier"
pip(f"install -U git+{base}")
restart()
@minicli.cli
def restart(*services):
"""Restart the systemd services."""
services = services or ["copanier", "nginx"]
with sudo():
systemctl(f"restart {' '.join(services)}")
@minicli.cli
def systemctl(*args):
"""Run a systemctl command on the remote server.
:command: the systemctl command to run.
"""
run(f'systemctl {" ".join(args)}')
@minicli.cli
def logs(lines=50):
"""Display the copanier logs.
:lines: number of lines to retrieve
"""
with sudo():
run(f"journalctl --lines {lines} --unit copanier --follow")
@minicli.cli
def status():
"""Get the services status."""
systemctl("status nginx copanier")
@minicli.cli
def access_logs():
"""See the nginx access logs."""
with sudo():
run("tail -F /var/log/nginx/access.log")
@minicli.cli
def error_logs():
"""See the nginx error logs."""
with sudo():
run("tail -F /var/log/nginx/error.log")
@minicli.cli
def upload_env():
"""Upload environment vars to the server.
Use those to deal with info not commitable.
"""
vars_ = {
"COPANIER_DATA_ROOT": "/srv/copanier/data",
"COPANIER_SEND_MAILS": "1",
"COPANIER_SMTP_PASSWORD": None,
"COPANIER_SMTP_LOGIN": None,
}
content = ""
for key, value in vars_.items():
try:
content += "{}={}\n".format(key, value or os.environ[key])
except KeyError:
sys.exit(f"The {key} environment variable does not exist.")
path = "/srv/copanier/env"
if exists(path):
run(f"cat {path}")
put(StringIO(content), path)
@minicli.wrap
def wrapper(hostname, configpath):
with connect(hostname=hostname, configpath=configpath):
yield
if __name__ == "__main__":
minicli.run(hostname="qa", configpath="remote/config.yml")

4
remote/certbot.ini Normal file
View file

@ -0,0 +1,4 @@
authenticator = webroot
webroot-path = /var/www/letsencrypt
domains = $$domains
email = yohanboniface@free.fr

4
remote/config.yml Normal file
View file

@ -0,0 +1,4 @@
domains:
- copanier.epinamap.org
username: ubuntu
hostname: 185.145.251.38

17
remote/copanier.service Normal file
View file

@ -0,0 +1,17 @@
[Unit]
Description=COPANIER
After=syslog.target network.target
[Service]
Type=simple
User=copanier
ExecStart=/srv/copanier/venv/bin/gunicorn copanier:app --config gunicorn.conf
WorkingDirectory=/srv/copanier/
EnvironmentFile=/srv/copanier/env
Restart=always
RestartSec=5
StandardOutput=journal
SyslogIdentifier=COPANIER
[Install]
WantedBy=default.target

4
remote/gunicorn.conf Normal file
View file

@ -0,0 +1,4 @@
worker_class = 'roll.worker.Worker'
workers = 4
bind = 'unix:/tmp/copanier.sock'
log_level = 'debug'

5
remote/letsencrypt.conf Normal file
View file

@ -0,0 +1,5 @@
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/letsencrypt;
auth_basic off;
}

14
remote/nginx-http.conf Normal file
View file

@ -0,0 +1,14 @@
upstream copanier {
server unix:///tmp/copanier.sock;
}
server {
listen 80;
server_name $${domains};
charset utf-8;
client_max_body_size 1M;
include /etc/nginx/snippets/letsencrypt.conf;
include /etc/nginx/snippets/copanier.conf;
}

40
remote/nginx-https.conf Normal file
View file

@ -0,0 +1,40 @@
upstream copanier {
server unix:///tmp/copanier.sock;
}
server {
listen 80;
server_name $${domains};
include /etc/nginx/snippets/letsencrypt.conf;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
server_name $${domains};
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/$$domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$$domain/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/$$domain/fullchain.pem;
include /etc/nginx/snippets/ssl.conf;
charset utf-8;
client_max_body_size 1M;
include /etc/nginx/snippets/letsencrypt.conf;
include /etc/nginx/snippets/copanier.conf;
}

View file

@ -0,0 +1,6 @@
root /srv/copanier;
location / {
proxy_pass http://copanier/;
}

5
remote/ssl-renew Normal file
View file

@ -0,0 +1,5 @@
#!/bin/bash
certbot -c /srv/eurordis/certbot.ini renew
systemctl reload nginx

15
remote/ssl.conf Normal file
View file

@ -0,0 +1,15 @@
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2;
ssl_ciphers EECDH+AESGCM:EECDH+AES;
ssl_ecdh_curve secp384r1;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=15768000; includeSubdomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

4
requirements-dev.txt Normal file
View file

@ -0,0 +1,4 @@
hupper
pytest
pytest-asyncio
usine

7
requirements.txt Normal file
View file

@ -0,0 +1,7 @@
Jinja2==2.10
minicli==0.4.4
openpyxl==2.6.1
PyJWT==1.7.1
PyYAML==5.1
roll==0.10.1
ujson==1.35

View file

@ -1,7 +1,7 @@
[metadata]
name = kaba
name = copanier
version = 0.0.1
[options.entry_points]
console_scripts =
kaba = kaba:main
copanier = copanier:main

View file

@ -5,14 +5,14 @@ import pytest
from roll.extensions import traceback
from roll.testing import Client as BaseClient
from kaba import app as kaba_app
from kaba import config as kconfig
from kaba import create_token
from kaba.models import Delivery, Person
from copanier import app as copanier_app
from copanier import config as kconfig
from copanier import create_token
from copanier.models import Delivery, Person
def pytest_configure(config):
os.environ["KABA_DATA_ROOT"] = "tmp/db"
os.environ["COPANIER_DATA_ROOT"] = "tmp/db"
kconfig.init()
assert str(kconfig.DATA_ROOT) == "tmp/db"
@ -58,8 +58,8 @@ def client(app, event_loop):
@pytest.fixture
def app(): # Requested by Roll testing utilities.
traceback(kaba_app)
return kaba_app
traceback(copanier_app)
return copanier_app
@pytest.fixture

View file

@ -1,26 +1,26 @@
import os
from pathlib import Path
from kaba import config
from copanier import config
def test_config_should_read_from_env():
old_root = os.environ.get("KABA_DATA_ROOT", "")
old_secret = os.environ.get("KABA_SECRET", "")
old_root = os.environ.get("COPANIER_DATA_ROOT", "")
old_secret = os.environ.get("COPANIER_SECRET", "")
assert config.SECRET == "sikretfordevonly"
assert isinstance(config.DATA_ROOT, Path)
os.environ["KABA_DATA_ROOT"] = "changeme"
os.environ["KABA_SECRET"] = "ultrasecret"
os.environ["COPANIER_DATA_ROOT"] = "changeme"
os.environ["COPANIER_SECRET"] = "ultrasecret"
config.init()
assert config.SECRET == "ultrasecret"
assert isinstance(config.DATA_ROOT, Path)
assert str(config.DATA_ROOT) == "changeme"
if old_root:
os.environ["KABA_DATA_ROOT"] = old_root
os.environ["COPANIER_DATA_ROOT"] = old_root
else:
del os.environ["KABA_DATA_ROOT"]
del os.environ["COPANIER_DATA_ROOT"]
if old_secret:
os.environ["KABA_SECRET"] = old_secret
os.environ["COPANIER_SECRET"] = old_secret
else:
del os.environ["KABA_SECRET"]
del os.environ["COPANIER_SECRET"]
config.init()

View file

@ -2,7 +2,7 @@ from datetime import datetime, timedelta
import pytest
from kaba.models import Delivery, Product, Person, Order, ProductOrder
from copanier.models import Delivery, Product, Person, Order, ProductOrder
now = datetime.now

View file

@ -1,6 +1,6 @@
import pytest
from kaba.models import Delivery
from copanier.models import Delivery
pytestmark = pytest.mark.asyncio