Fix up a few more things, add a Dockerfile and a local docker-compose deployment
parent
14fcfff656
commit
9f38029f62
|
@ -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"]
|
|
@ -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"
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
from libertywiki.app import get_app
|
||||
|
||||
|
||||
application = get_app()
|
|
@ -0,0 +1,10 @@
|
|||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
include uwsgi_params;
|
||||
uwsgi_pass wiki:3031;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue