diff --git a/Dockerfile b/Dockerfile index ea414d4..805f638 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,32 +21,28 @@ RUN apt-get update -qq && \ COPY ./requirements.txt /etc/requirements.txt RUN pip3 install -q -r /etc/requirements.txt gunicorn -# Nginx configuration -RUN echo "daemon off;" >> /etc/nginx/nginx.conf -COPY /etc/nginx.conf /etc/nginx/conf.d/nginx.conf - # Copy our static content in place COPY apprise_api/static /usr/share/nginx/html/s/ -# Supervisor configuration -COPY /etc/supervisord.conf /etc/supervisor/conf.d/supervisord.conf - # set work directory WORKDIR /opt/apprise # Copy over Apprise API COPY apprise_api/ webapp -# Change port of gunicorn -RUN sed -i -e 's/:8000/:8080/g' /opt/apprise/webapp/gunicorn.conf.py - # Cleanup RUN apt-get remove -y -qq build-essential libffi-dev libssl-dev python-dev && \ apt-get clean autoclean && \ apt-get autoremove --yes && \ rm -rf /var/lib/{apt,dpkg,cache,log}/ -EXPOSE 8000 -VOLUME /config +# Configuration Permissions (to run nginx as a non-root user) +RUN umask 0002 && \ + mkdir -p /config /run/apprise && \ + chown www-data:www-data -R /run/apprise /var/lib/nginx /config -CMD ["/usr/bin/supervisord"] +# Handle running as a non-root user (www-data is id/gid 33) +USER www-data +VOLUME /config +EXPOSE 8000 +CMD ["/usr/bin/supervisord", "-c", "/opt/apprise/webapp/etc/supervisord.conf"] diff --git a/README.md b/README.md index f72543e..a68d4ff 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Take advantage of [Apprise](https://github.com/caronc/apprise) through your network with a user-friendly API. -- Send notifications to more then 65+ services. +- Send notifications to more then 70+ services. - An incredibly lightweight gateway to Apprise. - A production ready micro-service at your disposal. @@ -51,6 +51,7 @@ A `docker-compose.yml` file is already set up to grant you an instant production docker-compose up ``` + ### Config Directory Permissions Under the hood, An NginX services is reading/writing your configuration files as the user (and group) `www-data` which generally has the id of `33`. In preparation so that you don't get the error: `An error occured saving configuration.` consider also setting up your local `/var/lib/apprise/config` permissions as: diff --git a/apprise_api/api/tests/test_add.py b/apprise_api/api/tests/test_add.py index 2e79109..1f22912 100644 --- a/apprise_api/api/tests/test_add.py +++ b/apprise_api/api/tests/test_add.py @@ -107,6 +107,19 @@ class AddTests(SimpleTestCase): ) assert response.status_code == 400 + # Test the handling of underlining disk/write exceptions + with patch('os.makedirs') as mock_mkdirs: + mock_mkdirs.side_effect = OSError() + # We'll fail to write our key now + response = self.client.post( + '/add/{}'.format(key), + data=json.dumps({'urls': 'mailto://user:pass@yahoo.ca'}), + content_type='application/json', + ) + + # internal errors are correctly identified + assert response.status_code == 500 + # Test the handling of underlining disk/write exceptions with patch('gzip.open') as mock_open: mock_open.side_effect = OSError() diff --git a/apprise_api/api/utils.py b/apprise_api/api/utils.py index 3a78f10..b9570bc 100644 --- a/apprise_api/api/utils.py +++ b/apprise_api/api/utils.py @@ -36,7 +36,7 @@ from django.conf import settings import logging # Get an instance of a logger -logger = logging.getLogger(__name__) +logger = logging.getLogger('django') class AppriseStoreMode(object): @@ -120,7 +120,13 @@ class AppriseConfigCache(object): # First two characters are reserved for cache level directory writing. path, filename = self.path(key) - os.makedirs(path, exist_ok=True) + try: + os.makedirs(path, exist_ok=True) + + except OSError: + # Permission error + logger.error('Could not create directory {}'.format(path)) + return False # Write our file to a temporary file _, tmp_path = tempfile.mkstemp(suffix='.tmp', dir=path) diff --git a/apprise_api/etc/nginx.conf b/apprise_api/etc/nginx.conf new file mode 100644 index 0000000..3862c11 --- /dev/null +++ b/apprise_api/etc/nginx.conf @@ -0,0 +1,73 @@ +daemon off; +worker_processes auto; +pid /run/apprise/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections 768; +} + +http { + ## + # Basic Settings + ## + sendfile on; + tcp_nopush on; + types_hash_max_size 2048; + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ## + # SSL Settings + ## + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE + ssl_prefer_server_ciphers on; + + ## + # Logging Settings + ## + access_log /dev/stdout; + error_log /dev/stdout info; + + ## + # Gzip Settings + ## + gzip on; + + ## + # Host Configuration + ## + client_body_buffer_size 256k; + client_body_in_file_only off; + + server { + listen 8000; + + # Main Website + location / { + include /etc/nginx/uwsgi_params; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://localhost:8080; + # Give ample time for notifications to fire + proxy_read_timeout 120s; + } + + # Static Content + location /s/ { + root /usr/share/nginx/html; + index index.html; + } + + # 404 error handling + error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +} diff --git a/etc/supervisord.conf b/apprise_api/etc/supervisord.conf similarity index 57% rename from etc/supervisord.conf rename to apprise_api/etc/supervisord.conf index c91c786..7cf31a9 100644 --- a/etc/supervisord.conf +++ b/apprise_api/etc/supervisord.conf @@ -1,16 +1,19 @@ [supervisord] nodaemon=true +pidfile=/run/apprise/supervisord.pid +logfile=/dev/null +logfile_maxbytes=0 +user=www-data [program:nginx] -command=/usr/sbin/nginx +command=/usr/sbin/nginx -c /opt/apprise/webapp/etc/nginx.conf stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 [program:gunicorn] -command=gunicorn -c /opt/apprise/webapp/gunicorn.conf.py --worker-tmp-dir /dev/shm core.wsgi -user=www-data +command=gunicorn -c /opt/apprise/webapp/gunicorn.conf.py -b :8080 --worker-tmp-dir /dev/shm core.wsgi directory=/opt/apprise/webapp stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 diff --git a/docker-compose.yml b/docker-compose.yml index b832ce1..2e9b89f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,4 +5,13 @@ services: build: . container_name: apprise ports: - - 8000:8000 \ No newline at end of file + - 8000:8000 + user: "www-data:www-data" + volumes: + - ./apprise_api:/opt/apprise/webapp:ro + # if uncommenting the below, you will need to type the following + # otherwise the django instance won't have permissions to write + # to the directory correctly: + # $> chown -R 33:33 ./config + # $> chmod -R 775 ./config + # - ./config:/config:rw diff --git a/etc/nginx.conf b/etc/nginx.conf deleted file mode 100644 index 13b44b2..0000000 --- a/etc/nginx.conf +++ /dev/null @@ -1,30 +0,0 @@ -server { - listen 8000; - - # Main Website - location / { - include uwsgi_params; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://localhost:8080; - # Give ample time for notifications to fire - proxy_read_timeout 120s; - } - - # Static Content - location /s/ { - root /usr/share/nginx/html; - index index.html; - } - - # 404 error handling - error_page 404 /404.html; - - # redirect server error pages to the static page /50x.html - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } -} \ No newline at end of file