1
0
forked from iarv/mc-webui

feat: Add dynamic Git-based versioning system

- Add app/version.py module generating version from Git metadata
- Format: YYYY.MM.DD+<commit_hash> (e.g., 2025.01.18+576c8ca9)
- Add +dirty suffix for uncommitted changes (ignores .env, technotes/)
- Add /api/version endpoint for monitoring
- Display version in hamburger menu
- Add freeze mechanism for Docker builds

Deploy command updated:
git push && ssh ... "cd ~/mc-webui && git pull && python -m app.version freeze && docker compose up -d --build"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-01-18 11:47:22 +01:00
parent 7a626ba105
commit 7ca3f4d2dd
7 changed files with 106 additions and 2 deletions

2
.gitignore vendored
View File

@@ -11,6 +11,8 @@
# Python
# ============================================
__pycache__/
# Auto-generated version file (created during Docker build)
app/version_frozen.py
*.py[cod]
*$py.class
*.so

View File

@@ -16,6 +16,8 @@ COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
# Note: Run 'python -m app.version freeze' before build to include version info
# The version_frozen.py file will be copied automatically if it exists
COPY app/ ./app/
# Expose Flask port

View File

@@ -1,5 +1,5 @@
"""
mc-webui - Flask application package
"""
__version__ = "0.1.0"
Version is managed dynamically via app/version.py based on Git metadata.
"""

View File

@@ -12,6 +12,7 @@ from flask_socketio import SocketIO, emit
from app.config import config, runtime_config
from app.routes.views import views_bp
from app.routes.api import api_bp
from app.version import VERSION_STRING
from app.archiver.manager import schedule_daily_archiving
from app.meshcore.cli import fetch_device_name_from_bridge
@@ -43,6 +44,11 @@ def create_app():
app.config['DEBUG'] = config.FLASK_DEBUG
app.config['SECRET_KEY'] = 'mc-webui-secret-key-change-in-production'
# Inject version into all templates
@app.context_processor
def inject_version():
return {'version': VERSION_STRING}
# Register blueprints
app.register_blueprint(views_bp)
app.register_blueprint(api_bp)

View File

@@ -1910,6 +1910,27 @@ def get_read_status_api():
}), 500
@api_bp.route('/version', methods=['GET'])
def get_version():
"""
Get application version.
Returns:
JSON with version info:
{
"success": true,
"version": "2025.01.18+576c8ca9",
"docker_tag": "2025.01.18-576c8ca9"
}
"""
from app.version import VERSION_STRING, DOCKER_TAG
return jsonify({
'success': True,
'version': VERSION_STRING,
'docker_tag': DOCKER_TAG
}), 200
@api_bp.route('/read_status/mark_read', methods=['POST'])
def mark_read_api():
"""

View File

@@ -58,6 +58,9 @@
<h5 class="offcanvas-title"><i class="bi bi-menu-button-wide"></i> Menu</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="px-3 pb-2 text-muted small border-bottom">
<i class="bi bi-tag"></i> {{ version }}
</div>
<div class="offcanvas-body">
<div class="list-group list-group-flush">
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" id="refreshBtn">

70
app/version.py Normal file
View File

@@ -0,0 +1,70 @@
"""
Git-based version management for mc-webui.
Format: YYYY.MM.DD+<short_hash> (e.g., 2025.01.18+576c8ca9)
"""
import subprocess
import shlex
import os
VERSION_STRING = "0.0.0+unknown"
DOCKER_TAG = "0.0.0-unknown"
def subprocess_run(args):
"""Execute subprocess and return stripped stdout."""
if not isinstance(args, (list, tuple)):
args = shlex.split(args)
proc = subprocess.run(
args,
capture_output=True,
text=True,
check=True,
env={"PATH": os.environ.get("PATH", ""), "LC_ALL": "C"}
)
return proc.stdout.strip()
def get_git_version():
"""Get version from git commit date and hash."""
# Get date (YYYY.MM.DD) and short hash
git_version = subprocess_run(
r"git show -s --date=format:%Y.%m.%d --format=%cd+%h"
)
# Keep full ISO format (with leading zeros)
docker_tag = git_version.replace("+", "-")
# Check for uncommitted changes (ignore .env and technotes/)
try:
subprocess_run("git diff --quiet -- . :!*.env :!.env :!technotes/")
except subprocess.CalledProcessError as e:
if e.returncode == 1:
git_version += "+dirty"
return git_version, docker_tag
# Load version: frozen file takes priority, then git, then fallback
try:
from app.version_frozen import VERSION_STRING, DOCKER_TAG
except ImportError:
try:
VERSION_STRING, DOCKER_TAG = get_git_version()
except Exception:
pass # Keep defaults
if __name__ == "__main__":
import sys
if len(sys.argv) >= 2 and sys.argv[1] == "freeze":
VERSION_STRING, DOCKER_TAG = get_git_version()
code = f'''"""Frozen version - auto-generated, do not edit."""
VERSION_STRING = "{VERSION_STRING}"
DOCKER_TAG = "{DOCKER_TAG}"
'''
path = os.path.join(os.path.dirname(__file__), "version_frozen.py")
with open(path, "w", encoding="utf8") as f:
f.write(code)
print(f"Version frozen: {VERSION_STRING}")
else:
print(f'VERSION_STRING="{VERSION_STRING}"')
print(f'DOCKER_TAG="{DOCKER_TAG}"')