Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .devcontainer/caddy/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
ontrack.dev {
header {
Content-Security-Policy "default-src https: 'unsafe-inline' 'unsafe-eval' blob: data: ws:; worker-src 'self' blob:"
}

tls internal

# Student submission downloads retain their existing record-based URL. Caddy
# asks Rails to authenticate and authorise the request, then serves only the
# relative path returned by that trusted internal endpoint.
@submissionDownload {
method GET HEAD
path_regexp submissionDownload ^/api/projects/[0-9]+/task_def_id/[0-9]+/(?:submission|submission_files)$
}
@portfolioDownload {
method GET HEAD
path_regexp portfolioDownload ^/api/submission/unit/[0-9]+/portfolio$
}
@unitContentFile {
method GET HEAD
path_regexp unitContentFile ^/api/units/[0-9]+/content/sites/[0-9]+/files(?:/.*)?$
}
route {
handle @unitContentFile {
route {
request_header -X-OnTrack-File
request_header -X-OnTrack-Content-Disposition
request_header -X-OnTrack-Content-Type
request_header -X-OnTrack-Content-Site-Id

forward_auth formatif-dev-container:3000 {
uri /api/internal/downloads/unit-content
header_up X-OnTrack-Download-Auth {$DOWNLOAD_AUTH_SECRET}
copy_headers X-OnTrack-File X-OnTrack-Content-Disposition X-OnTrack-Content-Type X-OnTrack-Content-Site-Id
}

rewrite * /{http.request.header.X-OnTrack-File}
root * /srv/student-work
header Content-Disposition "{http.request.header.X-OnTrack-Content-Disposition}"
header Content-Type "{http.request.header.X-OnTrack-Content-Type}"
header X-Content-Site-Id "{http.request.header.X-OnTrack-Content-Site-Id}"
header Cache-Control "private, no-cache"
file_server
}
}

handle @submissionDownload {
route {
# Never allow a client to seed the internal routing headers.
request_header -X-OnTrack-File
request_header -X-OnTrack-Content-Disposition
request_header -X-OnTrack-Content-Type

forward_auth formatif-dev-container:3000 {
uri /api/internal/downloads/submission
header_up X-OnTrack-Download-Auth {$DOWNLOAD_AUTH_SECRET}
copy_headers X-OnTrack-File X-OnTrack-Content-Disposition X-OnTrack-Content-Type
}

rewrite * /{http.request.header.X-OnTrack-File}
root * /srv/student-work
header Content-Disposition "{http.request.header.X-OnTrack-Content-Disposition}"
header Content-Type "{http.request.header.X-OnTrack-Content-Type}"
file_server
}
}

handle @portfolioDownload {
route {
# The browser cannot select the filesystem path; only Rails can
# return these headers after authenticating and authorising the unit.
request_header -X-OnTrack-File
request_header -X-OnTrack-Content-Disposition
request_header -X-OnTrack-Content-Type

forward_auth formatif-dev-container:3000 {
uri /api/internal/downloads/portfolio
header_up X-OnTrack-Download-Auth {$DOWNLOAD_AUTH_SECRET}
copy_headers X-OnTrack-File X-OnTrack-Content-Disposition X-OnTrack-Content-Type
}

rewrite * /{http.request.header.X-OnTrack-File}
root * /srv/student-work
header Content-Disposition "{http.request.header.X-OnTrack-Content-Disposition}"
header Content-Type "{http.request.header.X-OnTrack-Content-Type}"
header Cache-Control "private, no-store"
file_server
}
}

# Rails exposes its health check at /health. Keep the public URL aligned
# with the production /api namespace.
handle /api/health {
rewrite * /health
reverse_proxy formatif-dev-container:3000
}

# Preserve the /api prefix when forwarding application requests to Rails.
handle /api/* {
reverse_proxy formatif-dev-container:3000
}

# Angular's development server also uses this proxy for its HMR WebSocket.
handle {
reverse_proxy formatif-dev-container:4200
}
}
}
32 changes: 29 additions & 3 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ services:
# mounts are relative to the first file in the list, which is a level up.
- ..:/workspace:cached
- formatif_tmp_compose:/workspace/tmp
- formatif_student-work_compose:/student-work
- ../data/student-work:/student-work
- jplag_results:/jplag/results
- ws_node_modules:/workspace/node_modules
- web_node_modules:/workspace/doubtfire-web/node_modules
Expand All @@ -48,10 +48,34 @@ services:
environment:
LATEX_CONTAINER_NAME: 1-formatif-texlive-container
LATEX_BUILD_PATH: /texlive/shell/latex_build.sh
DF_INSTITUTION_HOST: https://ontrack.dev
DF_COOKIE_DOMAIN: ontrack.dev
DF_CADDY_DOWNLOAD_AUTH_SECRET: local-development-only-caddy-download-secret
depends_on:
- dev-db
- gotenberg-volumes

# Production-like local HTTPS entry point. Student work is mounted read-only
# and is only served after Rails authorises the protected content route.
caddy:
image: caddy:latest
container_name: ontrack-caddy
restart: unless-stopped
environment:
DOWNLOAD_AUTH_SECRET: local-development-only-caddy-download-secret
ports:
- "80:80"
- "443:443"
networks:
- frontnet
depends_on:
- formatif-dev-container
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
- ../data/student-work:/srv/student-work:ro

dev-db:
container_name: doubtfire-dev-db
image: mariadb:10
Expand Down Expand Up @@ -85,7 +109,7 @@ services:
# context: ../
# dockerfile: texlive.Dockerfile
volumes:
- formatif_student-work_compose:/student-work
- ../data/student-work:/student-work
- ../doubtfire-api/tmp/rails-latex:/workdir/texlive-latex
- ../doubtfire-api/public/assets/images:/workspace/doubtfire-api/public/assets/images
- ../doubtfire-api/test_files:/workspace/doubtfire-api/test_files
Expand All @@ -101,7 +125,7 @@ services:
# context: ../
# dockerfile: jplag.Dockerfile
volumes:
- formatif_student-work_compose:/student-work
- ../data/student-work:/student-work
- jplag_results:/jplag/results
- ../doubtfire-api/tmp/jplag:/tmp/jplag
command: /bin/sh -c "sleep infinity"
Expand Down Expand Up @@ -143,3 +167,5 @@ volumes:
codex_home:
claude_home:
vscode_server_home:
caddy_data:
caddy_config: