Skip to content

Commit

Permalink
Healthcheck web improvements + PUID & PGID support added to Docker (#198
Browse files Browse the repository at this point in the history
)
  • Loading branch information
caronc committed Jun 30, 2024
1 parent 6e57e33 commit c6b9c11
Show file tree
Hide file tree
Showing 15 changed files with 387 additions and 105 deletions.
32 changes: 16 additions & 16 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.11-slim as base
FROM python:3.11-slim AS base

# set version label
ARG BUILD_DATE
Expand All @@ -7,13 +7,13 @@ LABEL build_version="Apprise API version:- ${VERSION} Build-date:- ${BUILD_DATE}
LABEL maintainer="Chris-Caron"

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV APPRISE_CONFIG_DIR /config
ENV APPRISE_ATTACH_DIR /attach
ENV APPRISE_PLUGIN_PATHS /plugin
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV APPRISE_CONFIG_DIR=/config
ENV APPRISE_ATTACH_DIR=/attach
ENV APPRISE_PLUGIN_PATHS=/plugin

FROM base as builder
FROM base AS builder

WORKDIR /build/

Expand Down Expand Up @@ -41,11 +41,11 @@ RUN set -eux && \
--no-binary cryptography \
cryptography

FROM base as runtime
FROM base AS runtime

