Fix up a few more things, add a Dockerfile and a local docker-compose deployment

master
Raoul Snyman 2021-11-06 22:39:54 -07:00
parent 14fcfff656
commit 9f38029f62
11 changed files with 190 additions and 25 deletions

13
Dockerfile 100644
View File

@ -0,0 +1,13 @@
FROM python:3
RUN useradd --system --gid root --uid 1001 uwsgi
RUN pip install uwsgi psycopg2_binary
RUN mkdir -p /app
COPY . /app
WORKDIR /app
RUN pip install .
EXPOSE 3031
CMD ["uwsgi", "--master", "--workers", "4", "--socket", "0.0.0.0:3031", "--protocol", "uwsgi", \
"--plugin", "python3", "--uid", "uwsgi", "--wsgi", "libertywiki.wsgi"]

View File

@ -0,0 +1,21 @@
version: '3'
services:
wiki:
build:
context: .
environment:
- SQLALCHEMY_DATABASE_URI=postgresql://wiki:wiki@postgres/wiki
- SECRET_KEY=super-secret-key-replace-me
- SECURITY_PASSWORD_SALT=password-salt-replace-me
postgres:
image: postgres:14
environment:
- POSTGRES_USER=wiki
- POSTGRES_PASSWORD=wiki
- POSTGRES_DB=wiki
nginx:
image: nginx
volumes:
- "./nginx.conf:/etc/nginx/conf.d/default.conf:ro"
ports:
- "8000:80"

View File

@ -4,7 +4,7 @@ from flask import current_app, request
from flask_login import current_user
from flask_login.config import EXEMPT_METHODS
from libertywiki.utils import AccessType, WikiMode
from libertywiki.utils import WikiMode, needs_authentication
def check_access(access_type):
@ -21,12 +21,10 @@ def check_access(access_type):
if request.method in EXEMPT_METHODS or \
current_app.config.get('LOGIN_DISABLED'):
pass
elif (wiki_mode == WikiMode.PRIVATE
or (wiki_mode == WikiMode.PUBLIC and access_type == AccessType.READ)) \
elif needs_authentication(wiki_mode, access_type) \
and not current_user.is_authenticated:
return current_app.login_manager.unauthorized()
try:
# current_app.ensure_sync available in Flask >= 2.0
return current_app.ensure_sync(func)(*args, **kwargs)
except AttributeError:
return func(*args, **kwargs)

View File

@ -0,0 +1,104 @@
{% import 'admin/layout.html' as layout with context -%}
{% import 'admin/static.html' as admin_static with context %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% if admin_view.category %}{{ admin_view.category }} - {% endif %}{{ admin_view.name }} - {{ admin_view.admin.name }}{% endblock %}</title>
{% block head_meta %}
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
{% endblock %}
{% block head_css %}
<link href="{{ admin_static.url(filename='bootstrap/bootstrap4/swatch/{swatch}/bootstrap.min.css'.format(swatch=config.get('FLASK_ADMIN_SWATCH', 'default')), v='4.2.1') }}"
rel="stylesheet">
{% if config.get('FLASK_ADMIN_SWATCH', 'default') == 'default' %}
<link href="{{ admin_static.url(filename='bootstrap/bootstrap4/css/bootstrap.min.css', v='4.2.1') }}" rel="stylesheet">
{% endif %}
<link href="{{ admin_static.url(filename='admin/css/bootstrap4/admin.css', v='1.1.1') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='bootstrap/bootstrap4/css/font-awesome.min.css', v='4.7.0') }}" rel="stylesheet">
{% if admin_view.extra_css %}
{% for css_url in admin_view.extra_css %}
<link href="{{ css_url }}" rel="stylesheet">
{% endfor %}
{% endif %}
<style>
.hide {
display: none;
}
</style>
{% endblock %}
{% block head %}
{% endblock %}
{% block head_tail %}
{% endblock %}
</head>
<body>
{% block page_body %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-2" role="navigation">
<div class="container{% if config.get('FLASK_ADMIN_FLUID_LAYOUT', False) %}-fluid{% endif %}">
<!-- Brand and toggle get grouped for better mobile display -->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#admin-navbar-collapse"
aria-controls="admin-navbar-collapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- navbar content -->
<div class="collapse navbar-collapse" id="admin-navbar-collapse">
{% block brand %}
<a class="navbar-brand" href="{{ admin_view.admin.url }}">{{ admin_view.admin.name }}</a>
{% endblock %}
{% block main_menu %}
<ul class="navbar-nav mr-auto">
{{ layout.menu() }}
</ul>
{% endblock %}
{% block menu_links %}
<ul class="nav navbar-nav navbar-right">
{{ layout.menu_links() }}
</ul>
{% endblock %}
{% block access_control %}
{% endblock %}
</div>
</div>
</nav>
<div class="container{% if config.get('FLASK_ADMIN_FLUID_LAYOUT', False) %}-fluid{% endif %}">
{% block messages %}
{{ layout.messages() }}
{% endblock %}
{# store the jinja2 context for form_rules rendering logic #}
{% set render_ctx = h.resolve_ctx() %}
{% block body %}{% endblock %}
</div>
{% endblock %}
{% block tail_js %}
<script src="{{ admin_static.url(filename='vendor/jquery.min.js', v='2.1.4') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='bootstrap/bootstrap4/js/popper.min.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='bootstrap/bootstrap4/js/bootstrap.min.js', v='4.2.1') }}"
type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/moment.min.js', v='2.9.0') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/bootstrap4/util.js', v='4.3.1') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/bootstrap4/dropdown.js', v='4.3.1') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/select2/select2.min.js', v='4.2.1') }}"
type="text/javascript"></script>
<script src="{{ admin_static.url(filename='vendor/multi-level-dropdowns-bootstrap/bootstrap4-dropdown-ml-hack.js') }}" type="text/javascript"></script>
<script src="{{ admin_static.url(filename='admin/js/helpers.js', v='1.0.0') }}" type="text/javascript"></script>
{% if admin_view.extra_js %}
{% for js_url in admin_view.extra_js %}
<script src="{{ js_url }}" type="text/javascript"></script>
{% endfor %}
{% endif %}
{% endblock %}
{% block tail %}
{% endblock %}
</body>
</html>

