mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
build: add Docker functionality and documentation
This commit is contained in:
8
server/.dockerignore
Normal file
8
server/.dockerignore
Normal file
@ -0,0 +1,8 @@
|
||||
szurubooru/tests/*
|
||||
setup.cfg
|
||||
.pylintrc
|
||||
mypi.ini
|
||||
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
**/.gitignore
|
46
server/Dockerfile
Normal file
46
server/Dockerfile
Normal file
@ -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"]
|
144
server/config.yaml.dist
Normal file
144
server/config.yaml.dist
Normal file
@ -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
|
@ -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
|
||||
|
||||
|
||||
|
33
server/wait-for-es
Executable file
33
server/wait-for-es
Executable file
@ -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()
|
Reference in New Issue
Block a user