Skip to content

Bundle Python 3.10 for older Ubuntu support - #157

Open
rtibbles wants to merge 4 commits into
mainfrom
no_eol_python
Open

Bundle Python 3.10 for older Ubuntu support#157
rtibbles wants to merge 4 commits into
mainfrom
no_eol_python

Conversation

@rtibbles

@rtibbles rtibbles commented Mar 27, 2026

Copy link
Copy Markdown
Member

Summary

Bundles Python 3.10 from python-build-standalone in the .deb so Kolibri can run on Ubuntu 16.04–20.04 without depending on the system Python version.

Both x86_64 and aarch64 tarballs are included (~56 MB total). At install time, if system Python is already >= 3.10, the bundled tarballs are ignored.

Tested installation on Ubuntu 16.04 (Python 3.5) via distrobox — bundled Python 3.10.20 extracted and verified working, Kolibri service resolves to the correct interpreter, purge cleans up completely.

References

In support of #141 (Python availability only; kolibri-server merge is a separate effort)

Reviewer guidance

  • The build now requires make get-python which downloads ~56 MB of Python tarballs. This runs automatically as a prerequisite of make kolibri.deb and make kolibri.changes.
  • Key decision: python3 moved from Depends to Recommends (>= 3.10). The package no longer requires system Python to install.
  • debian/startup/kolibri.init:44-47 — when bundled Python is active, KOLIBRI_COMMAND is overridden to $KOLIBRI_PYTHON -m kolibri. Worth verifying this works correctly on a real system with systemd.
  • debian/install-python.sh — the install-time extraction script. Check the arch detection and SHA256 verification logic.

Open questions:

  • This adds 56MB to the Debian package - should we reserve that for only the completely offline installable packages, and make architecture/Ubuntu version specific DEB files in the PPA? I went for this as it made everything simpler overall, but it does add overhead to every download.

AI usage

Built collaboratively with Claude Code (Opus 4.6). Design brainstormed interactively, then implemented task-by-task. All code reviewed, tested via distrobox on Ubuntu 16.04, and commit history cleaned up manually.

@rtibbles
rtibbles force-pushed the no_eol_python branch 10 times, most recently from 7f2be83 to c8faa92 Compare March 27, 2026 21:53
@rtibbles
rtibbles marked this pull request as ready for review March 27, 2026 22:06
@rtibbles
rtibbles requested a review from rtibblesbot March 27, 2026 22:19

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well-structured approach to bundling Python 3.10 for older Ubuntu. The layered resolution (user config → bundled → system), SHA256 verification at both build and install time, and the kolibri-wrapper.sh replacing the setuptools entry point are all solid design choices.

CI passing across Ubuntu 16.04–24.04. No UI files changed — visual inspection skipped.

  • suggestion: install-python.sh architecture handling could be more graceful on unsupported arches
  • suggestion: python3-pkg-resources removal — confirm no runtime dependency
  • praise: Checksum verification, Python resolution chain, and add_state reordering are all well done

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Reviewed the pull request diff checking for:

  • Correctness: bugs, edge cases, undocumented behavior, resource leaks, hardcoded values
  • Design: unnecessary complexity, naming, readability, comment accuracy, redundant state
  • Architecture: duplicated concerns, minimal interfaces, composition over inheritance
  • Testing: behavior-based assertions, mocks only at hard boundaries, accurate coverage
  • Completeness: missing dependencies, unupdated usages, i18n, accessibility, security
  • Principles: DRY (same reason to change), SRP, Rule of Three (no premature abstraction)
  • Checked CI status and linked issue acceptance criteria
  • For UI changes: inspected screenshots for layout, visual completeness, and consistency

Comment thread debian/install-python.sh Outdated
;;
*)
echo "install-python.sh: Unsupported architecture: $DPKG_ARCH" >&2
exit 1

@rtibblesbot rtibblesbot Mar 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

suggestion: On architectures other than amd64/arm64 where system Python is below the minimum, this exit 1 will fail the postinst and break package installation. Since the package is Architecture: all, it could be installed on armhf or i386 systems.

Consider exit 0 with a warning instead, letting the system fall back to whatever Python is available — Kolibri may degrade gracefully rather than refusing to install entirely. Alternatively, if the intent is that Kolibri truly cannot run without Python 3.10, this is fine, but worth documenting in the package description.

Comment thread debian/control
Recommends: python3-cryptography (>= 1.2.3)
Depends: adduser
Recommends: python3 (>= 3.10),
python3-cryptography (>= 1.2.3)

@rtibblesbot rtibblesbot Mar 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

