diff --git a/Jenkinsfile b/Jenkinsfile
index 932f591..d6c463c 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -10,6 +10,7 @@ pipeline {
VUE_CONTAINER_NAME = "chore-app-frontend"
FLASK_CONTAINER_NAME = "chore-app-backend"
NETWORK_NAME = "chore-app-net"
+ BASE_VERSION = '1.0.0'
}
stages {
@@ -32,8 +33,7 @@ pipeline {
stage('Build Backend (Flask) App') {
steps {
dir('.') {
- sh 'docker build -t ${BACKEND_IMAGE} .'
- sh 'docker tag ${BACKEND_IMAGE} ${BACKEND_IMAGE_LATEST}'
+ sh """docker build --build-arg APP_BUILD=${BUILD_NUMBER} -t chore-app-backend:${BASE_VERSION} -t chore-app-backend:${BASE_VERSION}-${BUILD_NUMBER} -t chore-app-backend:latest ."""
}
}
}
@@ -65,6 +65,7 @@ pipeline {
docker run -d \\
--name ${FLASK_CONTAINER_NAME} \\
--network ${NETWORK_NAME} \\
+ -e BUILD_NUMBER=${BUILD_NUMBER} \\
-v ${FLASK_CONTAINER_NAME}_data:/app/data \\
${BACKEND_IMAGE_LATEST}
"""
diff --git a/config/version.py b/config/version.py
new file mode 100644
index 0000000..0aeb9d2
--- /dev/null
+++ b/config/version.py
@@ -0,0 +1,12 @@
+# python
+# file: config/version.py
+import os
+
+BASE_VERSION = "1.0.0" # update manually when releasing features
+
+def get_full_version() -> str:
+ """
+ Return semantic version with optional Jenkins build metadata, e.g. 1.2.3+build.456.
+ """
+ build = os.environ.get("BUILD_NUMBER") or os.environ.get("APP_BUILD")
+ return f"{BASE_VERSION}+build.{build}" if build else BASE_VERSION
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..f8966f9
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,30 @@
+# yaml
+version: '3.8'
+
+services:
+ chore-app-backend:
+ image: devserver.lan:5900/chore-app-backend:production
+ container_name: chore-app-backend
+ restart: unless-stopped
+ expose:
+ - "5000"
+ networks:
+ - chore-app-net
+ volumes:
+ - chore-app-backend-data:/app/data # persists backend data
+
+ chore-app-frontend:
+ image: devserver.lan:5900/chore-app-frontend:production
+ container_name: chore-app-frontend
+ restart: unless-stopped
+ ports:
+ - "4600:443"
+ networks:
+ - chore-app-net
+
+networks:
+ chore-app-net:
+ driver: bridge
+
+volumes:
+ chore-app-backend-data: {}
\ No newline at end of file
diff --git a/main.py b/main.py
index f048433..67cc52b 100644
--- a/main.py
+++ b/main.py
@@ -1,12 +1,13 @@
import sys, logging, os
from config.paths import get_user_image_dir
-from flask import Flask, request
+from flask import Flask, request, jsonify
from flask_cors import CORS
from api.child_api import child_api
from api.image_api import image_api
from api.reward_api import reward_api
from api.task_api import task_api
+from config.version import get_full_version
from events.broadcaster import Broadcaster
from events.sse import sse_response_for_user, send_to_user
from db.default import initializeImages
@@ -29,6 +30,10 @@ app.register_blueprint(task_api)
app.register_blueprint(image_api)
CORS(app)
+@app.route("/version")
+def api_version():
+ return jsonify({"version": get_full_version()})
+
@app.route("/events")
def events():
# Authenticate user or read a token
diff --git a/web/vue-app/src/layout/ParentLayout.vue b/web/vue-app/src/layout/ParentLayout.vue
index db8f8ab..6bff924 100644
--- a/web/vue-app/src/layout/ParentLayout.vue
+++ b/web/vue-app/src/layout/ParentLayout.vue
@@ -1,6 +1,6 @@
@@ -142,6 +156,8 @@ const showBack = computed(
+
+ v{{ appVersion }}
@@ -251,6 +267,19 @@ const showBack = computed(
align-self: start;
}
+.app-version {
+ position: fixed;
+ right: 18px;
+ bottom: 12px;
+ font-size: 0.92rem;
+ color: #cbd5e1; /* Brighter slate-200 */
+ opacity: 0.85;
+ z-index: 100;
+ pointer-events: none;
+ user-select: none;
+ font-family: monospace;
+}
+
@media (max-width: 480px) {
.back-btn {
padding: 0.45rem 0.75rem;