View File

@ -4,19 +4,8 @@
<div class="container">
<div class="row">
<div class="col-sm-10 col-sm-offset-1">
<h1>Flask-Admin example</h1>
<p class="lead">
Authentication
</p>
<p>
This example shows how you can use <a href="https://pythonhosted.org/Flask-Security/index.html" target="_blank">Flask-Security</a> for authentication.
</p>
<h1>LibertyWiki Administration</h1>
{% if not current_user.is_authenticated %}
<p>You can register as a regular user, or log in as a superuser with the following credentials:
<ul>
<li>email: <b>admin</b></li>
<li>password: <b>admin</b></li>
</ul>
<p>
<a class="btn btn-primary" href="{{ url_for('security.login') }}">login</a> <a class="btn btn-default" href="{{ url_for('security.register') }}">register</a>
</p>

View File

@ -5,8 +5,8 @@
<div class="navbar-text btn-group pull-right">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
<i class="glyphicon glyphicon-user"></i>
{% if current_user.first_name -%}
{{ current_user.first_name }}
{% if current_user.name -%}
{{ current_user.name }}
{% else -%}
{{ current_user.email }}
{%- endif %}<span class="caret"></span></a>

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Wiki</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.0/font/bootstrap-icons.css">
<style type="text/css">
body {
min-height: 75rem;
@ -18,8 +19,32 @@
<a class="navbar-brand d-flex" href="/">Wiki</a>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
<button class="btn btn-outline-light" type="submit">Search</button>
</form>
<ul class="navbar-nav">
{% if current_user.is_authenticated %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi-person-fill"></i>
{% if current_user.name -%}
{{ current_user.name }}
{% else -%}
{{ current_user.email }}
{%- endif %}
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
{% if current_user.has_role('admin') %}
<li><a class="dropdown-item" href="{{ url_for('admin.index') }}">Admin</a></li>
{% endif %}
<li><a class="dropdown-item" href="{{ url_for('security.logout') }}">Logout</a></li>
</ul>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" aria-current="page" href="{{ url_for('security.login') }}">Log in</a>
</li>
{% endif %}
</ul>
</div>
</nav>

View File

@ -1,6 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<article class="{{page.slug}}">
<h1 class="display-6">{{ page.title }}</h1>
<ul class="nav nav-tabs">
<li class="nav-item">
<span class="nav-link active" aria-current="page">Article</span>
@ -9,8 +10,7 @@
<a class="nav-link" href="/{{page.slug}}/edit">Edit</a>
</li>
</ul>
<h1 class="display-6 border-bottom">{{ page.title }}</h1>
<div class="row">
<div class="row mt-2">
<div class="col">
{{ page.body | rst2html | safe }}
</div>

View File

@ -1,7 +1,5 @@
from enum import Enum
from flask_bcrypt import Bcrypt
class AccessType(Enum):
READ = 1
@ -14,7 +12,10 @@ class WikiMode(Enum):
PRIVATE = 3
bcrypt = Bcrypt()
def needs_authentication(wiki_mode, access_type):
"""Determine if the access type needs authentication based on the wiki mode"""
return wiki_mode == WikiMode.PRIVATE \
or (wiki_mode == WikiMode.PUBLIC and access_type == AccessType.WRITE)
__all__ = ['AccessType', 'WikiMode', 'bcrypt']
__all__ = ['AccessType', 'WikiMode']

View File

@ -0,0 +1,4 @@
from libertywiki.app import get_app
application = get_app()

10
nginx.conf 100644
View File

@ -0,0 +1,10 @@
server {
listen 80;
listen [::]:80;
server_name _;
location / {
include uwsgi_params;
uwsgi_pass wiki:3031;
}
}