# Install requirements and gunicorn
COPY ./requirements.txt /etc/requirements.txt
COPY --from=builder /build/*.whl .
COPY --from=builder /build/*.whl ./
RUN set -eux && \
echo "Installing cryptography" && \
pip3 install *.whl && \
Expand All @@ -55,6 +55,9 @@ RUN set -eux && \
apt-get update -qq && \
apt-get install -y -qq \
nginx && \
echo "Installing tools" && \
apt-get install -y -qq \
sed && \
echo "Cleaning up" && \
apt-get --yes autoremove --purge && \
apt-get clean --yes && \
Expand All @@ -73,16 +76,13 @@ WORKDIR /opt/apprise
# Copy over Apprise API
COPY apprise_api/ webapp

#
# # Configuration Permissions (to run nginx as a non-root user)
# Configuration Permissions (to run nginx as a non-root user)
RUN umask 0002 && \
mkdir -p /attach /config /plugin /run/apprise && \
chown www-data:www-data -R /run/apprise /var/lib/nginx /attach /config /plugin
touch /etc/nginx/server-override.conf && \
touch /etc/nginx/location-override.conf

# Handle running as a non-root user (www-data is id/gid 33)
USER www-data
VOLUME /config
VOLUME /attach
VOLUME /plugin
EXPOSE 8000
CMD ["/usr/local/bin/supervisord", "-c", "/opt/apprise/webapp/etc/supervisord.conf"]
CMD ["/opt/apprise/webapp/supervisord-startup"]
104 changes: 66 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,17 @@ docker pull caronc/apprise:latest
# setting APPRISE_STATEFUL_MODE to simple allows you to map your defined {key}
# straight to a file found in the `/config` path. In simple home configurations
# this is sometimes the ideal expectation.
#
# Set your User ID or Group ID if you wish to over-ride the default of 1000
# in the below example, we make sure it runs as the user we created the container as

docker run --name apprise \
-p 8000:8000 \
-v /var/lib/apprise/config:/config \
-v /var/lib/apprise/plugin:/plugin \
-v /var/lib/apprise/attach:/attach \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-v /path/to/local/config:/config \
-v /path/to/local/plugin:/plugin \
-v /path/to/local/attach:/attach \
-e APPRISE_STATEFUL_MODE=simple \
-e APPRISE_WORKER_COUNT=1 \
-d caronc/apprise:latest
Expand All @@ -72,11 +78,17 @@ A common change one might make is to update the Dockerfile to point to the maste
# Setup your environment the way you like
docker build -t apprise/local:latest -f Dockerfile .

# Set up a directory you wish to store your configuration in:
mkdir -p /etc/apprise

# Launch your instance
docker run --name apprise \
-p 8000:8000 \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-e APPRISE_STATEFUL_MODE=simple \
-e APPRISE_WORKER_COUNT=1 \
-v /etc/apprise:/config \
-d apprise/local:latest
```
A `docker-compose.yml` file is already set up to grant you an instant production ready simulated environment:
Expand All @@ -86,40 +98,6 @@ 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:

```bash
# Create a user/group (if one doesn't already exist) owned
# by the user and group id of 33
id 33 &>/dev/null || sudo useradd \
--system --no-create-home --shell /bin/false \
-u 33 -g 33 www-data

# Securely set the directory limiting access to only those who
# are part of the www-data group:
sudo chmod 770 -R /var/lib/apprise/config
sudo chown 33:33 -R /var/lib/apprise/config

# Now optionally add yourself to the group if you wish to be able to view
# contents.
sudo usermod -a -G 33 $(whoami)

# You may need to log out and back in again for the above usermod
# to reflect on you. Alternatively you can just type the following
# and it will work as a temporary solution:
sudo su - $(whoami)
```

Alternatively a dirty solution is to just set the directory with full read/write permissions (which is not ideal in a production environment):

```bash
# Grant full permission to the local directory you're saving your
# Apprise configuration to:
chmod 777 /var/lib/apprise/config
```

## Dockerfile Details

The following architectures are supported: `amd64`, `arm/v7`, and `arm64`. The following tags can be used:
Expand Down Expand Up @@ -398,6 +376,8 @@ The use of environment variables allow you to provide over-rides to default sett

| Variable | Description |
|--------------------- | ----------- |
| `PUID` | The User ID you wish the Apprise instance under the hood to run as. The default is `1000` if not otherwise specified.
| `PGID` | The Group ID you wish the Apprise instance under the hood to run as. The default is `1000` if not otherwise specified.
| `APPRISE_DEFAULT_THEME` | Can be set to `light` or `dark`; it defaults to `light` if not otherwise provided. The theme can be toggled from within the website as well.
| `APPRISE_DEFAULT_CONFIG_ID` | Defaults to `apprise`. This is the presumed configuration ID you always default to when accessing the configuration manager via the website.
| `APPRISE_CONFIG_DIR` | Defines an (optional) persistent store location of all configuration files saved. By default:<br/> - Configuration is written to the `apprise_api/var/config` directory when just using the _Django_ `manage runserver` script. However for the path for the container is `/config`.
Expand All @@ -421,8 +401,56 @@ The use of environment variables allow you to provide over-rides to default sett
| `DEBUG` | This defaults to `no` and can however be set to `yes` by simply defining the global variable as such.


## Development Environment
## Nginx Overrides

The 2 files you can override are:
1. `/etc/nginx/location-override.conf` which is included within all of the Apprise API NginX `location` references.
1. `/etc/nginx/server-override.conf` which is included within Apprise API `server` reference.

### Authentication
Under the hood, Apprise-API is running a small NginX instance. It allows for you to inject your own configuration into it. One thing you may wish to add is basic authentication.

Below we create ourselves some nginx directives we'd like to apply to our Apprise API:
```nginx
# Our override.conf file:
auth_basic "Apprise API Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
```

Now let's set ourselves up with a simple password file (for more info on htpasswd files, see [here](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/)
```bash
# Create ourselves a for our user 'foobar'; the below will prompt you for the pass
# you want to provide:
htpasswd -c apprise_api.htpasswd foobar

# Note: the -c above is only needed to create the database for the first time
```

Now we can create our docker container with this new authentication information:
```bash
# Create our container containing Basic Auth:
docker run --name apprise \
-p 8000:8000 \
-e PUID=$(id -u) \
-e PGID=$(id -g) \
-v /path/to/local/config:/config \
-v /path/to/local/attach:/attach \
-v ./override.conf:/etc/nginx/location-override.conf:ro \
-v ./apprise_api.htpasswd:/etc/nginx/.htpasswd:ro \
-e APPRISE_STATEFUL_MODE=simple \
-e APPRISE_WORKER_COUNT=1 \
-d caronc/apprise:latest
```

Visit http://localhost:8000 to see if things are working as expected. If you followed the example above, you should log in as the user `foobar` using the credentials you provided the account.

You can add further accounts to the existing database by omitting the `-c` switch:
```bash
# Add another account
htpasswd apprise_api.htpasswd user2
```

## Development Environment
The following should get you a working development environment to test with:

```bash
Expand Down
62 changes: 56 additions & 6 deletions apprise_api/api/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
</a>
<h1>{% trans "Apprise API" %}</h1>
<ul>
<li>APPRISE v{{APPRISE_VERSION}}</li>
<li class="theme"><a href="{{ request.path }}?theme={{request.next_theme}}"><i class="material-icons">invert_colors</i></a></li>
</ul>
<li>APPRISE v{{APPRISE_VERSION}}</li>
<li class="theme"><a href="{{ request.path }}?theme={{request.next_theme}}"><i class="material-icons">invert_colors</i></a></li>
</ul>
</div>
</div>
<!-- Page Layout here -->
Expand All @@ -50,10 +50,10 @@ <h1>{% trans "Apprise API" %}</h1>
<ul class="collection z-depth-1">
<a class="collection-item" href="{% url 'config' DEFAULT_CONFIG_ID %}"><i class="material-icons">settings</i>
{% trans "Configuration Manager" %}</a>
{% if not CONFIG_LOCK %}
{% if not CONFIG_LOCK %}
<a class="collection-item" href="{% url 'config' UNIQUE_CONFIG_ID %}"><i class="material-icons">refresh</i>
{% trans "New Configuration" %}</a>
{% endif %}
{% endif %}
</ul>
{% endif %}
<ul class="collection z-depth-1">
Expand All @@ -80,6 +80,28 @@ <h1>{% trans "Apprise API" %}</h1>
</div>

<div class="col s9">
<div id="health-check" class="section" style="display: none">
<h4><i class="material-icons" style="color: orange">warning</i>&nbsp;{% trans "Apprise Health Check Failed" %}&nbsp;<i class="material-icons" style="color: orange">warning</i></h4>
{% blocktrans %}The following disk access errors have been detected with your Apprise instance{% endblocktrans %}:
<ul>
<li class="can_write_config" style="display: none"><strong>
<i class="material-icons"
style="color: red">cancel</i>&nbsp;{% trans "Configuration Write Failure" %}</strong>
<p>{% blocktrans %}Apprise can not write new configuration information to the directory:{% endblocktrans %} <code>{{CONFIG_DIR}}</code>.</p>
<p>{% blocktrans %}<em>Note:</em> If this is the expected behavior, you should pre-set the environment variable <code>APPRISE_CONFIG_LOCK=yes</code> and reload your Apprise instance.{% endblocktrans %}</p>
</li>

<li class="can_write_attach" style="display: none"><strong>
<i class="material-icons"
style="color: red">cancel</i>&nbsp;{% trans "Attachment Temporary Storage Write Failure" %}</strong>
<p>{% blocktrans %}Apprise can not circulate attachments (if provided) along to supported endpoints due to not having write access to the directory:{% endblocktrans %} <code>{{ATTACH_DIR}}</code>.</p>
<p>{% blocktrans %}<em>Note:</em> If this is the expected behavior, you should pre-set the environment variable <code>APPRISE_ATTACH_SIZE=0</code> and reload your Apprise instance.{% endblocktrans %}</p>
</p>
</li>
</ul>
<p>{% blocktrans %}Under most circumstances, the issue(s) identified here are usually related to permission issues. Make sure you set the correct <code>PUID</code> and <code>GUID</code> to reflect the permissions you wish Apprise to utilize when it is reading and writing its files. In addition to this, you may need to make sure the permissions are set correctly on the directories you mapped them too.{% endblocktrans %}</p>
<p>{% blocktrans %}The issue(s) identified here can also be associated with SELinux too. You may wish to rule out SELinux by first temporarily disabling it using the command <code>setenforce 0</code>. You can always re-enstate it with <code>setenforce 1</code>{% endblocktrans %}.</p>
</div>
{% block body %}{% endblock %}
</div>

Expand All @@ -91,9 +113,37 @@ <h1>{% trans "Apprise API" %}</h1>
M.AutoInit();
// highlightjs
hljs.initHighlightingOnLoad();
{% block onload %} {% endblock %}
{% block onload %}{% endblock %}
// healthcheck
health_check()
});
{% block jsfooter %} {% endblock %}
function health_check() {
// perform our health check
document.querySelector('#health-check').style.display = 'none';
document.querySelector('#health-check li.can_write_config').style.display = 'none';
document.querySelector('#health-check li.can_write_attach').style.display = 'none';
let response = fetch('{% url "health" %}', {
method: 'GET',
headers: {
'Accept': 'application/json;charset=utf-8'
},

}).then(function(response) {
if(response.status != 200)
{
response.json().then(function(content) {
if (content['status']['can_write_config'] === false && content['config_lock'] === false) {
document.querySelector('#health-check li.can_write_config').style.display = '';
}
if (content['status']['can_write_attach'] === false && content['attach_lock'] === false) {
document.querySelector('#health-check li.can_write_attach').style.display = '';
}
document.querySelector('#health-check').style.display = '';
})
}
});
}
</script>
</body>

Expand Down
4 changes: 2 additions & 2 deletions apprise_api/api/templates/config.html
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ <h4>{% trans "Persistent Store Endpoints" %}</h4>
{% endif %}
{% endblock %}
{% block jsfooter %}

{{ block.super }}
{% if STATEFUL_MODE != 'disabled' %}
function update_count() {
const p_count = document.querySelectorAll('#url-list li.card-panel.selected').length;
Expand Down Expand Up @@ -695,8 +695,8 @@ <h4>{% trans "Persistent Store Endpoints" %}</h4>
{% endblock %}

{% block onload %}
{% if STATEFUL_MODE != 'disabled' %}
{{ block.super }}
{% if STATEFUL_MODE != 'disabled' %}
document.querySelector('label [for="id_tag"]')
{
// create a new div with the class 'chips' assigned to it
Expand Down
1 change: 0 additions & 1 deletion apprise_api/api/templates/welcome.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{% extends 'base.html' %}
{% load i18n %}

{% block body %}
<h4>{% trans "The Apprise API" %}</h4>
<p>
Expand Down
7 changes: 6 additions & 1 deletion apprise_api/api/tests/test_healthecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def test_healthcheck_simple(self):
content = loads(response.content)
assert content == {
'config_lock': False,
'attach_lock': False,
'status': {
'can_write_config': True,
'can_write_attach': True,
Expand All @@ -87,6 +88,7 @@ def test_healthcheck_simple(self):
content = loads(response.content)
assert content == {
'config_lock': True,
'attach_lock': False,
'status': {
'can_write_config': False,
'can_write_attach': True,
Expand All @@ -109,6 +111,7 @@ def test_healthcheck_simple(self):
content = loads(response.content)
assert content == {
'config_lock': False,
'attach_lock': False,
'status': {
'can_write_config': False,
'can_write_attach': True,
Expand All @@ -131,6 +134,7 @@ def test_healthcheck_simple(self):
content = loads(response.content)
assert content == {
'config_lock': False,
'attach_lock': True,
'status': {
'can_write_config': True,
'can_write_attach': False,
Expand All @@ -153,9 +157,10 @@ def test_healthcheck_simple(self):
content = loads(response.content)
assert content == {
'config_lock': False,
'attach_lock': False,
'status': {
'can_write_config': True,
'can_write_attach': False,
'can_write_attach': True,
'details': ['OK']
}
}
Expand Down
Loading

0 comments on commit c6b9c11

Please sign in to comment.