Skip to content

Dev#41

Merged
nbars merged 139 commits intomainfrom
dev
Apr 16, 2026
Merged

Dev#41
nbars merged 139 commits intomainfrom
dev

Conversation

@nbars
Copy link
Copy Markdown
Member

@nbars nbars commented Apr 16, 2026

No description provided.

Nils Bars added 30 commits December 20, 2025 16:50
- Replace requirements.txt with pyproject.toml in all Docker images
- Install uv in Dockerfiles before installing Python dependencies
- Use 'uv pip install --system' instead of 'pip install'
Coverage configuration enables automatic code coverage collection across
all Docker containers during test runs. Includes sitecustomize.py that
starts coverage when COVERAGE_PROCESS_START is set.

- Add coverage volume and environment variables to docker-compose
- Install coverage package in all Dockerfiles
- Copy sitecustomize.py for automatic coverage collection
- Propagate coverage environment to student containers
- Add shutdown delay for coverage data flush
Uses bridge_id template variable to create unique network bridge names
during test runs, preventing conflicts when running multiple test
instances in parallel.
Introduces a separate config_test.py with TestConfig class for running
unit tests outside the container environment. Uses descriptors to raise
RuntimeError when infrastructure-dependent config values are accessed,
helping identify code paths incompatible with standalone testing.
SFTP and non-interactive SSH sessions require a clean stdout channel
for protocol data. This change ensures the wrapper does not pollute
stdout with messages that would break these sessions.

- Redirect print_ok/warn/err functions to stderr
- Only print banner and welcome messages for interactive sessions
- Check sys.stdout.isatty() before printing user-facing messages
Introduces ref.core.logging with get_logger() that works both inside
Flask application context and in standalone environments. Replaces
direct LocalProxy(lambda: current_app.logger) usage across modules,
enabling unit testing without Flask being available.
- Use psycopg2-binary instead of psycopg2 for easier installation
- Add version to setup.py
Unit tests for error handling, exercise config parsing, security
module, SSH client, web client, and utility functions.
Run linting (ruff, pyright, mypy), unit tests, and E2E tests.
Upload coverage reports and failure logs as artifacts.
Load submission_tests as a Python module instead of running it as a
subprocess. This allows direct use of the run_tests() function from
ref_utils, eliminating the need for JSON file-based result passing.

Changes:
- submission_tests: Use @environment_test and @submission_test decorators
- task.py: Import and call run_tests() directly, capture output via TeeWriter
The create_submission method expects List[SubmissionTestResult] but was
being called with (int, str) arguments. Create a proper SubmissionTestResult
object with task_name='manual' for admin-created submissions.

Fixes #28
Replace generic message with actionable guidance explaining that users
should remove debug prints or reduce unnecessary output to stay within
the allowed limit.

Fixes #27
Generate a 6-character alphanumeric ID on first startup and store it
in the database. This ID is included in all Docker resource names
(containers, networks, images) to distinguish resources created by
different REF installations.

Resource naming changes from:
  ref-ressource-exercise-v1-entry-42
to:
  ref-a1b2c3-exercise-v1-entry-42

This enables detection and cleanup of orphan resources from old or
reset installations.

Closes #24
Add tee command to save build output to tests/build_logs/docker-build.log
and upload it as an artifact for debugging failed builds.
Initialize only the submodules required for testing (openssh-portable,
ref-utils, ace-builds) instead of cloning all submodules recursively.

Add REF_CI_RUN environment variable to skip cgroup freezer, cgroup
version checks, and ref-linux submodule requirement in CI.
- Remove unused imports and variables
- Replace bare `except:` clauses with `except Exception:`
- Replace lambda assignments with def statements (E731)
- Fix ambiguous variable names (l -> link, etc.)
- Add TYPE_CHECKING imports for forward references
- Use explicit re-export syntax for module __init__.py files
- Add noqa comments for SQLAlchemy comparisons (== None/True)
Add hooks/pre-commit that runs ruff check and ruff format --check,
rejecting commits that fail. Include hooks/install.sh for easy setup.
Run the full CI lint suite: ruff check, ruff format, pyright, and mypy.
- Add mypy config to tests/pyproject.toml with strict settings
- Add pyright config options for missing imports and lambda types
- Add type annotations to test fixture parameters in test_util.py
- Fix formatting in webapp/ref/model/exercise.py
Replace pyright with mypy as the sole type checker. Update documentation, CI workflow, and pre-commit hook to use only ruff and mypy.
The hook ensures main is always an ancestor of dev, so rebasing
dev onto main never requires a merge commit.
Nils Bars added 26 commits April 13, 2026 12:59
Extract administrative fields (category, deadlines, grading points,
submission test toggle) from Exercise into a new ExerciseConfig model.
All versions of an exercise share one ExerciseConfig row via config_id FK.
Exercise proxies the moved fields as properties for full backwards
compatibility.

Add web UI for editing ExerciseConfig (category, deadlines, grading,
short name rename) with date pickers. Add scoring_policy JSON column
for future scoreboard integration.

Deprecate admin fields in exercise YAML configs with inline warnings
in the importable list. Add graceful handling for pending migrations
on startup.
Rewrite prepare.py as a first-run initializer that generates settings.yaml
with cryptographically secure random secrets (ADMIN_PASSWORD, SECRET_KEY,
SSH_TO_WEB_KEY, POSTGRES_PASSWORD) using Python's secrets module, auto-detects
DOCKER_GROUP_ID, and renders settings.env from settings.yaml so docker-compose
keeps working unchanged. Refuses to run when settings.yaml already exists so
existing secrets are never silently overwritten.

ctrl.sh now bootstraps configuration by invoking prepare.py automatically on
the first run (when no settings files are present) and otherwise verifies that
all generated artifacts exist. settings.yaml is added to .gitignore so the
plaintext secrets cannot be committed by accident. README and template.env are
updated to point at the new flow.

Closes #11
…rading

Adds a GroupNameList model for admin-curated name pools, a System -> Group
Names page to manage them, and two system settings (GROUPS_ENABLED,
GROUP_SIZE) that gate the feature. When enabled, student registration
offers a datalist of names from enabled lists with current occupancy
(n/k), creating or joining a UserGroup under a row lock with a capacity
check. Admins can also assign users to groups from the edit page,
including names from enabled lists. Grading switches to one submission
per group via Exercise.submission_heads_by_group[_global](), falling back
to per-user behavior for ungrouped users. Seeds Raid and Fuzzing name
lists (32 each) via an Alembic migration.

Refs #14
The build thread detaches the Exercise plus its forward relationships so
attribute access during the long-running Docker build does not trigger a
lazy load that would reopen a transaction and hold the build advisory
lock. Extends that pattern to the config relationship added with
ExerciseConfig, otherwise touching exercise.config in __run_build raises
DetachedInstanceError.
Introduce a public scoreboard ported from the raid/raid prototype,
rebuilt on the current dev codebase as two independent extension points:

- Scoring policy per ExerciseConfig — linear (with configurable raw
  bounds), threshold, or tiered — applied server-side when the
  submissions API serves transformed scores.
