• v0.4.0 a5c1b519ed

    v0.4.0
    Release / release (push) Successful in 1m10s
    Test / test (push) Successful in 1m0s
    Stable

    petrbalvin released this 2026-07-26 16:19:36 +00:00 | 5 commits to main since this release

    Added

    • volumen init — a new init subcommand is the operational
      bootstrap (config, data dirs, admin password, optional systemd unit). Run
      as root for a system install (default --systemd); pass --local for a
      per-user install (~/.config/volumen/, ~/.local/share/volumen/). The
      command renders the production-hardened config from the same commented
      TOML template (host = "::1", env = "production", trust_proxy = true,
      cookie_secure = true), prompts for an admin password (getpass), hashes
      it via the existing scrypt helper, generates a 64-byte hex session key,
      creates the system user (useradd --system) and the /etc/volumen/ /
      /var/lib/volumen/ layout, and (with --systemd) installs a hardened
      /etc/systemd/system/volumen.service and runs
      systemctl daemon-reload && systemctl enable --now.
    • PyPI publish metadatapyproject.toml gains readme, keywords,
      a complete classifiers array (Development Status, Framework :: FastAPI,
      supported Python versions, OS, Topic, Typing), and a [project.urls]
      block (Homepage, Repository, Issues, Changelog, Documentation). PyPI now
      renders the README on the project page and links back to sourcedock.dev.
    • volumen status — read-only inspection of an installation. Prints
      human output by default or --json for tooling; reports config_exists,
      data_dir_exists, users_file_exists, password_hash_set,
      session_key_ok, systemd_unit_installed, service_active,
      admin_user_present, and any issues found. Exit code is 1 when any
      issue is reported.
    • volumen check-update — compares the installed version (read via
      importlib.metadata) with the latest release on PyPI. Uses only
      urllib.request (stdlib, 5 s timeout); exit code is 0 up-to-date,
      1 update available (suggests uv tool upgrade volumen), 2 PyPI
      unreachable / offline / malformed response. --json returns the same
      fields plus checked_at for cron-friendly alerting.
    • src/volumen/installer.py — a new module exposing system_install,
      user_install, inspect, plus the private _mutate_config_text helper
      used to apply line-anchored substitutions to the rendered TOML template
      (preserves comment blocks byte-for-byte).
    • Teststests/test_installer.py adds 15 tests covering both install
      flows, the config-template mutator, the systemd-unit rendering,
      idempotency / backup behaviour, the inspect round-trip, and the
      --systemd + --local CLI guard.

    Changed

    • First-run setup is now volumen init — replaces the legacy
      install.sh. The CLI subcommand renders config, creates data dirs,
      prompts for an admin password, generates a session key, and (with
      --systemd) installs and enables a hardened systemd unit. No bash
      required.
    • Publishing now targets PyPIrelease.yml runs
      uv publish --token "$PYPI_TOKEN" after python -m build for v*
      tags. Wheel + sdist continue to be uploaded to sourcedock.dev Releases as a
      secondary mirror. End users get uv tool install volumen and
      pipx install volumen as the one-line install path; no checkout
      required.
    • Distribution path is uv tool install (or pipx install) — the
      CLI binary is installed globally; the operational bootstrap is delegated
      to volumen init. The legacy uv sync + project-local venv at
      /opt/volumen/.venv is gone.
    • Config.load round-trips config-file content_dir / users_file
      the rendered template places these two keys after the [server] header,
      so TOML table folding puts them inside [server]. Config.load now
      hoists them to the root so files produced by volumen init (and any
      hand-edited config) round-trip through the canonical accessors without
      relying on CLI overrides.
    • Migrated from Ruby to Python: the runtime has been rewritten on top of
      FastAPI + Uvicorn, replacing Sinatra with FastAPI, kramdown with
      python-markdown + pymdown-extensions, Puma with Uvicorn, Bundler with uv,
      minitest with pytest, RuboCop with Ruff, and the TOML parser
      (toml-rb) with the standard library tomllib (plus tomli-w for
      round-tripping edits). The public API (/api/volumen/*) and the
      server-rendered admin (/admin/*) keep the same shape and HTTP semantics.
    • Installerinstall.sh now bootstraps uv instead of Bundler, runs
      uv sync --frozen to provision /opt/volumen/.venv, and points the
      systemd ExecStart at the venv-resident volumen console script. The
      Python runtime is auto-installed on Fedora/openEuler; other distros get a
      heredoc with the equivalent apt / pacman / zypper / apk lines.
    • CI — Forgejo Actions moved to setup-uv + Ruff + pytest on a single
      Python 3.12 job (was: Ruby 3.3/3.4 matrix with Bundler, RuboCop, and
      bundle exec rake test). The release workflow builds wheel + sdist via
      python -m build and uploads them with the Forgejo Releases API.
    • Migration guard — new .forgejo/workflows/migration-guard.yml greps
      the tracked tree for Ruby-only terms (bundle exec, RuboCop, Sinatra,
      Puma, kramdown, gem build, Volumen::, Rack::Session::Cookie,
      OpenSSL::KDF, lib/volumen/) and fails the build if any of them leak
      back into production paths.
    • Configurationconfig.toml gains six new keys surfaced in
      config.toml.example: [server].env ("production" enables strict
      startup), [server].trust_proxy (honour X-Forwarded-Proto from
      nginx), [admin].min_password_length (default 10),
      [admin].max_password_length (default 1024, caps scrypt work),
      [admin].max_upload_bytes (default 10485760, 10 MB), and
      [admin].cookie_secure (default false in dev, set true behind
      HTTPS). See docs/configuration.md for the full schema.
    • Default bind address — the installer and config.toml.example
      default to host = "::" (IPv6 with automatic IPv4 fallback) so the
      service is reachable on both stacks out of the box. Bind to "::1"
      when running behind a reverse proxy.
    • Test client now uses httpx2 — dev dependencies replace
      httpx>=0.28 with httpx2>=2.7 (the actively maintained fork by
      Pydantic). Starlette 1.3.x prefers httpx2 in TestClient and emits
      StarletteDeprecationWarning when only legacy httpx is installed;
      shipping httpx2 keeps the test suite warning-free. The legacy
      httpx package is still pulled in via fastapi[standard] for the
      fastapi CLI and remains API-compatible. The unused
      ignore::DeprecationWarning:starlette.testclient filter is removed
      from [tool.pytest.ini_options] (it targeted the wrong warning
      class and never matched).

    Removed

    • install.sh — the bash installer is deleted. Use uv tool install volumen (or pipx install volumen) followed by volumen init.
    • pkg/ build output and volumen.gem (Ruby gem packaging is gone; the
      project now ships as a wheel + sdist built with python -m build).
    • Gemfile, volumen.gemspec, Rakefile, .rubocop.yml and the
      Sinatra-era lib/ tree.
    • .editorconfig — Ruff (pyproject.toml's [tool.ruff]) is the single
      source of truth for Python formatting; modern editors default to UTF-8,
      LF, and a trailing newline. Markdown files in this repo use blank lines
      between paragraphs (no \n line-break idiom), so trimming trailing
      whitespace is safe.

    Security

    • scrypt password hashing now uses hashlib.scrypt and constant-time
      verification via secrets.compare_digest (was: Ruby's OpenSSL::KDF).
    • Session cookies are signed by Starlette's SessionMiddleware
      (itsdangerous) with HttpOnly and SameSite=Strict; Secure is added
      by SecureCookieMiddleware when the request was forwarded over HTTPS
      (only when [server].trust_proxy = true).
    • CSRF tokens are generated with secrets.token_hex(32) and verified
      with secrets.compare_digest on every state-changing admin POST.
    • Markdown sanitisation policy — the renderer still passes inline HTML
      through (authors are the trusted admin); document the trust model
      explicitly in docs/security.md so deployments that ingest untrusted
      content know to add a sanitiser.

    Note: refine the Security bullets above once the Python agent confirms the
    final cryptographic controls (e.g. whether image/* upload signatures get
    re-validated server-side and whether the CSRF token is rotated on login).

    Downloads