summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorShyam Sunder <sgsunder1@gmail.com>2018-07-11 23:30:13 -0400
committerrr- <rr-@sakuya.pl>2018-07-25 13:39:57 +0200
commit6a6c4dc822344df01af0b3849083bc3ceda148e9 (patch)
tree3a05c302814485a686e07f5db8cf825e5ab139c7 /server
parent9730aa5c050352736327a992f9a428b6bc82cd75 (diff)
build: add Docker functionality and documentation
Diffstat (limited to 'server')
-rw-r--r--server/.dockerignore8
-rw-r--r--server/Dockerfile46
-rw-r--r--server/config.yaml.dist144
-rw-r--r--server/szurubooru/config.py32
-rwxr-xr-xserver/wait-for-es33
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()