Skip to content

Commit caaa410

Browse files
authored
Merge pull request #41 from remote-exercise-framework/dev
Dev
2 parents ed67e64 + d03b5e1 commit caaa410

237 files changed

Lines changed: 36661 additions & 5655 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CLAUDE.md

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Important Documents
6+
7+
- `README.md` - Project overview and setup instructions
8+
- `EXERCISES.md` - Exercise creation and submission testing
9+
- `docs/ARCHITECTURE.md` - System architecture and components
10+
- `.claude/CONTEXT.md` - Ongoing work and recent changes (create if missing)
11+
12+
## Build and Run Commands
13+
14+
**Note:** In sandboxed environments where `~/.docker` may be read-only, set `DOCKER_CONFIG` to a writable directory before running Docker commands:
15+
16+
```bash
17+
export DOCKER_CONFIG=/path/to/repo/.docker-cache
18+
```
19+
20+
The test infrastructure (`tests/helpers/ref_instance.py`) automatically sets this to `.docker-cache/` in the repo root.
21+
22+
```bash
23+
# Build all Docker images
24+
./ctrl.sh build
25+
26+
# Start services
27+
# For development, always use --debug and --hot-reloading:
28+
# --debug enables Flask debug mode and verbose logging
29+
# --hot-reloading enables Flask auto-reload and runs the spa-frontend
30+
# under `vite dev` (Vite HMR) instead of a static build
31+
./ctrl.sh up --debug --hot-reloading
32+
./ctrl.sh up # production-style start, no HMR
33+
34+
# Stop services
35+
./ctrl.sh stop # Keep containers
36+
./ctrl.sh down # Remove containers
37+
38+
# Database migrations
39+
./ctrl.sh db-upgrade
40+
41+
# View logs
42+
./ctrl.sh logs -f
43+
```
44+
45+
## Code Quality
46+
47+
Python code must pass the same checks as CI. **Always run these checks on new or modified code.**
48+
49+
```bash
50+
# Install tools (if needed)
51+
uv tool install ruff
52+
uv tool install mypy
53+
54+
# Install test dependencies (required for mypy)
55+
cd tests && uv sync
56+
57+
# Linting and formatting (run from repo root)
58+
ruff check .
59+
ruff format --check . # Verify formatting (use 'ruff format .' to fix)
60+
61+
# Type checking (run from tests/ directory)
62+
cd tests && uv run mypy .
63+
```
64+
65+
These checks must pass before committing. CI will reject PRs that fail any of these checks.
66+
67+
### Git Hooks
68+
69+
A pre-commit hook is available that automatically runs linting checks before each commit:
70+
71+
```bash
72+
# Install git hooks
73+
./hooks/install.sh
74+
```
75+
76+
The hook runs `ruff check`, `ruff format --check`, and `mypy`, rejecting commits that fail.
77+
78+
## Testing
79+
80+
**Important:** Never manually start a REF instance for running automated Python tests. The test infrastructure handles instance lifecycle automatically. Starting instances manually for interactive testing/debugging is fine.
81+
82+
```bash
83+
# Install test dependencies
84+
cd tests && uv sync
85+
86+
# Run all tests (test infrastructure manages REF instance)
87+
cd tests && pytest
88+
89+
# Run only unit tests
90+
cd tests && pytest unit/
91+
92+
# Run only integration tests
93+
cd tests && pytest integration/
94+
95+
# Run only E2E tests
96+
cd tests && pytest e2e/
97+
98+
# Skip slow tests
99+
cd tests && pytest -m "not slow"
100+
101+
# Run a single test file
102+
cd tests && pytest unit/test_ssh_client.py
103+
104+
# Run a specific test
105+
cd tests && pytest unit/test_ssh_client.py::test_function_name
106+
```
107+
108+
Tests must fail if dependencies are missing. Only skip tests if explicitly requested.
109+
110+
**Do not write tests that check CLI help commands.** Testing `--help` output is low value.
111+
112+
**Do not use hardcoded values in assertions.** Tests should verify behavior and relationships, not specific magic numbers or strings that may change.
113+
114+
### Test Architecture and Abstractions
115+
116+
Tests outside of `tests/unit/` (e.g., integration tests, E2E tests) must **never directly manipulate database objects**. Instead, they should:
117+
118+
1. **Use manager classes** - `ExerciseManager`, `InstanceManager`, `ExerciseImageManager` provide the business logic layer
119+
2. **Follow view function patterns** - Replicate the same logic that view functions in `ref/view/` use
120+
3. **Use `tests/helpers/method_exec.py`** - Pre-built functions that call managers via `remote_exec`
121+
122+
This ensures tests exercise the same code paths as the real application, catching integration issues that unit tests might miss.
123+
124+
**Example - Correct approach:**
125+
```python
126+
# Use InstanceManager.remove() like the view does
127+
mgr = InstanceManager(instance)
128+
mgr.remove()
129+
```
130+
131+
**Example - Incorrect approach:**
132+
```python
133+
# Don't directly delete DB objects
134+
db.session.delete(instance)
135+
db.session.commit()
136+
```
137+
138+
The abstraction layers are:
139+
- `ref/view/` - HTTP request handlers (views)
140+
- `ref/core/` - Business logic managers (ExerciseManager, InstanceManager, etc.)
141+
- `ref/model/` - SQLAlchemy models (data layer)
142+
143+
Tests should interact with `ref/core/` managers or replicate `ref/view/` logic, not bypass them to manipulate `ref/model/` directly.
144+
145+
## Dependency Management
146+
147+
Use `uv` for all Python dependency management. Each component has its own `pyproject.toml`:
148+
- `webapp/pyproject.toml` - Web application
149+
- `ref-docker-base/pyproject.toml` - Container base image
150+
- `tests/pyproject.toml` - Test suite
151+
152+
## Architecture Overview
153+
154+
REF is a containerized platform for hosting programming exercises with isolated student environments. See `docs/ARCHITECTURE.md` for full details.
155+
156+
### Components
157+
158+
1. **Web Application** (`webapp/`) - Flask app served by uWSGI on internal port 8000 (not published; reached via `frontend-proxy`)
159+
- `ref/view/` - HTML route handlers (exercises, grading, instances, file browser, visualization, admin student management, system settings, etc.)
160+
- `ref/services_api/` - JSON endpoints called by services (SSH reverse proxy hooks in `ssh.py`, student container callbacks in `instance.py`)
161+
- `ref/frontend_api/` - JSON endpoints consumed by the Vue SPA (registration/restore-key in `students.py`, public scoreboard in `scoreboard.py`; mounted under `/api/v2/*` + `/api/scoreboard/*`)
162+
- `ref/model/` - SQLAlchemy models (users, groups, exercises, instances, submissions, grades, system settings)
163+
- `ref/core/` - Business logic managers (`ExerciseManager`, `InstanceManager`, `ExerciseImageManager`, `UserManager`, `DockerClient`, etc.)
164+
165+
Student-facing pages (registration, restore-key, public scoreboard) are served by the Vue SPA under `/spa/*` and talk to `ref/frontend_api/`. Admin pages live under `ref/view/` as Jinja-rendered HTML.
166+
167+
2. **Frontend Proxy** (`frontend-proxy/`) - Caddy 2 container that fronts the Flask `web` service and the Vue SPA on a **single host port** (`HTTP_HOST_PORT`, default 8000). Multi-stage Dockerfile: stage 1 builds the SPA with Node; stage 2 is `caddy:2-alpine` with `dist/` baked in at `/srv/spa-dist`. Routes:
168+
- `/spa/*``spa-frontend:5173` (dev) or baked `/srv/spa-dist` via `file_server` (prod)
169+
- `/static/*` → bind-mount of `webapp/ref/static` served directly
170+
- `/admin`, `/admin/` → 302 to `/admin/exercise/view`
171+
- `/spa` → 308 `/spa/`
172+
- everything else → `reverse_proxy web:8000` with `header_up X-Tinyproxy {remote_host}` so Flask's rate limiter keys on the real client IP
173+
Dev/prod is selected at container start by `entrypoint.sh` via `$HOT_RELOADING`. The `spa-frontend` service is gated behind the `dev` compose profile, and `ctrl.sh` exports `COMPOSE_PROFILES=dev` when `--hot-reloading` is active. **Never run `--hot-reloading` on a publicly reachable host**`vite dev` is not a production server. The SPA renders a hazard-striped warning banner when `import.meta.env.DEV` is true.
174+
175+
3. **SSH Reverse Proxy** (`ssh-reverse-proxy/`) - Rust-based SSH proxy on port 2222
176+
- Routes student SSH connections to exercise containers
177+
- Uses web API with HMAC-signed requests for authentication and provisioning
178+
- Supports shell, exec, SFTP, local/remote port forwarding, and X11 forwarding
179+
180+
4. **Instance Container** (`ref-docker-base/`) - Ubuntu 24.04 with dev tools
181+
- Isolated per student/exercise under `ref-instances.slice` cgroup
182+
- SSH server on port 13370
183+
- Contains `ref-utils` for submission testing
184+
- `task`/`_task` scripts for submission testing, `reset-env` for container reset
185+
186+
5. **Database** - PostgreSQL 17.2 storing users, groups, exercises, instances, submissions, grades, system settings
187+
188+
### Connection Flow
189+
190+
```
191+
Client (ssh exercise@host -p 2222)
192+
-> ssh-reverse-proxy validates via /api/getkeys
193+
-> ssh-reverse-proxy provisions via /api/provision
194+
-> Traffic proxied to container SSH (port 13370)
195+
```
196+
197+
### Docker Networks
198+
199+
- `web-host` - Web ↔ Host (HTTP access)
200+
- `web-and-ssh` - Web ↔ SSH reverse proxy API (internal)
201+
- `web-and-db` - Web ↔ PostgreSQL (internal)
202+
- `ssh-and-host` - SSH reverse proxy ↔ Host
203+
204+
### Data Persistence
205+
206+
- `/data/postgresql-db/` - Database files
207+
- `/data/data/imported_exercises/` - Exercise definitions
208+
- `/data/data/persistance/` - User submissions and instance data
209+
- `/data/ssh-proxy/` - SSH proxy state
210+
- `/data/log/` - Application logs
211+
212+
## Code Comments
213+
214+
- Do not reference line numbers in comments (e.g., "see ssh.py lines 397-404"). Line numbers change frequently and become outdated. Reference functions, classes, or use direct code references instead.
215+
216+
## Pending Tasks
217+
218+
Pending tasks in the codebase are marked with `FIXME(claude)` and `TODO(claude)`. When the user requests to process todos or fixmes, search for these markers and address them.
219+
220+
## Fixing Race Conditions
221+
222+
**Never fix race conditions by:**
223+
- Adding timeouts or delays (e.g., `time.sleep()`)
224+
- Reducing the number of threads or parallel processes
225+
- Reducing test parallelism (e.g., changing `-n 10` to `-n 4`)
226+
227+
These approaches hide the underlying problem rather than fixing it. Race conditions must be fixed by addressing the root cause: proper synchronization, locking, atomic operations, or architectural changes.
228+
229+
## Commit Messages
230+
231+
- Do not include Claude as author or co-author in commit messages.
232+
- Do not include historical context like "this fixes the failing test" or "this addresses the previous issue". Describe what the change does, not why it was needed.
233+
234+
## Test Log Summary
235+
236+
After test failures, a summary is automatically generated at `tests/failure_logs/SUMMARY.txt`. To regenerate manually:
237+
238+
```bash
239+
cd tests && python3 summarize_logs.py
240+
```
241+
242+
**Maintaining the pattern list:** The `ERROR_PATTERNS` dict in `tests/summarize_logs.py` defines which errors are detected. Keep this list accurate:
243+
- **Add patterns** for error types that appear in logs but are missing from the summary
244+
- **Remove patterns** that trigger false positives (matching non-error text)

.dockerignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
**/.git
2+
**/node_modules
3+
**/__pycache__
4+
**/*.pyc
5+
**/.venv
6+
**/.mypy_cache
7+
**/.ruff_cache
8+
**/.pytest_cache
9+
10+
ref-linux/
11+
data/
12+
backup_exercises/
13+
ref-exercises/
14+
.docker-cache/
15+
16+
tests/
17+
tests/failure_logs/
18+
19+
webapp/ref_webapp.egg-info/
20+
21+
.codex/
22+
.claude/
23+
24+
docs/
25+
*.md
26+
27+
spa-frontend/dist/

0 commit comments

Comments
 (0)