stash
This commit is contained in:
parent
419893d93d
commit
b51d3d05f4
3 changed files with 94 additions and 17 deletions
|
|
@ -158,6 +158,14 @@ STATIC_URL = '/static/'
|
|||
MEDIA_ROOT = os.environ.get('TOOLSHED_USERFILES_PATH', 'userfiles')
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
# In prod, nginx (running as www-data) reads these files directly - see
|
||||
# SERVE_X_ACCEL_REDIRECT and playbook.yml's `location /redirect_media/`.
|
||||
# Pinned explicitly rather than left to the backend process's ambient umask,
|
||||
# so group-read (www-data is added to the backend's system group) is
|
||||
# guaranteed regardless of how the container is started.
|
||||
FILE_UPLOAD_PERMISSIONS = 0o640
|
||||
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o750
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,11 @@ toolshed:
|
|||
in `user@yourtoolshed.tld`) — see [DNS](#3-dns) for how those two relate.
|
||||
- `toolshed_handle_domain` — the **handle domain**, only needed when it's
|
||||
different from `toolshed_domain`. Omit it when the two are the same (it
|
||||
then defaults to `toolshed_domain`). Set so nginx/Django accept requests
|
||||
for either domain, whichever ends up as the `Host` header.
|
||||
then defaults to `toolshed_domain`). It doesn't affect nginx/Django at all
|
||||
(they only ever accept `toolshed_domain` as the `Host` header) — it's used
|
||||
solely to populate the `/local/domains` registration fixture (see
|
||||
`toolshed_register_domains` in `playbook.yml`); publishing the SRV record is a
|
||||
separate, manual DNS step either way.
|
||||
- `toolshed_repo_url` — the git remote the playbook checks out and builds
|
||||
from. Required, no default.
|
||||
- `toolshed_version` — the branch, tag or commit to check out and build.
|
||||
|
|
@ -94,10 +97,12 @@ There are two distinct domains at play here, and it's easy to conflate them:
|
|||
`user@yourtoolshed.tld`. Toolshed usernames don't encode a server address
|
||||
directly; the frontend resolves the handle domain to a server via an SRV
|
||||
record, `_toolshed-server._tcp.<handle domain>.` (see
|
||||
`frontend/src/store.js`, `lookupServer`). What's in `toolshed_handle_domain`
|
||||
(see [Per-deployment configuration](#2-per-deployment-configuration)) only
|
||||
makes nginx/Django accept it as a `Host` header — publishing the actual SRV
|
||||
record is still a separate, manual DNS step, covered below.
|
||||
`frontend/src/store.js`, `lookupServer`), which always points at the web
|
||||
domain — nginx/Django never see the handle domain as a `Host` header.
|
||||
`toolshed_handle_domain` (see [Per-deployment
|
||||
configuration](#2-per-deployment-configuration)) only feeds the
|
||||
`/local/domains` registration fixture; publishing the actual SRV record is
|
||||
still a separate, manual DNS step, covered below.
|
||||
|
||||
The SRV lookup happens for every login, not just federation with other
|
||||
servers, so **every** deployment needs it published for its own handle
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@
|
|||
toolshed_backend_port: 8000
|
||||
toolshed_wiki_dist_dir: /var/www/toolshed-wiki
|
||||
toolshed_local_dir: /var/www/toolshed-local
|
||||
# Django's collectstatic output (admin/drf-yasg assets etc.), exported
|
||||
# from the built backend image so nginx can serve it directly instead of
|
||||
# proxying to gunicorn for every asset request.
|
||||
toolshed_static_dir: /var/www/toolshed-static
|
||||
# Domain(s) this server accepts registrations for (the "handle domain" -
|
||||
# see the README's DNS section). Served as a static /local/domains
|
||||
# fixture that the frontend's registration/pairing forms fetch to
|
||||
|
|
@ -73,15 +77,14 @@
|
|||
# header instead.
|
||||
toolshed_x_forwarded_proto: >-
|
||||
{{ '$http_x_forwarded_proto' if (behind_tls_proxy | default(false) | bool) else '$scheme' }}
|
||||
# The web domain (toolshed_domain, mandatory) and the handle domain
|
||||
# (toolshed_handle_domain, optional - defaults to the web domain when
|
||||
# they're the same) both need to be accepted by nginx/Django, since
|
||||
# either may show up as the Host header depending on how the admin set
|
||||
# up DNS for this deployment. Deduplicated so setting them equal
|
||||
# doesn't produce a repeated entry.
|
||||
# Only the web domain (toolshed_domain) - nginx server_name, Django
|
||||
# ALLOWED_HOSTS, and the cert certbot requests. The handle domain
|
||||
# (toolshed_handle_domain) is resolved by clients via its own SRV record
|
||||
# and doesn't necessarily have an A record pointing at this host at all
|
||||
# (see the README's DNS section), so it can't reliably serve an HTTP-01
|
||||
# challenge or ever show up as this nginx's Host header.
|
||||
toolshed_hostnames: >-
|
||||
{{ [toolshed_domain | mandatory('toolshed_domain must be set as a host_var for ' ~ inventory_hostname),
|
||||
toolshed_handle_domain | default(toolshed_domain)] | unique }}
|
||||
{{ [toolshed_domain | mandatory('toolshed_domain must be set as a host_var for ' ~ inventory_hostname)] }}
|
||||
# Generated once per host on the controller and reused on every
|
||||
# subsequent run against that host, keyed by inventory_hostname so
|
||||
# separate deployments never end up sharing a Django SECRET_KEY.
|
||||
|
|
@ -120,6 +123,11 @@
|
|||
proxy_pass http://toolshed_backend;
|
||||
}
|
||||
|
||||
# Django (SignatureAuthentication + per-file friend/owner checks,
|
||||
# see files/media_urls.py) decides whether the request is allowed
|
||||
# at all; it never streams the bytes itself here (SERVE_X_ACCEL_REDIRECT
|
||||
# is on), it just answers with an X-Accel-Redirect to the internal
|
||||
# location below, which nginx follows and serves directly from disk.
|
||||
location /media {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
|
@ -128,6 +136,19 @@
|
|||
proxy_pass http://toolshed_backend;
|
||||
}
|
||||
|
||||
# Only reachable via the X-Accel-Redirect above, never directly by
|
||||
# clients (`internal`) - this is what makes it safe for nginx to
|
||||
# serve these bytes itself without reimplementing the access
|
||||
# checks Django already did in the /media location.
|
||||
location /redirect_media/ {
|
||||
internal;
|
||||
alias {{ toolshed_data_dir }}/userfiles/;
|
||||
# Django would normally set this itself (CORS_ALLOW_ALL_ORIGINS,
|
||||
# see settings.py) but never gets to run for a request nginx
|
||||
# serves directly - see the comment in files/media_urls.py.
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
}
|
||||
|
||||
location /djangoadmin {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
|
@ -144,8 +165,9 @@
|
|||
proxy_pass http://toolshed_backend;
|
||||
}
|
||||
|
||||
location /static {
|
||||
proxy_pass http://toolshed_backend/static;
|
||||
location /static/ {
|
||||
alias {{ toolshed_static_dir }}/;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location /wiki/ {
|
||||
|
|
@ -283,6 +305,20 @@
|
|||
- "{{ toolshed_data_dir }}"
|
||||
- "{{ toolshed_data_dir }}/userfiles"
|
||||
|
||||
# nginx's `location /redirect_media/` (below) reads user-uploaded files
|
||||
# straight off disk as www-data - group membership plus the 0750 mode
|
||||
# above/FILE_UPLOAD_PERMISSIONS (backend/backend/settings.py) is what
|
||||
# makes that readable without loosening it to world-readable.
|
||||
- name: Allow nginx to read backend user files
|
||||
ansible.builtin.user:
|
||||
name: www-data
|
||||
groups: toolshed
|
||||
append: true
|
||||
# New group membership only takes effect for processes started (or
|
||||
# forked) after this - nginx's already-running workers won't see it
|
||||
# until reloaded.
|
||||
notify: reload nginx
|
||||
|
||||
- name: Create frontend static output directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ toolshed_dist_dir }}"
|
||||
|
|
@ -291,6 +327,14 @@
|
|||
group: www-data
|
||||
mode: "0755"
|
||||
|
||||
- name: Create backend static output directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ toolshed_static_dir }}"
|
||||
state: directory
|
||||
owner: www-data
|
||||
group: www-data
|
||||
mode: "0755"
|
||||
|
||||
- name: Write backend environment file
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ toolshed_data_dir }}/backend.env"
|
||||
|
|
@ -306,7 +350,7 @@
|
|||
DEBUG={{ toolshed_debug }}
|
||||
SECRET_KEY={{ toolshed_secret_key }}
|
||||
ALLOWED_HOSTS={{ toolshed_hostnames | join(',') }}
|
||||
SERVE_X_ACCEL_REDIRECT=False
|
||||
SERVE_X_ACCEL_REDIRECT=True
|
||||
TOOLSHED_DB_PATH=/data/db.sqlite3
|
||||
TOOLSHED_USERFILES_PATH=/data/userfiles
|
||||
notify: restart backend
|
||||
|
|
@ -325,6 +369,26 @@
|
|||
changed_when: true
|
||||
notify: restart backend
|
||||
|
||||
# Dockerfile.backend runs collectstatic at build time, baking the result
|
||||
# into the image at /app/staticfiles - copy it out to the host so nginx
|
||||
# can serve it directly instead of proxying every asset request to
|
||||
# gunicorn. No Django settings/DB access needed, so this can run as a
|
||||
# one-off command against the image rather than the container.
|
||||
- name: Export backend static files
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
docker run --rm -v {{ toolshed_static_dir }}:/output
|
||||
{{ toolshed_backend_image }}:latest
|
||||
sh -c "cp -a /app/staticfiles/. /output/"
|
||||
changed_when: true
|
||||
|
||||
- name: Fix ownership of exported backend static files
|
||||
ansible.builtin.file:
|
||||
path: "{{ toolshed_static_dir }}"
|
||||
owner: www-data
|
||||
group: www-data
|
||||
recurse: true
|
||||
|
||||
- name: Install systemd unit for the backend container
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/toolshed-backend.service
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue