Skip to content
Draft
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
30 changes: 30 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,36 @@ distro-ubuntu-deb: ## build Ubuntu .deb package locally (output to distro/deb/)
xargs ls -t | head -1 | xargs -I{} cp -v {} $(DIR_DISTRO_DEB)/
@echo "DONE: .deb package in file://$(CURDIR)/$(DIR_DISTRO_DEB)"

#
# TEST: Ubuntu .deb, release by release, in Docker
#
# Each target builds the .deb ON the target Ubuntu release, installs it
# (postinst provisions uv + Python 3.12), and leaves MyTraL running on
# http://localhost:8888 for manual testing. Scoped to the legacy releases that
# predate Python 3.12 and use debhelper compat 9 (the #89 concern); modern
# releases are covered by the canonical packaging and distro-docker-debian.
#

.PHONY: test-ubuntu-trusty
test-ubuntu-trusty: ## build+install the .deb on Ubuntu 14.04 trusty in Docker and run MyTraL (http://localhost:8888)
@./build/docker/ubuntu/build.sh trusty
@./build/docker/ubuntu/run.sh trusty

.PHONY: test-ubuntu-xenial
test-ubuntu-xenial: ## build+install the .deb on Ubuntu 16.04 xenial in Docker and run MyTraL (http://localhost:8888)
@./build/docker/ubuntu/build.sh xenial
@./build/docker/ubuntu/run.sh xenial

.PHONY: test-ubuntu-bionic
test-ubuntu-bionic: ## build+install the .deb on Ubuntu 18.04 bionic in Docker and run MyTraL (http://localhost:8888)
@./build/docker/ubuntu/build.sh bionic
@./build/docker/ubuntu/run.sh bionic

.PHONY: test-ubuntu-focal
test-ubuntu-focal: ## build+install the .deb on Ubuntu 20.04 focal in Docker and run MyTraL (http://localhost:8888)
@./build/docker/ubuntu/build.sh focal
@./build/docker/ubuntu/run.sh focal

#
# DISTRIBUTION: desktop application
#
Expand Down
113 changes: 113 additions & 0 deletions build/docker/ubuntu/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Copyright (C) 2015-2026 Martin Dvorak <martin.dvorak@mindforger.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# MyTraL per-Ubuntu-release test image.
#
# Faithful end-to-end test of the Ubuntu .deb on one specific release:
# 1. build the .deb ON that release (uv provisions Python 3.12 for the wheel),
# 2. install the .deb (postinst provisions uv + Python 3.12, installs the app),
# 3. leave MyTraL ready to run for manual testing.
#
# Everything happens at image-build time, so a failure on the chosen release
# (build, TLS to GitHub, glibc, ...) fails the build loudly. Pick the release
# with --build-arg UBUNTU_RELEASE=<codename> (see build.sh / run.sh).
#
# Build context: a clean copy of the MyTraL source tree with the old-Ubuntu
# debian/ overlay applied at the root (see build.sh).

ARG UBUNTU_RELEASE=trusty
FROM ubuntu:${UBUNTU_RELEASE}

LABEL org.opencontainers.image.title="MyTraL"
LABEL org.opencontainers.image.description="My training log — personal sport training log web application"
LABEL org.opencontainers.image.licenses="AGPL-3.0"
LABEL org.opencontainers.image.source="https://github.com/dvorka-oss/mytral"

ENV DEBIAN_FRONTEND=noninteractive

# pinned uv release — must match debian/postinst
ENV UV_VERSION=0.7.8

#
# Install the Debian packaging toolchain (works back to trusty's debhelper 9)
#
COPY build/docker/ubuntu/apt-setup.sh /tmp/apt-setup.sh
RUN sh /tmp/apt-setup.sh && \
apt-get install -y --no-install-recommends \
build-essential \
debhelper \
devscripts \
dpkg-dev \
fakeroot \
curl \
ca-certificates \
xz-utils \
&& rm -rf /var/lib/apt/lists/*

#
# Install uv — fetched straight from GitHub releases, the same source and
# version debian/postinst uses, so the build exercises the same TLS path.
#
RUN curl -LsSf \
"https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-x86_64-unknown-linux-gnu.tar.gz" \
| tar -xz -C /usr/local/bin --strip-components=1 uv-x86_64-unknown-linux-gnu/uv
Comment on lines +63 to +65

#
# Build the .deb on this release
#
COPY . /opt/mytral-src
WORKDIR /opt/mytral-src
RUN dpkg-buildpackage -b -us -uc

#
# Install the freshly built .deb — postinst provisions uv + Python 3.12 and
# installs the app under /opt/mytral. The cache update is needed because -f
# install pulls runtime dependencies from apt.
#
RUN ( dpkg -i ../mytral_*.deb || ( sh /tmp/apt-setup.sh && apt-get -f install -y ) ) && \
rm -rf /var/lib/apt/lists/* ../mytral_*

#
# Runtime configuration (mirrors build/docker/debian/Dockerfile)
#

# bind to all interfaces so the container is reachable from the host
ENV MYTRAL_HOST=0.0.0.0

# data directory — mount a host volume at /mytral for persistence
ENV MYTRAL_DATA_DIR=/mytral

# disable debug mode by default (override for development)
ENV MYTRAL_DEBUG=false

# desktop incarnation keeps first-run as simple as possible
ENV MYTRAL_INCARNATION=DESKTOP

# enable user registration by default to make it easy to create an account
ENV MYTRAL_USER_REGISTRATION=true

# enable caching by default to speed up the run
ENV MYTRAL_ENABLE_CACHE=true

RUN mkdir -p /mytral

EXPOSE 5000

# health check — poll the home page every 30 seconds
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD curl -f http://localhost:5000/ || exit 1

# run the installed web server (the wrapper execs /opt/mytral/venv/bin/mytral-web)
CMD ["/usr/bin/mytral-web"]
38 changes: 38 additions & 0 deletions build/docker/ubuntu/apt-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/sh
#
# MyTraL: my trailing log
#
# Copyright (C) 2015-2026 Martin Dvorak <martin.dvorak@mindforger.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# Make "apt-get update" work on any Ubuntu release, including end-of-life ones.
#
# EOL releases (trusty, xenial, ...) are removed from archive.ubuntu.com and
# moved to old-releases.ubuntu.com, and their Release files are expired. This
# script first tries a normal update and, only if that fails, switches the
# sources to old-releases and disables the Valid-Until check.

set -e

if apt-get update 2>/dev/null; then
exit 0
fi

echo "apt-setup: archive update failed — switching to old-releases.ubuntu.com"
sed -i \
-e 's|//archive.ubuntu.com|//old-releases.ubuntu.com|g' \
-e 's|//security.ubuntu.com|//old-releases.ubuntu.com|g' \
/etc/apt/sources.list
apt-get -o Acquire::Check-Valid-Until=false update
116 changes: 116 additions & 0 deletions build/docker/ubuntu/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
#
# MyTraL: my trailing log
#
# Copyright (C) 2015-2026 Martin Dvorak <martin.dvorak@mindforger.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# Build a MyTraL test image for one specific Ubuntu release.
#
# The image builds the .deb ON that release and installs it (see Dockerfile).
#
# Usage:
# ./build.sh [release] [repo-root]
# release Ubuntu codename (default: trusty)
# repo-root MyTraL git checkout (default: repository this script lives in)

set -euo pipefail

RELEASE="${1:-trusty}"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="${2:-$(cd "${SCRIPT_DIR}/../../.." && pwd)}"

#
# Resolve version
#
VERSION=$(cd "${REPO_ROOT}" && uv run python -c "
import sys; sys.path.insert(0, 'mytral')
import version; print(version.__version__)
" 2>/dev/null || echo "unknown")

IMAGE_TAG="mytral-ubuntu-${RELEASE}:${VERSION}"
IMAGE_LATEST="mytral-ubuntu-${RELEASE}:latest"

echo "=== MyTraL Docker Ubuntu (${RELEASE}) image builder ==="
echo " Release: ${RELEASE}"
echo " Version: ${VERSION}"
echo " Repo: ${REPO_ROOT}"
echo " Image: ${IMAGE_TAG}"

#
# Build context: clean copy of the working tree (so uncommitted changes are
# included), minus heavy or irrelevant artifacts.
#
BUILD_CTX="/tmp/mytral-docker-ubuntu-${RELEASE}-build"
echo " Build ctx: ${BUILD_CTX}"

rm -rf "${BUILD_CTX}"
mkdir -p "${BUILD_CTX}"
Comment on lines +57 to +61
tar -C "${REPO_ROOT}" \
--exclude=.git \
--exclude=.venv --exclude=venv \
--exclude=distro --exclude=data \
--exclude=.idea --exclude=.vscode --exclude=.claude \
--exclude=.ruff_cache --exclude=.pytest_cache \
--exclude=__pycache__ --exclude='*.pyc' \
--exclude=vibe --exclude=uv.lock \
-cf - . | tar -C "${BUILD_CTX}" -xf -

#
# Apply the old-Ubuntu debian/ overlay at the context root so dpkg-buildpackage
# finds ./debian/ there. Shared files (postinst, postrm, copyright, docs) come
# from the canonical packaging; the overlay only replaces what trusty needs.
#
echo ""
echo "--- Preparing debian/ (canonical + old-Ubuntu overlay) ---"
cp -r "${BUILD_CTX}/build/ubuntu/debian" "${BUILD_CTX}/debian"
cp -r "${BUILD_CTX}/build/docker/ubuntu/debian-overlay/." "${BUILD_CTX}/debian/"

#
# Generate a native-format changelog for this local test build
#
CHANGELOG_DATE=$(date "+%a, %d %b %Y %H:%M:%S %z")
cat > "${BUILD_CTX}/debian/changelog" <<EOF
mytral (${VERSION}~${RELEASE}) ${RELEASE}; urgency=low

* Local Docker test build for Ubuntu ${RELEASE}.

-- Martin Dvorak (Dvorka) <martin.dvorak@mindforger.com> ${CHANGELOG_DATE}
EOF

#
# Build Docker image
#
echo ""
echo "--- Building Docker image (build + install the .deb on ${RELEASE}) ---"
docker build \
--build-arg "UBUNTU_RELEASE=${RELEASE}" \
--tag "${IMAGE_TAG}" \
--tag "${IMAGE_LATEST}" \
--file "${BUILD_CTX}/build/docker/ubuntu/Dockerfile" \
"${BUILD_CTX}"

rm -rf "${BUILD_CTX}"

echo ""
echo "================================================================================"
echo " DOCKER IMAGE BUILT"
echo "================================================================================"
echo " Image: ${IMAGE_TAG}"
echo " Also tagged as: ${IMAGE_LATEST}"
echo ""
echo " To run: ./build/docker/ubuntu/run.sh ${RELEASE}"
echo "================================================================================"
1 change: 1 addition & 0 deletions build/docker/ubuntu/debian-overlay/compat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9
15 changes: 15 additions & 0 deletions build/docker/ubuntu/debian-overlay/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Source: mytral
Section: utils
Priority: optional
Maintainer: Martin Dvorak <martin.dvorak@mindforger.com>
Build-Depends: debhelper (>= 9), curl, ca-certificates
Standards-Version: 3.9.5
Homepage: https://github.com/dvorka-oss/mytral
Vcs-Git: https://github.com/dvorka-oss/mytral.git
Vcs-Browser: https://github.com/dvorka-oss/mytral

Package: mytral
Architecture: all
Depends: ${misc:Depends}, curl, ca-certificates
Description: Sovereign athlete training log for deeper insights and smarter progress.
Your log, your data, your edge. Track, analyze, understand, and optimize to achieve your goals. Train smarter, not harder.
51 changes: 51 additions & 0 deletions build/docker/ubuntu/debian-overlay/rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/make -f

# Old-Ubuntu compatible packaging (trusty, xenial, bionic, focal, ...).
#
# These releases predate Python 3.12 and do not ship the build tooling the
# modern packaging relies on (pybuild-plugin-pyproject, python3-hatchling).
# uv is therefore used to provision a self-contained Python 3.12 that builds
# the wheel here; the wheel and its dependencies are installed at configure
# time by debian/postinst — exactly as on modern releases. This file mirrors
# build/ubuntu/debian/rules but replaces the pybuild build with uv.

export DH_VERBOSE=1

# keep uv's managed Python and cache inside the build tree
export UV_PYTHON_INSTALL_DIR=$(CURDIR)/.uv/python
export UV_CACHE_DIR=$(CURDIR)/.uv/cache

%:
dh $@

override_dh_auto_clean:
rm -rf .uv dist

override_dh_auto_build:
# trusty ships Python 3.4 — provision 3.12 and build the wheel with uv
uv build --wheel --out-dir dist

override_dh_auto_install:
# bundle the built wheel for venv installation in postinst
mkdir -p debian/mytral/opt/mytral
find dist -name "mytral-*.whl" | head -1 | xargs -I{} cp {} debian/mytral/opt/mytral/
# generate requirements.txt from pyproject.toml — single source of truth
# include core deps + runtime desktop deps (exclude build-only: pyinstaller, backports.*, importlib-*)
uv run --python 3.12 --no-project python -c "\
import pathlib, tomllib; \
data = tomllib.loads(pathlib.Path('pyproject.toml').read_text()); \
build_only = {'pyinstaller', 'importlib-metadata', 'importlib-resources', \
'backports.functools-lru-cache', 'backports.zoneinfo', 'backports.tarfile'}; \
desktop = [d for d in data['dependency-groups']['desktop'] \
if not any(d.startswith(p) for p in build_only)]; \
deps = data['project']['dependencies'] + desktop; \
print('\n'.join(deps))" > debian/mytral/opt/mytral/requirements.txt
# wrapper scripts use the isolated venv — nothing touches system Python
install -d debian/mytral/usr/bin
printf '#!/bin/sh\nexport MYTRAL_INCARNATION=DESKTOP\nexport MYTRAL_USER_REGISTRATION=true\nexport MYTRAL_ENABLE_CACHE=true\nexec /opt/mytral/venv/bin/mytral-desktop "$$@"\n' > debian/mytral/usr/bin/mytral
printf '#!/bin/sh\nexport MYTRAL_ENABLE_CACHE=true\nexec /opt/mytral/venv/bin/mytral-web "$$@"\n' > debian/mytral/usr/bin/mytral-web
printf '#!/bin/sh\nexport MYTRAL_INCARNATION=DESKTOP\nexport MYTRAL_USER_REGISTRATION=true\nexport MYTRAL_ENABLE_CACHE=true\nexec /opt/mytral/venv/bin/mytral-desktop "$$@"\n' > debian/mytral/usr/bin/mytral-desktop
chmod 755 debian/mytral/usr/bin/mytral debian/mytral/usr/bin/mytral-web debian/mytral/usr/bin/mytral-desktop

override_dh_auto_test:
# tests require an active virtualenv — skip in package build environment
1 change: 1 addition & 0 deletions build/docker/ubuntu/debian-overlay/source/format
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.0 (native)
Loading
Loading