- Ranking strategy — f1_time_weighted (Formula 1 time-weighted points)
  or best_sum (sum of each team's best per challenge). Selected at
  runtime via SCOREBOARD_RANKING_MODE; adding a third strategy is one
  JS module plus one entry in ref/core/scoring.py.
- Visual view — default (retro-terminal dark theme with Chart.js
  highscore panels, ranking table, points-over-time chart, per-challenge
  plots, live countdowns) or minimal (Bootstrap ranking table).
  Selected via SCOREBOARD_VIEW; adding a third view is one template plus
  one entry module.

Only assignments whose exercises are actually online (a built default
Exercise row exists) are exposed by /api/scoreboard/config. The default
view auto-selects the assignment whose submission window is currently
open, and preserves user assignment/challenge tab selections across the
5-second auto-refresh polls.

Team identity is group-aware: ranking rows show the UserGroup name when
GROUPS_ENABLED, otherwise the student's full name.

Site-wide navigation is unified via a new _navbar.html partial included
from student_base.html and admin_base.html, with Registration / Restore
Key / Scoreboard entries always visible and Admin / Grading / System
dropdowns gated on the user's authorization.

Adds a landing chooser page (SCOREBOARD_LANDING_PAGE = "chooser") that
shows the registration/restore buttons plus a live ranking preview.

Closes #12
Closes #13
Closes #15
Install ref-utils at /opt/ref-utils with `uv pip install -e .` and
bind-mount the host source read-only over it from the webapp so edits
apply inside instance containers without rebuilding the base image.
Document view/model/core module layouts, container tooling, ref-utils
exports, Docker network bridge names, ctrl.sh subcommands, test
directory structure, and CI pipeline.
The forked PySocks package was only used by the Python SSH proxy in
webapp/ref/proxy/, which was removed when the Rust SSH reverse proxy
replaced it.
…emand

prepare.py now has two modes: bootstrap (no settings.yaml) generates fresh
secrets as before, and re-render (settings.yaml exists) loads the existing
yaml and re-propagates it into settings.env and docker-compose.yml without
touching the secrets. Routine config edits are now "edit settings.yaml,
re-run ./prepare.py, ./ctrl.sh restart" instead of hand-syncing two files.
Pass --fresh to force regeneration; the existing yaml is moved to
settings.yaml.backup first.

Lift data_path, exercises_path, ref_utils_path, and binfmt_support from
hard-coded values in prepare.py into new paths and runtime sections of
settings.yaml, and parameterize the ref-utils bind-mount source in the
compose template via {{ ref_utils_path }}. load_settings_yaml backfills
these sections into older yamls and always re-emits the file so the
current schema, key order, and section comments propagate automatically.
The test harness passes an absolute ref_utils_path so its generated
compose still renders correctly.

Drop the dead debug / maintenance_enabled fields from settings.yaml and
settings.env — they were always overridden by ctrl.sh up's shell exports.
The compose template now uses ${DEBUG:-0} / ${MAINTENANCE_ENABLED:-0}
defaults so non-up commands (build, restart, logs) no longer need the
values in settings.env.

settings.yaml and settings.env are now self-documenting: each top-level
yaml section carries a preamble comment explaining its purpose, and each
env var gets a descriptive comment above it. docs/CONFIG.md documents the
full configuration pipeline: the three-file data flow, bootstrap vs
re-render, the yaml schema, secret rotation, test-harness divergence, and
the remaining gotchas. template.env (a redirect stub) and the obsolete
settings.env.backup gitignore entry are removed.
- Replace 'wave' with 'assignment' in SCOREBOARD.md and in comments
  and user-facing strings under webapp/ref/{view/api.py, core/scoring.py,
  model/exercise_config.py}.
- Add an "Optional Features" section to README.md describing the groups
  and scoreboard settings, both disabled by default and configured from
  the admin system-settings page.
- Move JSON endpoints out of `view/api.py` into `services_api/` (SSH
  proxy + container callbacks) and `frontend_api/` (SPA `/api/v2/*` +
  scoreboard). `view/build_status.py` hosts the admin build-status poll.
- Delete Flask-rendered registration, restore-key, scoreboard, and
  chooser landing pages along with their templates, static JS/CSS, and
  vendored chart assets; the SPA serves these under `/v2/*`.
- Drop `SCOREBOARD_VIEW` setting and the view resolver — only one
  scoreboard renderer exists.
- Redirect `/`, `/student`, and `/student/` straight to the configured
  SPA landing page; admin navbar drops its public student links.
- Rework the Vue scoreboard charts to plot only per-team improvements,
  use distinct marker shapes, and support drag-pan / wheel-zoom /
  shift-drag box-zoom via `chartjs-plugin-zoom`; clamp the x-axis at
  the earliest data point and preserve zoom state across data polls.
- Refresh `ARCHITECTURE.md`, `SCOREBOARD.md`, `CLAUDE.md`, and
  `README.md` to describe the current package layout.
Vue 3 + Vite + Vuetify app served under `/v2/`. Hosts the public
scoreboard, student registration, and key-restore flows backed by
`ref/frontend_api/`. Ships the ranking strategies, scoreboard
components, routing, theme tokens, and the Dockerfile/entrypoint used
by the `ref-spa-frontend` service.
- Add a `spa-frontend` service to the compose template that bind-mounts
  the host sources and toggles between `vite dev` and `vite preview`
  via `HOT_RELOADING`.
- Publish the container port through `SPA_HOST_PORT` (default 5173)
  and teach `prepare.py` to render it into `settings.env`.
- Extend `--hot-reloading` help in `ctrl.sh` to mention the SPA Vite HMR.
- Ignore `spa-frontend/node_modules/` and `spa-frontend/dist/`.
setuptools regenerates this directory on every install, so keeping it in
the tree just produces spurious diffs whenever `pyproject.toml` changes.
ExerciseConfig now carries a per_task_scoring_policies JSON column keyed
by submission-test task name instead of a single scoring_policy dict.
Task names are AST-discovered from each exercise's submission_tests
file, so the config editor shows exactly the tasks registered by the
test script. Submissions are scored with score_submission(), which
applies each task's policy independently and returns (total, per-task
breakdown); the scoreboard API exposes the breakdown to the SPA.