suggestion: The previous Depends included python3-pkg-resources. With it now fully removed (not moved to Recommends), could Kolibri fail at runtime if pkg_resources is imported? The wrapper avoids the setuptools console_scripts entry point, but Kolibri internals or its vendored dependencies may still use pkg_resources. Worth confirming this isn't needed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kolibri already bundles this internally when needed.


# Resolve the Python interpreter to use for Kolibri.
# Priority: 1) KOLIBRI_PYTHON from daemon.conf/conf.d, 2) /opt/kolibri/python, 3) system python3
if [ -z "$KOLIBRI_PYTHON" ]; then

@rtibblesbot rtibblesbot Mar 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

praise: Clean resolution chain with well-documented priority (user config → bundled → system). The comment on line 69 makes the intent immediately clear, and the fallback to python3 ensures the package degrades gracefully when bundled Python isn't needed.

Comment thread build_tools/download_python.sh Outdated
if [ -f "$dest" ]; then
echo "Already downloaded: $dest"
local actual_sha256
actual_sha256=$(sha256sum "$dest" | cut -d' ' -f1)

@rtibblesbot rtibblesbot Mar 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

praise: Good defensive pattern — verifying checksums on both cache hits (lines 57–64) and fresh downloads (lines 73–79), with automatic re-download on mismatch. This prevents stale or corrupted tarballs from propagating into builds.

echo -n "$KOLIBRI_USER" > /etc/kolibri/username
fi

add_state "after_postinst"

@rtibblesbot rtibblesbot Mar 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

praise: Moving add_state "after_postinst" before service registration is a good robustness improvement. If systemctl enable or update-rc.d fails (e.g., in containers without systemd), the package state is still correctly recorded.

Comment thread debian/kolibri-wrapper.sh

# Source the kolibri environment if available (sets KOLIBRI_PYTHON, PYTHONPATH, etc.)
if [ -f /etc/default/kolibri ]; then
set -o allexport

@rtibblesbot rtibblesbot Mar 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

suggestion: The wrapper sources /etc/default/kolibri to resolve KOLIBRI_PYTHON. When invoked via the init script, /etc/default/kolibri has already been sourced and the environment exported. The wrapper then re-sources it, which is harmless but redundant for the init path. For the direct CLI path (user runs kolibri from terminal), this sourcing is essential.

No change needed — just noting that this dual-sourcing is intentional and safe, since the file is idempotent.

@bjester bjester left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks complete. Some areas for improvement:

  • have the download script ensure it has checksums to check the download with before downloading
  • use source instead of . in scripts
  • consider the tradeoffs of using checksum files and sha256sum -c instead-- may not be of benefit because of the rich error logging

Comment thread build_tools/download_python.sh Outdated
Comment on lines +16 to +29
ARCH=""

