diff options
| author | Shyam Sunder <sgsunder1@gmail.com> | 2018-07-11 23:30:13 -0400 |
|---|---|---|
| committer | rr- <rr-@sakuya.pl> | 2018-07-25 13:39:57 +0200 |
| commit | 6a6c4dc822344df01af0b3849083bc3ceda148e9 (patch) | |
| tree | 3a05c302814485a686e07f5db8cf825e5ab139c7 /server | |
| parent | 9730aa5c050352736327a992f9a428b6bc82cd75 (diff) | |
build: add Docker functionality and documentation
Diffstat (limited to 'server')
| -rw-r--r-- | server/.dockerignore | 8 | ||||
| -rw-r--r-- | server/Dockerfile | 46 | ||||
| -rw-r--r-- | server/config.yaml.dist | 144 | ||||
| -rw-r--r-- | server/szurubooru/config.py | 32 | ||||
| -rwxr-xr-x | server/wait-for-es | 33 |
5 files changed, 263 insertions, 0 deletions
diff --git a/server/.dockerignore b/server/.dockerignore new file mode 100644 index 0000000..a20ffa9 --- /dev/null +++ b/server/.dockerignore @@ -0,0 +1,8 @@ +szurubooru/tests/* +setup.cfg +.pylintrc +mypi.ini + +Dockerfile +.dockerignore +**/.gitignore diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..3aa5ab3 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,46 @@ +FROM scratch as approot +WORKDIR /opt/app + +COPY alembic.ini wait-for-es generate-thumb ./ +COPY szurubooru/ ./szurubooru/ +COPY config.yaml.dist ../ + + +FROM python:3.6-slim +WORKDIR /opt/app + +ARG PUID=1000 +ARG PGID=1000 +ARG PORT=6666 +RUN \ + # Set users + mkdir -p /opt/app /data && \ + groupadd -g ${PGID} app && \ + useradd -d /opt/app -M -c '' -g app -r -u ${PUID} app && \ + chown -R app:app /opt/app /data && \ + # Create init file + echo "#!/bin/sh" >> /init && \ + echo "set -e" >> /init && \ + echo "cd /opt/app" >> /init && \ + echo "./wait-for-es" >> /init && \ + echo "alembic upgrade head" >> /init && \ + echo "exec waitress-serve --port ${PORT} szurubooru.facade:app" \ + >> /init && \ + chmod a+x /init && \ + # Install ffmpeg + apt-get -yqq update && \ + apt-get -yq install --no-install-recommends ffmpeg && \ + rm -rf /var/lib/apt/lists/* && \ + # Install waitress + pip3 install --no-cache-dir waitress + +COPY --chown=app:app requirements.txt ./requirements.txt +RUN pip3 install --no-cache-dir -r ./requirements.txt + +# done to minimize number of layers in final image +COPY --chown=app:app --from=approot / / + +VOLUME ["/data/"] +EXPOSE ${PORT} +USER app +CMD ["/init"] diff --git a/server/config.yaml.dist b/server/config.yaml.dist new file mode 100644 index 0000000..8a3a8b0 --- /dev/null +++ b/server/config.yaml.dist @@ -0,0 +1,144 @@ +# rather than editing this file, it is strongly suggested to create config.yaml +# and override only what you need. + +# shown in the website title and on the front page +name: szurubooru +# user agent name used to download files from the web on behalf of the api users +user_agent: +# used to salt the users' password hashes +secret: change +# required for running the test suite +test_database: 'sqlite:///:memory:' + +# Delete thumbnails and source files on post delete +# Original functionality is no, to mitigate the impacts of admins going +# on unchecked post purges. +delete_source_files: no + +thumbnails: + avatar_width: 300 + avatar_height: 300 + post_width: 300 + post_height: 300 + +convert: + gif: + to_webm: false + to_mp4: false + +# used to send password reset e-mails +smtp: + host: # example: localhost + port: # example: 25 + user: # example: bot + pass: # example: groovy123 + # host can be left empty, in which case it is recommended to fill contactEmail. + +contact_email: # example: bob@example.com. Meant for manual password reset procedures + +enable_safety: yes + +tag_name_regex: ^\S+$ +tag_category_name_regex: ^[^\s%+#/]+$ + +# don't make these more restrictive unless you want to annoy people; if you do +# customize them, make sure to update the instructions in the registration form +# template as well. +password_regex: '^.{5,}$' +user_name_regex: '^[a-zA-Z0-9_-]{1,32}$' + +default_rank: regular + +privileges: + 'users:create:self': anonymous # Registration permission + 'users:create:any': administrator + 'users:list': regular + 'users:view': regular + 'users:edit:any:name': moderator + 'users:edit:any:pass': moderator + 'users:edit:any:email': moderator + 'users:edit:any:avatar': moderator + 'users:edit:any:rank': moderator + 'users:edit:self:name': regular + 'users:edit:self:pass': regular + 'users:edit:self:email': regular + 'users:edit:self:avatar': regular + 'users:edit:self:rank': moderator # one can't promote themselves or anyone to upper rank than their own. + 'users:delete:any': administrator + 'users:delete:self': regular + + 'user_tokens:list:any': administrator + 'user_tokens:list:self': regular + 'user_tokens:create:any': administrator + 'user_tokens:create:self': regular + 'user_tokens:edit:any': administrator + 'user_tokens:edit:self': regular + 'user_tokens:delete:any': administrator + 'user_tokens:delete:self': regular + + 'posts:create:anonymous': regular + 'posts:create:identified': regular + 'posts:list': anonymous + 'posts:reverse_search': regular + 'posts:view': anonymous + 'posts:view:featured': anonymous + 'posts:edit:content': power + 'posts:edit:flags': regular + 'posts:edit:notes': regular + 'posts:edit:relations': regular + 'posts:edit:safety': power + 'posts:edit:source': regular + 'posts:edit:tags': regular + 'posts:edit:thumbnail': power + 'posts:feature': moderator + 'posts:delete': moderator + 'posts:score': regular + 'posts:merge': moderator + 'posts:favorite': regular + 'posts:bulk-edit:tags': power + 'posts:bulk-edit:safety': power + + 'tags:create': regular + 'tags:edit:names': power + 'tags:edit:category': power + 'tags:edit:description': power + 'tags:edit:implications': power + 'tags:edit:suggestions': power + 'tags:list': regular + 'tags:view': anonymous + 'tags:merge': moderator + 'tags:delete': moderator + + 'tag_categories:create': moderator + 'tag_categories:edit:name': moderator + 'tag_categories:edit:color': moderator + 'tag_categories:list': anonymous + 'tag_categories:view': anonymous + 'tag_categories:delete': moderator + 'tag_categories:set_default': moderator + + 'comments:create': regular + 'comments:delete:any': moderator + 'comments:delete:own': regular + 'comments:edit:any': moderator + 'comments:edit:own': regular + 'comments:list': regular + 'comments:view': regular + 'comments:score': regular + + 'snapshots:list': power + + 'uploads:create': regular + +## ONLY SET THESE IF DEPLOYING OUTSIDE OF DOCKER +#debug: 0 # generate server logs? +#show_sql: 0 # show sql in server logs? +#data_url: /data/ +#data_dir: /var/www/data +## usage: schema://user:password@host:port/database_name +## example: postgres://szuru:dog@localhost:5432/szuru_test +#database: +#elasticsearch: # used for reverse image search +# host: localhost +# port: 9200 +# index: szurubooru diff --git a/server/szurubooru/config.py b/server/szurubooru/config.py index 567b457..3ddbea5 100644 --- a/server/szurubooru/config.py +++ b/server/szurubooru/config.py @@ -1,6 +1,7 @@ from typing import Dict import os import yaml +from szurubooru import errors def merge(left: Dict, right: Dict) -> Dict: @@ -15,12 +16,43 @@ def merge(left: Dict, right: Dict) -> Dict: return left +def docker_config() -> Dict: + for key in [ + 'POSTGRES_USER', + 'POSTGRES_PASSWORD', + 'POSTGRES_HOST', + 'ESEARCH_HOST' + ]: + if not os.getenv(key, False): + raise errors.ConfigError(f'Environment variable "{key}" not set') + return { + 'debug': True, + 'show_sql': int(os.getenv('LOG_SQL', 0)), + 'data_url': os.getenv('DATA_URL', '/data/'), + 'data_dir': '/data/', + 'database': 'postgres://%(user)s:%(pass)s@%(host)s:%(port)d/%(db)s' % { + 'user': os.getenv('POSTGRES_USER'), + 'pass': os.getenv('POSTGRES_PASSWORD'), + 'host': os.getenv('POSTGRES_HOST'), + 'port': int(os.getenv('POSTGRES_PORT', 5432)), + 'db': os.getenv('POSTGRES_DB', os.getenv('POSTGRES_USER')) + }, + 'elasticsearch': { + 'host': os.getenv('ESEARCH_HOST'), + 'port': int(os.getenv('ESEARCH_PORT', 9200)), + 'index': os.getenv('ESEARCH_INDEX', 'szurubooru') + } + } + + def read_config() -> Dict: with open('../config.yaml.dist') as handle: ret = yaml.load(handle.read()) if os.path.exists('../config.yaml'): with open('../config.yaml') as handle: ret = merge(ret, yaml.load(handle.read())) + if os.path.exists('/.dockerenv'): + ret = merge(ret, docker_config()) return ret diff --git a/server/wait-for-es b/server/wait-for-es new file mode 100755 index 0000000..6855c75 --- /dev/null +++ b/server/wait-for-es @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +''' +Docker helper script. Blocks until the ElasticSearch service is ready. +''' +import logging +import time +import elasticsearch +from szurubooru import config, errors + + +def main(): + print('Looking for ElasticSearch connection...') + logging.basicConfig(level=logging.ERROR) + es = elasticsearch.Elasticsearch([{ + 'host': config.config['elasticsearch']['host'], + 'port': config.config['elasticsearch']['port'], + }]) + + TIMEOUT = 30 + DELAY = 0.1 + for _ in range(int(TIMEOUT / DELAY)): + try: + es.cluster.health(wait_for_status='yellow') + print('Connected to ElasticSearch!') + return + except Exception: + time.sleep(DELAY) + pass + raise errors.ThirdPartyError('Error connecting to ElasticSearch') + + +if __name__ == '__main__': + main() |