The public scoreboard drops the user-selectable ranking strategy: the
SCOREBOARD_RANKING_MODE setting and RANKING_STRATEGIES registry are
removed, and the SPA is hardwired to the best-sum strategy. The chart
samples the timeline at real event timestamps only, so every plotted
point corresponds to an actual submission or window edge.
- Swap chart.js, chartjs-adapter-date-fns, chartjs-plugin-annotation, and
  chartjs-plugin-zoom for echarts v6 in the SPA dependencies.
- Reimplement PointsOverTimeChart and ChallengePlot against the echarts
  API (line charts, markLine boundaries, built-in dataZoom slider).
- Read axis, grid, legend, tooltip, slider, and data palette colors from
  Vuetify --v-theme-* tokens and re-render via a MutationObserver when
  the body theme class flips.
- Give .term-chart-wrap an explicit height and use a flex child so the
  chart container reports a non-zero size to echarts.init().
- Extend the x-axis range to include assignment boundary markers so the
  dashed guide lines stay inside the viewport.
- Rotate assignment labels 90 degrees and center them along each vertical
  boundary line; center the baseline label on the horizontal markLine.
best_sum.computeChartScoresOverTime() used to inject a synthetic
{time: now, score: 0} point for any team present in the submission
map but without any events inside an assignment window. That placeholder
rendered as a stray zero data point in the chart. Omit such teams from
the result instead.