while [[ $# -gt 0 ]]; do
case "$1" in
--arch)
ARCH="$2"
shift 2
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
done

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there's only one argument, there minimal benefit of the named argument versus a positional argument.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gone!

Comment thread build_tools/download_python.sh Outdated
Comment on lines +84 to +88
download_and_verify "x86_64" "$PYTHON_SHA256_X86_64"
fi

if [ -z "$ARCH" ] || [ "$ARCH" = "aarch64" ]; then
download_and_verify "aarch64" "$PYTHON_SHA256_AARCH64"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering these env vars are sourced, it'd be helpful if there was a comment, and perhaps even more helpful if these checked whether the checksum is even set. Why go through with the download when you know it'll fail without a checksum to check it with!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a check for the env vars before doing the download and verify.

Comment thread build_tools/download_python.sh Outdated
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

. "$SCRIPT_DIR/python_versions.conf"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't totally uncommon for .conf to be used for this use case, but it does feel odd to me. Since this file is intended to just hold environment variables, I think .env is clearer.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to an .env file.

Comment thread build_tools/update_python_version.sh Outdated
Comment on lines +41 to +70
python3 -c "
import json, sys
target = '${TARGET}'
releases = json.load(sys.stdin)
for r in releases:
urls = {}
python_version = None
full_version = None
for a in r.get('assets', []):
name = a['name']
if f'cpython-{target}' not in name:
continue
if 'install_only_stripped' not in name:
continue
if 'unknown-linux-gnu' not in name:
continue
if python_version is None:
# Extract e.g. '3.10.20' and '3.10.20+20260325'
full_version = name.split('-')[1]
python_version = full_version.split('+')[0]
# Match plain x86_64 (not v2/v3/v4) and aarch64
if 'x86_64-unknown' in name:
urls['x86_64'] = a['browser_download_url']
elif 'aarch64-unknown' in name:
urls['aarch64'] = a['browser_download_url']
if python_version and 'x86_64' in urls and 'aarch64' in urls:
print(f\"{r['tag_name']}|{python_version}|{full_version}|{urls['x86_64']}|{urls['aarch64']}\")
sys.exit(0)
print('NOT_FOUND', file=sys.stderr)
sys.exit(1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oof.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have updated all the new build time scripting to Python to avoid these shenanigans.

Comment thread debian/install-python.sh Outdated
exit 0
fi

. "$CONF_FILE"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

. is quick and dirty, for when you're in the terminal. In scripts, source is better IMO

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

Comment thread .github/workflows/install_test.yml Outdated
fi
sleep 2
done
echo "FAIL: Kolibri did not respond on port 8080 within 120 seconds"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's localhost so it shouldn't but what if the curl call hangs? this comment shows an assumption, but what if it waits for some 'timeout' period?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a 5 second timeout on every loop above.

@rtibbles
rtibbles force-pushed the no_eol_python branch 3 times, most recently from 23d3a63 to 33fea47 Compare March 31, 2026 03:22

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prior findings

Resolved:

  • Exit 1 on unknown arch in debian/install-python.sh (suggestion) — code now exits 0 with "Falling back to system Python."

Acknowledged (not re-raised):

  • python3-pkg-resources removal from Depends (suggestion) — author replied: "Kolibri already bundles this internally when needed."

2/2 prior findings resolved or acknowledged. 0 re-raised.


CI passing. No UI files changed.

  • praise: see inline on build_tools/manage_python.py
  • suggestion: see inline on build_tools/manage_python.py

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

for arch in arches:
sha_key = f"PYTHON_SHA256_{arch.upper()}"
expected_sha = config.get(sha_key)
if not expected_sha:

@rtibblesbot rtibblesbot May 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

praise: Good defensive check — failing fast with a clear error before making a network request is much better than downloading 28 MB only to discover the checksum is missing. Addresses bjester's prior concern cleanly.

Comment thread build_tools/manage_python.py Outdated

# Download and compute checksums
checksums = {}
with tempfile.TemporaryDirectory() as tmpdir:

@rtibblesbot rtibblesbot May 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

suggestion: cmd_update downloads both tarballs (~56 MB) to a temp directory just to compute checksums, then discards them. After running make update-python, the developer must run make get-python to download the same 56 MB again for the actual build.

Consider writing directly to build_src/ during update — that directory is where cmd_download already puts them. You could reuse the same download-and-verify loop from cmd_download, just seeded with the freshly-fetched URLs and newly-computed checksums.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — cmd_update now writes directly to build_src/ using the same tarball_filename(full_version, arch) naming cmd_download expects, so a subsequent make get-python hits the cache and verifies instead of re-downloading.

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prior findings

All 1 prior finding resolved: cmd_update double-download suggestion — code now writes directly to build_src/ using tarball_filename(full_version, arch), matching cmd_download's naming. 0 re-raised.


Clean additions — smoke tests and KOLIBRI_PYTHON resolution are both well-structured. CI passing. No UI files changed.

  • praise: see inline on install_test.yml and debian/startup/kolibri.default
  • nitpick: see inline on install_test.yml

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

name: Install test (${{ matrix.image }})
runs-on: ubuntu-latest
strategy:
fail-fast: false

@rtibblesbot rtibblesbot May 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

praise: fail-fast: false is the right choice for a smoke test — ensures all five Ubuntu versions are tested independently so a failure on 16.04 doesn't hide a separate failure on 22.04.

Comment thread .github/workflows/install_test.yml Outdated
fi
sleep 2
done
echo "FAIL: Kolibri did not respond on port 8080 within 120 seconds"

@rtibblesbot rtibblesbot May 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

nitpick: The failure message says "within 120 seconds" but the actual max wait is ~420 seconds (60 iterations × up to 5s curl timeout + 2s sleep). Could say "within 2 minutes of sleep time" or just drop the time estimate.

# When using a non-system Python, the bundled Python needs access to
# Kolibri installed in system dist-packages
if [ "$KOLIBRI_PYTHON" != "python3" ] && [ -n "$KOLIBRI_PYTHON" ]; then
PYTHONPATH="/usr/lib/python3/dist-packages${PYTHONPATH:+:$PYTHONPATH}"

@rtibblesbot rtibblesbot May 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved — addressed in the current code.

praise: ${PYTHONPATH:+:$PYTHONPATH} correctly handles both the set and unset cases — prepends a colon only when PYTHONPATH is already non-empty, avoiding a spurious leading colon that would add an implicit . to the search path.

@rtibblesbot

rtibblesbot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

🔵 Review posted

Last updated: 2026-07-22 00:03 UTC

@rtibblesbot rtibblesbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #157 — All prior findings resolved or acknowledged; the runtime path (init → wrapper → bundled/system Python) is coherent and covered by the new 16.04–24.04 smoke test. CI passing. Two new findings on the new build_tools/manage_python.py, both inline.

  • importantmanage_python.py ships with no tests despite the sibling generate_changelog.py/tests/ convention. Its pure functions (tarball_filename, tarball_url, read_config/write_config, find_release) are prime unit-test candidates.
  • suggestion — unanchored version-prefix match in find_release could resolve 3.1 to a 3.10/3.11 tarball.

No UI files changed — Phase 3 (UI verification / manual QA) not applicable.

Prior-finding status

RESOLVED — debian/install-python.sh — exit 1 on unknown arch (now falls back to system Python)
ACKNOWLEDGED — debian/control:22 — python3-pkg-resources removal (Kolibri bundles internally)
RESOLVED — debian/startup/kolibri.default:70 — resolution-chain documentation
RESOLVED — build_tools/download_python.sh — checksum/source/.env concerns (rewritten to manage_python.py)
RESOLVED — debian/kolibri.scripts-common:285 — add_state ordering
RESOLVED — debian/kolibri-wrapper.sh:9 — wrapper re-sources /etc/default/kolibri under su
RESOLVED — build_tools/manage_python.py:143 — fail-fast before network request
RESOLVED — build_tools/manage_python.py — cmd_update double-download (now writes to build_src/)
RESOLVED — .github/workflows/install_test.yml:16 — fail-fast: false
RESOLVED — .github/workflows/install_test.yml — retry-wait wording (now "after 60 attempts")
RESOLVED — debian/startup/kolibri.default:81 — PYTHONPATH prepend handling


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews via the GitHub API
  • Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Ran the same phased review passes as a first review (core, frontend/backend lenses, manual QA when required)
  • Synthesized one review from the passes and chose the verdict from the findings, CI status, and QA evidence

@@ -0,0 +1,295 @@
#!/usr/bin/env python3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important: This new 295-line module ships with no tests. The sibling build_tools/generate_changelog.py already establishes the convention — tests/test_generate_changelog.py unit-tests its pure functions and uses a VCR cassette for the GitHub-API call. manage_python.py has the same shape: tarball_filename, tarball_url (esp. the +%2B encoding branch), read_config/write_config round-trip, and find_release (fixture-driven from a list of release dicts — the existing tests/cassettes/releases_full.yaml shows the team already captures /releases responses) are all directly testable with no network. Please add a test file following that pattern.

Comment thread build_tools/manage_python.py Outdated
urls = {}
for arch in SUPPORTED_ARCHES:
for name, url in assets.items():
if name.startswith(f"cpython-{target}") and name.endswith(f"-{arch}{suffix}"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The version match name.startswith(f"cpython-{target}") (and the matched_name selection on line 191) isn't anchored on the version boundary. target is validated only as ^3\.\d+$, so cpython-3.1 is a prefix of cpython-3.10.20+… — requesting 3.1 would silently resolve to a 3.10/3.11 tarball (same class of collision for a future 3.10 vs 3.100). Since the filename always has a patch component, anchoring on the trailing dot fixes it cheaply:

name.startswith(f"cpython-{target}.")

Low severity — current Kolibri targets (≥3.10) don't collide — but the guard is nearly free and removes a silent-wrong-version footgun.

rtibbles and others added 4 commits July 27, 2026 15:44
Add scripts and configuration to download and manage Python 3.10
from python-build-standalone for bundling in the .deb:

- python_versions.conf: pins version, release tag, and SHA256 checksums
- download_python.sh: downloads and verifies tarballs for x86_64/aarch64
- update_python_version.sh: developer tool to bump Python version
- Makefile: get-python and update-python targets, get-python as
  prerequisite for deb and changes builds, fix globs to not match
  cpython tarballs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Package now includes python-build-standalone tarballs for x86_64 and
aarch64. At install time, if system Python is below 3.10, the matching
tarball is extracted to /opt/kolibri/python/.

- install-python.sh: install-time script to verify and extract Python
- debian/rules: installs bundled artifacts to /opt/kolibri/bundled/
- build.sh: copies tarballs and config into source tree during build
- postinst: calls install-python.sh during configure
- postrm: cleans up /opt/kolibri/ on purge
- control: move python3 to Recommends (>= 3.10) since we bundle our own

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
At service start, resolve the Python interpreter:
1. KOLIBRI_PYTHON from daemon.conf (user override)
2. /opt/kolibri/python/bin/python3 (package-installed)
3. system python3 (fallback)

When using a non-system Python, the init script overrides
KOLIBRI_COMMAND to use '$KOLIBRI_PYTHON -m kolibri' instead of
the system 'kolibri' entry point.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Test that the .deb installs correctly and Kolibri starts on all
supported Ubuntu versions. Verifies Python >= 3.10 is available
and Kolibri responds on port 8080.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

3 participants