* Update devenv

* Added uv for python package management

* Clean up gunicorn config artifacts

* Scaffolded site

* Fixed configuration for 13.0

* Added iform config/theme

* Added agent contracts to allow for more automation of work in repo

* Added a local rebuild command to justfile for development

* Fixed deployment persistence issues

* Site polish and deployment streamlining

* Updated justfile to include production deployment commands

* Fixed deployment bugs

* Fixed local test commands
This commit is contained in:
2026-07-16 12:05:06 +01:00
committed by GitHub
parent 15a179c2bf
commit 413c85096d
76 changed files with 8396 additions and 1143 deletions
@@ -0,0 +1,5 @@
FROM docker.io/library/nginx
COPY nginx.conf /etc/nginx/nginx.conf
COPY conf.d/* /etc/nginx/conf.d/
COPY test.key /etc/ssl/private/test.key
COPY test.crt /etc/ssl/certs/test.crt
@@ -0,0 +1,129 @@
# This nginx configuration defines two servers, one on port 80 and one on port
# 443. All traffix on port 80 is redirect to port 443 on SSL.
#
# Nginx proxies all requests on port 443 to upstream the application server
# which is expected to be running on port 5000/5001.
# HTTP server
server {
# Redirects all requests to https. - this is in addition to HAProxy which
# already redirects http to https. This redirect is needed in case you access
# the server directly (e.g. useful for debugging).
listen 80 default_server; # IPv4
listen [::]:80 default_server; # IPv6
server_name _;
return 301 https://$host$request_uri;
}
# Upstream servers
upstream ui_server {
server web-ui:5000;
}
upstream api_server {
server web-api:5000;
}
# HTTPS server
server {
listen 443 default_server ssl http2; # IPv4
listen [::]:443 default_server ssl http2; # IPv6
server_name _;
charset utf-8;
keepalive_timeout 5;
# SSL configuration according to best practices from
# https://mozilla.github.io/server-side-tls/ssl-config-generator/
# The provided certificate (test.crt) and private key (test.key) is only for
# testing and must never be used in production environment.
ssl_certificate /etc/ssl/certs/test.crt;
ssl_certificate_key /etc/ssl/private/test.key;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# Accepted protocols and ciphers
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=15768000"; # 6 months
# Request ID tracing (allows end-to-end tracking of requests for better
# troubleshooting)
add_header X-Request-ID $request_id;
# The request body is sent to the proxied server immediately as it is
# received
proxy_request_buffering off;
# Sets the HTTP protocol v1.1 for proxying in order to not use the buffer
# in case of chunked transfer encoding
proxy_http_version 1.1;
# Proxying to the application server
## UI server
location / {
uwsgi_pass ui_server;
include uwsgi_params;
uwsgi_buffering off;
uwsgi_request_buffering off;
chunked_transfer_encoding off;
uwsgi_param HTTP_HOST $host;
uwsgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;
# Pass request id to the ui server
uwsgi_param HTTP_X_REQUEST_ID $request_id;
# X-Session-ID / X-User-ID is read by nginx and included in the logs,
# however we don't want to expose them to clients so we are hiding them.
uwsgi_hide_header X-Session-ID;
uwsgi_hide_header X-User-ID;
# Max upload size (except for files) is set to 100mb as default.
client_max_body_size 100m;
}
## Most API
location /api {
uwsgi_pass api_server;
include uwsgi_params;
uwsgi_buffering off;
uwsgi_request_buffering off;
chunked_transfer_encoding off;
uwsgi_param HTTP_HOST $host;
uwsgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;
# Pass request id to the api server
uwsgi_param HTTP_X_REQUEST_ID $request_id;
# X-Session-ID / X-User-ID is read by nginx and included in the logs,
# however we don't want to expose them to clients so we are hiding them.
uwsgi_hide_header X-Session-ID;
uwsgi_hide_header X-User-ID;
# Max upload size (except for files) is set to 100mb as default.
client_max_body_size 100m;
}
## API files
# Another location is defined in order to allow large file uploads in the files
# API without exposing the other parts of the application to receive huge
# request bodies. This includes part content uploads of multipart file uploads.
location ~ /api/records/.+/draft/files/.+/content(/[0-9]+)?/?$ {
gzip off;
uwsgi_pass api_server;
include uwsgi_params;
uwsgi_buffering off;
uwsgi_request_buffering off;
chunked_transfer_encoding off;
uwsgi_param HTTP_HOST $host;
uwsgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;
# Pass request id to api server
uwsgi_param HTTP_X_REQUEST_ID $request_id;
# X-Session-ID / X-User-ID is read by nginx and included in the logs,
# however we don't want to expose them to clients so we are hiding them.
uwsgi_hide_header X-Session-ID;
uwsgi_hide_header X-User-ID;
# Max upload size for files is set to 50GB (configure as needed).
client_max_body_size 50G;
}
# Static content is served directly by nginx and not the application server.
location /static {
alias /opt/invenio/var/instance/static;
autoindex off;
}
}
@@ -0,0 +1,81 @@
user nginx;
worker_processes 1;
error_log /var/log/nginx/error_real.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
types {
# The default MIME types file doesn't assign "application/javascript"
# to "*.mjs" files (ECMAScript modules)
application/javascript js mjs;
}
# Standard log format
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Request tracing log format - includes request id, session id, user id,
# and request timing.
log_format trace '$remote_addr - [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" $request_id '
'$msec $request_time '
'$upstream_http_x_session_id $upstream_http_x_user_id';
access_log /var/log/nginx/access.log trace;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
gzip on;
gzip_disable "msie6";
gzip_http_version 1.1;
gzip_comp_level 5; # or anything between 4-6
gzip_min_length 100;
gzip_proxied any;
# We may need more mime-types here (eg. 'application/x-bibtex')
gzip_types
application/atom+xml
application/javascript
application/json
application/ld+json
application/manifest+json
application/octet-stream
application/rss+xml
application/vnd.geo+json
application/vnd.ms-fontobject
application/x-font-ttf
application/x-javascript
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
application/xml+rss
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/javascript
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy
text/xml;
gzip_vary on;
include /etc/nginx/conf.d/*.conf;
}
@@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFpzCCA4+gAwIBAgIUSz9J4Gq7+Qn5md8T24tFHEPr3OcwDQYJKoZIhvcNAQEL
BQAwYzELMAkGA1UEBhMCQ0gxCjAIBgNVBAgMAS4xCjAIBgNVBAcMAS4xCjAIBgNV
BAoMAS4xCjAIBgNVBAsMAS4xEjAQBgNVBAMMCWxvY2FsaG9zdDEQMA4GCSqGSIb3
DQEJARYBLjAeFw0yNTExMjcwOTQ1MTVaFw0yNjExMjcwOTQ1MTVaMGMxCzAJBgNV
BAYTAkNIMQowCAYDVQQIDAEuMQowCAYDVQQHDAEuMQowCAYDVQQKDAEuMQowCAYD
VQQLDAEuMRIwEAYDVQQDDAlsb2NhbGhvc3QxEDAOBgkqhkiG9w0BCQEWAS4wggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC24k/6OfzIdlxXvrlW1xSo7QZp
55sWsct0NK1bPiWElEybZgcVMYg5nK037Nk9xZPh59j/LD7wKvw+S6RVd6k8+UBU
ms4QrZOSG9CyMXlneEc+Pl/4pzM+2R1NMQG0f9tsc3R5Eom+e5sK4XEcVPrrcHs0
QKYZT734lVq1oFKmVHn/2CfECfwv+M3Vh2uY2s1vDWQL2HGCumeyHspbuIxfX+zA
KlR3WlOC82g13eIpJRVQeVznOPm7O1c8qSMEe32YR6JjsYwaEGPX5f7EnOtp0KLu
YusP8mgotU5h32KXmyGGPQa9tjUWvjiEuZMGeRY51ppsdDg4N8HVEkuKXkEU7BxS
19kFQUZUrdH8a9ziO+xGQ63qJ0kZxqn3rHXsuMHpqf/1s9oscj/SIITuXQVGm/I0
tE7cd8d+FxxS3ZAWYtNneq08810Jt3EKHzqnKimdUdK5ewV6suQgm9zAOdesOayQ
JBsrjtJfYptveyQyx5WNi2raKPSzMnsCJ3wQqijR8hmX6uBkjmDod+fcqxp++1d7
b5G6uZqp1BbUBn822Zj3Y3xA7Ytc0Gw1qGWE22LG6//JxZ5BnINWeOvic0i6x7Hw
qeBjJM80wUsTAK8khE+YnBe1xhiexJMXn5MnrIljjv7u79EqSAkx/hBL0co14WI8
5HxbqKTsub4g8EIfuQIDAQABo1MwUTAdBgNVHQ4EFgQUgaT39u46Jud7DJdbgF6I
pEB6XI0wHwYDVR0jBBgwFoAUgaT39u46Jud7DJdbgF6IpEB6XI0wDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAc8bweJSg/8S82x0cabl4bl+WiqG2
PYSzPgMnMXK0rbx8sss9cUIeKoOEWR0DzLdnTE9xPI4rLaKwK3LRv4JWmx1QjIac
tvrnDdoO6uKubqS4Vf0Q+QSeiVIIvu4qveK8syhJaEZJtQiUaYanxYSkn8skOhoL
ZZv/t8Iu0KDJ1EI5eNMCDTzny8VOYk08Nx97TAGazgCp6fx3xpk7mc3iNQcCrtmC
3emhW0Pgro8tHg9bECtugLk/pyBys0mt65gUMXu572LOUdNoOq1jbsXU2/HpA9G2
/5IntXmP8k6i7w+ZtbN8JpwfUTg25JSGYVEZ0SrtAj6AfLZvG+MdrhWcw7XCbm2a
94h2O1H0rQ2u4EEOqPpAXByVCyfGmQQxzyCinLy42eNPcAiehdSDEgTInIHxmIik
PqCYyWL/s1ltbV+zai1FKK7GU2N911e5kcioGHIpWjo/a+aNsmcw+umpSjyFkQ2x
FtE8i4oePPosSNhDZdpv+0PDygKGU8uMZLO09IOeUIeyZlphCUglBM2dDZY3Rpzg
AQxYMVvP75ceNl4XZ0UFbzM937/a6ElsOSrP1zNHPV8AGWX2VNKWqGMd+qADu8/A
3pcDjJk+YLwT6343+tYc7rRu8skXtMxxRgwvAUdCUaTjNT924PaOf6ImoVhpPgkf
1i0GCvJT5yQK4q4=
-----END CERTIFICATE-----
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
NkuWkf4v3IVpMofi+eIU909FiFXNeQ==
-----END PRIVATE KEY-----