Also refresh docs/SCOREBOARD.md to describe the current ECharts-based
rendering, the theme-token color wiring, the assignment-boundary label
layout, and the x-axis range expansion that keeps boundary markers in
the viewport.
Tooltip and x-axis labels in the points-over-time and challenge plots now
use Intl.DateTimeFormat with the user's locale. The points-over-time chart
only renders assignment boundary markers for assignments whose start date
has already passed. Also fix pre-existing typecheck errors in the ECharts
series/markLine options and resolve a type-identity conflict between the
'echarts' and 'echarts/core' module declarations.
Adds a new frontend-proxy service (multi-stage Dockerfile: node builder
stage compiles the SPA bundle; caddy:2-alpine runtime stage serves it)
that terminates the host HTTP port and fans out over the existing
web-host network:

  /spa/*     -> spa-frontend:5173 (vite dev) in dev, baked /srv/spa-dist
               via file_server in prod
  /static/*  -> webapp/ref/static bind-mount served directly by Caddy
  /admin, /admin/ -> 302 to /admin/exercise/view
  /spa -> 308 /spa/
  everything else -> reverse_proxy web:8000 (X-Tinyproxy set from
               remote_host so the rate limiter keys on real client IP)

Dev/prod Caddyfiles are selected at container start via entrypoint.sh
based on $HOT_RELOADING. Prod adds Cache-Control headers: immutable +
long max-age on /spa/assets/*, no-cache on the SPA HTML fallback, 1h on
Flask static assets. Caddy's reverse_proxy auto-upgrades Vite's HMR
websocket for /spa/@vite/client.

The SPA URL prefix moves from /v2 to /spa (vite base, router history,
Flask landing redirects). The /api/v2/* JSON API path is unchanged.

The spa-frontend service is gated behind the 'dev' compose profile so
it only starts when hot-reloading is active; ctrl.sh exports
COMPOSE_PROFILES=dev by default and clears it in prod-mode up. The
prod-mode branch of spa-frontend/entrypoint.sh is now a hard error
since the prod bundle is baked into the frontend-proxy image.

web and spa-frontend no longer publish host ports; the test harness
maps frontend-proxy:8000 instead. SPA_HOST_PORT is removed from
settings.yaml/env and prepare.py. A repo-root .dockerignore keeps the
frontend-proxy build context lean.

UI:
- SPA DefaultLayout app bar gets an unobtrusive mdi-shield-account-outline
  icon button linking to /admin.
- Admin navbar gains a "Student View" link (school icon + label) with a
  pipe separator from the user name/logout group.
- Admin navbar brand (logo + course name) now links to the exercise view
  instead of the SPA landing.
SPA:
- DefaultLayout renders a yellow/black hazard-striped v-system-bar at
  the top of every page when import.meta.env.DEV is true, with the
  text "Served by vite dev with HMR — do not expose this instance
  publicly." The whole block is tree-shaken out of the production
  vite build.
- App.vue sets document.title from the nav store's courseName
  ("REF - <course>"), falling back to plain "REF" until hydrate
  finishes or if the API call fails.

Docs:
- README gains "Single-port web interface" and "Hot reloading (local
  development only)" sections spelling out that --hot-reloading runs
  vite dev behind Caddy and must never be used on a publicly
  reachable host, and that SPA source changes require ./ctrl.sh build
  in prod mode since the bundle is baked into the frontend-proxy
  image.
- docs/ARCHITECTURE.md adds /admin and /spa redirect notes, a
  Dev-mode security warning block covering the vite dev attack
  surface, a note on ctrl.sh's COMPOSE_PROFILES=dev wiring, and a
  warning box next to the --hot-reloading command reference.
- .claude/CLAUDE.md splits the web description into separate entries
  for the Flask webapp (internal port only) and the Caddy
  frontend-proxy, with the full routing table, profile gate, and
  security warning.
Registration generates ed25519 pairs but the Content-Disposition on the
student key downloads and the `download` attributes in KeyDownloadCard
were hardcoded to `id_rsa`, so users saved ed25519 keys under an RSA
filename. Add `ssh_key_basename()` in `ref.core.util` and a TS twin in
the SPA to pick the name ssh-keygen would default to (id_ed25519 /
id_ecdsa / id_rsa / id_dsa), and route both download endpoints and the
card through it. Also switch admin bootstrap to ed25519 so a freshly
seeded admin user matches the rest of the system.
Fix typos, clarify service descriptions (frontend-proxy routing,
SSH reverse proxy naming), and remove outdated wording.
The frontend-proxy (Caddy) now supports three TLS modes controlled by
tls.mode in settings.yaml:
- off: plain HTTP on a single port (previous behavior)
- internal: self-signed certificate with HTTPS + plain HTTP side-by-side
- acme: Let's Encrypt certificate via ACME with automatic renewal

The Caddyfile is rendered at container start from a Jinja2 template
(Caddyfile.prod.j2) by render_caddyfile.py based on environment
variables. Shared routing directives live in Caddyfile.routes, imported
by all prod configurations.

New settings: tls.mode, tls.domain (required when TLS is enabled),
tls.redirect_http_to_https, and ports.https_host_port. New installs
default to internal mode on ports 8080/8443. Existing configs are
backfilled to off mode preserving current port assignments.

A caddy-data Docker volume persists certificate state across restarts.
@gitguardian
Copy link
Copy Markdown

gitguardian Bot commented Apr 16, 2026

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
23606779 Triggered OpenSSH Private Key fa33740 container-keys/user_key View secret
23606780 Triggered OpenSSH Private Key fa33740 container-keys/root_key View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Comment thread .github/workflows/ci.yml
Comment on lines +14 to +49
name: Lint & Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Initialize submodules (excluding ref-linux)
run: |
git submodule update --init ref-docker-base/ref-utils
git submodule update --init webapp/ref/static/ace-builds

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Set up Python
run: uv python install ${{ env.PYTHON_VERSION }}

- name: Install linting tools
run: |
uv tool install ruff
uv tool install mypy

- name: Install test dependencies (for mypy)
working-directory: tests
run: uv sync

- name: Run ruff check
run: ruff check .

- name: Run ruff format check
run: ruff format --check .

- name: Run mypy
working-directory: tests
run: uv run mypy .

unit-tests:
Comment thread .github/workflows/ci.yml
Comment on lines +50 to +74
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Initialize submodules (excluding ref-linux)
run: |
git submodule update --init ref-docker-base/ref-utils
git submodule update --init webapp/ref/static/ace-builds

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Set up Python
run: uv python install ${{ env.PYTHON_VERSION }}

- name: Install test dependencies
working-directory: tests
run: uv sync

- name: Run unit tests
working-directory: tests
run: uv run pytest unit/ -v -m "not slow"

e2e-tests:
Comment thread .github/workflows/ci.yml
Comment on lines +75 to +188
name: E2E Tests
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- name: Initialize submodules (excluding ref-linux)
run: |
git submodule update --init ref-docker-base/ref-utils
git submodule update --init webapp/ref/static/ace-builds

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y jq

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Set up Python
run: uv python install ${{ env.PYTHON_VERSION }}

- name: Install Python dependencies for ctrl.sh
run: pip install jinja2

- name: Create settings.env
run: |
DOCKER_GID=$(getent group docker | cut -d: -f3)
cat > settings.env << EOF
DEBUG=1
MAINTENANCE_ENABLED=0
ADMIN_PASSWORD=TestAdmin123!
DOCKER_GROUP_ID=${DOCKER_GID}
SSH_HOST_PORT=2222
HTTP_HOST_PORT=8000
SECRET_KEY=TestSecretKeyForCI12345
SSH_TO_WEB_KEY=TestSSHToWebKeyForCI
POSTGRES_PASSWORD=TestPostgresPassword123!
EOF
# Remove leading whitespace from each line
sed -i 's/^[[:space:]]*//' settings.env

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-

- name: Build Docker images
run: |
mkdir -p tests/build_logs
export REF_CI_RUN=1
./ctrl.sh build 2>&1 | tee tests/build_logs/docker-build.log
exit ${PIPESTATUS[0]}

- name: Install test dependencies
working-directory: tests
run: uv sync

- name: Run E2E tests
working-directory: tests
run: uv run pytest e2e/ -v --timeout=300

- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: tests/coverage_reports/
retention-days: 7

- name: Generate failure log summary
if: failure()
working-directory: tests
run: |
if [ -d "failure_logs" ]; then
uv run python summarize_logs.py || true
fi

- name: Upload failure logs
uses: actions/upload-artifact@v4
if: failure()
with:
name: failure-logs
path: tests/failure_logs/
retention-days: 7

- name: Upload container logs on failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: container-logs
path: tests/container_logs/
retention-days: 7

- name: Upload build logs
uses: actions/upload-artifact@v4
if: always()
with:
name: build-logs
path: tests/build_logs/
retention-days: 7

- name: Cleanup Docker resources
if: always()
run: |
docker ps -aq --filter "name=ref_test_" | xargs -r docker rm -f || true
docker network ls -q --filter "name=ref_test_" | xargs -r docker network rm || true
docker volume ls -q --filter "name=ref_test_" | xargs -r docker volume rm || true
Comment thread prepare.py
f"POSTGRES_PASSWORD={settings['secrets']['postgres_password']}",
"",
]
SETTINGS_ENV.write_text("\n".join(lines))
Comment thread prepare.py
print()
print("Admin credentials for first login:")
print(" user: 0")
print(f" password: {settings['admin']['password']}")

# Connect via Rust proxy
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# Connect via Rust proxy
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# Connect via Rust proxy
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
pkey = paramiko.Ed25519Key.from_private_key(key_file)

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
pkey = paramiko.Ed25519Key.from_private_key(key_file)

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
invalid_key = paramiko.RSAKey.generate(2048)

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
valid_key = paramiko.Ed25519Key.from_private_key(key_file)

valid_client = paramiko.SSHClient()
valid_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
pkey = paramiko.Ed25519Key.from_private_key(key_file)

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
@nbars nbars merged commit caaa410 into main Apr 16, 2026
3 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants