Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3571bb6f6d | |||
| b037434e89 | |||
| ad14499df0 | |||
| 6f50fb2263 |
@@ -25,7 +25,6 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-docker:
|
release-docker:
|
||||||
# Hinweis: Falls dein lokaler Gitea-Runner ein anderes Label hat (z.B. 'linux' oder 'self-hosted'), passe dies hier an.
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -35,7 +34,6 @@ jobs:
|
|||||||
- name: Set metadata
|
- name: Set metadata
|
||||||
id: meta
|
id: meta
|
||||||
env:
|
env:
|
||||||
# Gitea stellt das GITHUB_TOKEN für Kompatibilität mit GitHub Actions automatisch zur Verfügung
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
REPO: ${{ gitea.repository }}
|
REPO: ${{ gitea.repository }}
|
||||||
EVENT_NAME: ${{ gitea.event_name }}
|
EVENT_NAME: ${{ gitea.event_name }}
|
||||||
@@ -46,7 +44,6 @@ jobs:
|
|||||||
if [ "$EVENT_NAME" = "push" ] && [ -n "$REF_NAME" ]; then
|
if [ "$EVENT_NAME" = "push" ] && [ -n "$REF_NAME" ]; then
|
||||||
TAG="$REF_NAME"
|
TAG="$REF_NAME"
|
||||||
else
|
else
|
||||||
# Gitea API Endpunkt nutzen, um das aktuellste Release abzufragen
|
|
||||||
latest_tag="v0.8.31"
|
latest_tag="v0.8.31"
|
||||||
if meta_json=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/json" "https://git.invario-software.eu/api/v1/repos/$REPO/releases/latest" 2>/dev/null); then
|
if meta_json=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/json" "https://git.invario-software.eu/api/v1/repos/$REPO/releases/latest" 2>/dev/null); then
|
||||||
tag_name=$(printf "%s" "$meta_json" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)
|
tag_name=$(printf "%s" "$meta_json" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)
|
||||||
@@ -63,7 +60,6 @@ jobs:
|
|||||||
major=0; minor=8; patch=31
|
major=0; minor=8; patch=31
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Bump strategy: major / minor / patch
|
|
||||||
if [ "${BUMP_TYPE:-}" = "major" ]; then
|
if [ "${BUMP_TYPE:-}" = "major" ]; then
|
||||||
major=$((major + 1)); minor=0; patch=0
|
major=$((major + 1)); minor=0; patch=0
|
||||||
elif [ "${BUMP_TYPE:-}" = "minor" ]; then
|
elif [ "${BUMP_TYPE:-}" = "minor" ]; then
|
||||||
@@ -72,11 +68,9 @@ jobs:
|
|||||||
patch=$((patch + 1))
|
patch=$((patch + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Zusammenbau des Tags für den manuellen Run
|
|
||||||
TAG="v${major}.${minor}.${patch}"
|
TAG="v${major}.${minor}.${patch}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validierung des erzeugten oder übergebenen Tags
|
|
||||||
if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+(-dev(\.[0-9]+)?)?$'; then
|
if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+(-dev(\.[0-9]+)?)?$'; then
|
||||||
echo "Error: tag '$TAG' is not valid semver (vX.Y.Z)"
|
echo "Error: tag '$TAG' is not valid semver (vX.Y.Z)"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -95,13 +89,11 @@ jobs:
|
|||||||
LATEST_MAJOR="0"
|
LATEST_MAJOR="0"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If not explicitly bumping major, disallow changing major version
|
|
||||||
if [ "${BUMP_TYPE:-}" != "major" ] && [ "$TAG_MAJOR" != "$LATEST_MAJOR" ]; then
|
if [ "${BUMP_TYPE:-}" != "major" ] && [ "$TAG_MAJOR" != "$LATEST_MAJOR" ]; then
|
||||||
echo "Error: major version must stay v$LATEST_MAJOR.x.x (got $TAG)"
|
echo "Error: major version must stay v$LATEST_MAJOR.x.x (got $TAG)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure tag uniqueness: if tag exists append numeric suffix
|
|
||||||
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
|
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
|
||||||
i=1
|
i=1
|
||||||
base="$TAG"
|
base="$TAG"
|
||||||
@@ -111,24 +103,18 @@ jobs:
|
|||||||
TAG="${base}.${i}"
|
TAG="${base}.${i}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Docker Images verlangen Kleinbuchstaben. Repository-Namen daher umwandeln.
|
|
||||||
LOWER_REPO=$(echo "$REPO" | tr '[:upper:]' '[:lower:]')
|
LOWER_REPO=$(echo "$REPO" | tr '[:upper:]' '[:lower:]')
|
||||||
IMAGE="git.invario-software.eu/${LOWER_REPO}:${TAG}"
|
IMAGE="git.invario-software.eu/${LOWER_REPO}:${TAG}"
|
||||||
|
|
||||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||||
echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
|
echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
|
||||||
echo "lower_repo=$LOWER_REPO" >> "$GITHUB_OUTPUT"
|
echo "lower_repo=$LOWER_REPO" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Ensure Docker CLI is available and up to date
|
- name: Ensure Docker CLI is available and up to date
|
||||||
run: |
|
run: |
|
||||||
install_docker=true
|
install_docker=true
|
||||||
|
|
||||||
# Prüfen, ob Docker existiert und ob die Version ausreicht
|
|
||||||
if command -v docker >/dev/null 2>&1; then
|
if command -v docker >/dev/null 2>&1; then
|
||||||
# Extrahiere die Major-Version
|
|
||||||
DOCKER_MAJOR=$(docker --version | grep -oE '[0-9]+' | head -n1)
|
DOCKER_MAJOR=$(docker --version | grep -oE '[0-9]+' | head -n1)
|
||||||
|
|
||||||
# API 1.44 erfordert mindestens Docker v25
|
|
||||||
if [ -n "$DOCKER_MAJOR" ] && [ "$DOCKER_MAJOR" -ge 25 ]; then
|
if [ -n "$DOCKER_MAJOR" ] && [ "$DOCKER_MAJOR" -ge 25 ]; then
|
||||||
install_docker=false
|
install_docker=false
|
||||||
fi
|
fi
|
||||||
@@ -136,34 +122,19 @@ jobs:
|
|||||||
|
|
||||||
if [ "$install_docker" = true ]; then
|
if [ "$install_docker" = true ]; then
|
||||||
echo "Veraltete oder fehlende Docker-Installation erkannt. Lade statische Docker CLI herunter..."
|
echo "Veraltete oder fehlende Docker-Installation erkannt. Lade statische Docker CLI herunter..."
|
||||||
|
|
||||||
DOCKER_VERSION="26.1.4"
|
DOCKER_VERSION="26.1.4"
|
||||||
|
|
||||||
# Download der statischen Binaries via curl oder wget (umgeht apt-get komplett)
|
|
||||||
if command -v curl >/dev/null 2>&1; then
|
if command -v curl >/dev/null 2>&1; then
|
||||||
curl -fsSLO "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz"
|
curl -fsSLO "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz"
|
||||||
else
|
else
|
||||||
wget -q "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz"
|
wget -q "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tar -xzf docker-${DOCKER_VERSION}.tgz
|
tar -xzf docker-${DOCKER_VERSION}.tgz
|
||||||
|
|
||||||
# Installation in lokalen Benutzer-Pfad, um sudo/root-Rechte-Probleme zu vermeiden
|
|
||||||
mkdir -p "$HOME/.local/bin"
|
mkdir -p "$HOME/.local/bin"
|
||||||
cp docker/docker "$HOME/.local/bin/"
|
cp docker/docker "$HOME/.local/bin/"
|
||||||
|
|
||||||
# Pfad für nachfolgende GitHub Actions Schritte verfügbar machen
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
# Pfad für diesen spezifischen Shell-Run exportieren
|
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
rm -rf docker docker-${DOCKER_VERSION}.tgz
|
rm -rf docker docker-${DOCKER_VERSION}.tgz
|
||||||
echo "Docker CLI wurde erfolgreich aktualisiert."
|
|
||||||
else
|
|
||||||
echo "Docker CLI ist bereits auf einem aktuellen Stand."
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker --version
|
docker --version
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -186,18 +157,6 @@ jobs:
|
|||||||
${{ steps.meta.outputs.image }}
|
${{ steps.meta.outputs.image }}
|
||||||
git.invario-software.eu/${{ steps.meta.outputs.lower_repo }}:latest
|
git.invario-software.eu/${{ steps.meta.outputs.lower_repo }}:latest
|
||||||
|
|
||||||
- name: Build local image tar for offline deploy
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
IMG="${{ steps.meta.outputs.image }}"
|
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
|
||||||
|
|
||||||
echo "Pulling freshly pushed image from registry: $IMG"
|
|
||||||
docker pull "$IMG"
|
|
||||||
|
|
||||||
echo "Saving image to offline tar archive..."
|
|
||||||
docker save "$IMG" | gzip > "inventarsystem-image-${TAG}.tar.gz"
|
|
||||||
|
|
||||||
- name: Create release-only docker bundle
|
- name: Create release-only docker bundle
|
||||||
run: |
|
run: |
|
||||||
mkdir -p release-bundle
|
mkdir -p release-bundle
|
||||||
@@ -275,21 +234,18 @@ jobs:
|
|||||||
redis_data:
|
redis_data:
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Copy runtime scripts and config if present
|
|
||||||
for f in start.sh stop.sh restart.sh backup.sh restore.sh config.json update.sh; do
|
for f in start.sh stop.sh restart.sh backup.sh restore.sh config.json update.sh; do
|
||||||
if [ -f "$f" ]; then
|
if [ -f "$f" ]; then
|
||||||
cp "$f" "release-bundle/$(basename "$f")"
|
cp "$f" "release-bundle/$(basename "$f")"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Multitenant scripts & docs (optional)
|
|
||||||
for f in docker-compose-multitenant.yml manage-tenant.sh run-tenant-cmd.sh MULTITENANT_DEPLOYMENT.md MULTITENANT_PYTHON_API.md; do
|
for f in docker-compose-multitenant.yml manage-tenant.sh run-tenant-cmd.sh MULTITENANT_DEPLOYMENT.md MULTITENANT_PYTHON_API.md; do
|
||||||
if [ -f "$f" ]; then
|
if [ -f "$f" ]; then
|
||||||
cp "$f" "release-bundle/$(basename "$f")"
|
cp "$f" "release-bundle/$(basename "$f")"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Make any shipped scripts executable
|
|
||||||
find release-bundle -maxdepth 1 -type f -name '*.sh' -exec chmod +x {} \; || true
|
find release-bundle -maxdepth 1 -type f -name '*.sh' -exec chmod +x {} \; || true
|
||||||
tar -czf inventarsystem-docker-bundle.tar.gz -C release-bundle .
|
tar -czf inventarsystem-docker-bundle.tar.gz -C release-bundle .
|
||||||
|
|
||||||
@@ -298,5 +254,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
tag_name: ${{ steps.meta.outputs.tag }}
|
tag_name: ${{ steps.meta.outputs.tag }}
|
||||||
files: |
|
files: |
|
||||||
inventarsystem-docker-bundle.tar.gz
|
inventarsystem-docker-bundle.tar.gz
|
||||||
inventarsystem-image-${{ steps.meta.outputs.tag }}.tar.gz
|
|
||||||
+4
-6
@@ -10376,18 +10376,15 @@ def my_borrowed_items():
|
|||||||
@app.route('/notifications')
|
@app.route('/notifications')
|
||||||
def notifications_view():
|
def notifications_view():
|
||||||
"""Notification center for users and admins."""
|
"""Notification center for users and admins."""
|
||||||
|
from Web.push_notifications import _get_vapid_public
|
||||||
if 'username' not in session:
|
if 'username' not in session:
|
||||||
flash('Bitte melden Sie sich an, um Benachrichtigungen zu sehen.', 'error')
|
flash('Bitte melden Sie sich an, um Benachrichtigungen zu sehen.', 'error')
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
username = session['username']
|
username = session['username']
|
||||||
is_admin_user = False
|
|
||||||
current_permissions = us.get_effective_permissions(session['username'])
|
current_permissions = us.get_effective_permissions(session['username'])
|
||||||
|
|
||||||
if not current_permissions['actions'].get('can_manage_settings', False):
|
is_admin_user = current_permissions['actions'].get('can_manage_settings', False)
|
||||||
is_admin_user = True
|
|
||||||
else:
|
|
||||||
is_admin_user = False
|
|
||||||
|
|
||||||
client = None
|
client = None
|
||||||
try:
|
try:
|
||||||
@@ -10421,6 +10418,7 @@ def notifications_view():
|
|||||||
is_admin_user=is_admin_user,
|
is_admin_user=is_admin_user,
|
||||||
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
||||||
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
||||||
|
vapid_public_key=_get_vapid_public()
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
app.logger.error(f"Error loading notifications: {exc}")
|
app.logger.error(f"Error loading notifications: {exc}")
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ def _match_student_cards(path):
|
|||||||
|
|
||||||
def _match_mail(path):
|
def _match_mail(path):
|
||||||
if not path: return False
|
if not path: return False
|
||||||
return path.startswith(('/'))
|
return path.startswith(('/configure'))
|
||||||
|
|
||||||
# Register core modules into the pipeline
|
# Register core modules into the pipeline
|
||||||
MODULES.register('inventory', INVENTORY_MODULE_ENABLED, _match_inventory)
|
MODULES.register('inventory', INVENTORY_MODULE_ENABLED, _match_inventory)
|
||||||
|
|||||||
+34
-25
@@ -20,40 +20,49 @@ logger = logging.getLogger(__name__)
|
|||||||
# VAPID keys for push notifications
|
# VAPID keys for push notifications
|
||||||
VAPID_PUBLIC_KEY = os.getenv('VAPID_PUBLIC_KEY', '')
|
VAPID_PUBLIC_KEY = os.getenv('VAPID_PUBLIC_KEY', '')
|
||||||
VAPID_PRIVATE_KEY = os.getenv('VAPID_PRIVATE_KEY', '')
|
VAPID_PRIVATE_KEY = os.getenv('VAPID_PRIVATE_KEY', '')
|
||||||
VAPID_SUBJECT = os.getenv('VAPID_SUBJECT', f'mailto:admin@{os.getenv("SERVER_NAME", "localhost")}')
|
VAPID_SUBJECT = os.getenv('VAPID_SUBJECT', f'mailto:support@invario-software.de')
|
||||||
|
|
||||||
|
# VAPID keys file paths
|
||||||
VAPID_PRIVATE_PEM = os.path.join(os.path.dirname(__file__), 'vapid_private.pem')
|
VAPID_PRIVATE_PEM = os.path.join(os.path.dirname(__file__), 'vapid_private.pem')
|
||||||
VAPID_PUBLIC_PEM = os.path.join(os.path.dirname(__file__), 'vapid_public.pem')
|
VAPID_PUBLIC_PEM = os.path.join(os.path.dirname(__file__), 'vapid_public.pem')
|
||||||
|
|
||||||
# Auto-generate VAPID keys if none are provided
|
# Load or auto-generate VAPID keys
|
||||||
if not VAPID_PUBLIC_KEY or not VAPID_PRIVATE_KEY:
|
try:
|
||||||
try:
|
from py_vapid import Vapid, b64urlencode
|
||||||
from py_vapid import Vapid, b64urlencode
|
from cryptography.hazmat.primitives import serialization
|
||||||
from cryptography.hazmat.primitives import serialization
|
|
||||||
|
if not os.path.exists(VAPID_PRIVATE_PEM) or not os.path.exists(VAPID_PUBLIC_PEM):
|
||||||
vapid = Vapid()
|
vapid = Vapid()
|
||||||
if not os.path.exists(VAPID_PRIVATE_PEM):
|
vapid.generate_keys()
|
||||||
vapid.generate_keys()
|
vapid.save_key(VAPID_PRIVATE_PEM)
|
||||||
vapid.save_key(VAPID_PRIVATE_PEM)
|
vapid.save_public_key(VAPID_PUBLIC_PEM)
|
||||||
vapid.save_public_key(VAPID_PUBLIC_PEM)
|
logger.info("Auto-generated new VAPID keys")
|
||||||
logger.info("Auto-generated new VAPID keys")
|
else:
|
||||||
else:
|
vapid = Vapid.from_file(VAPID_PRIVATE_PEM)
|
||||||
vapid = Vapid.from_file(VAPID_PRIVATE_PEM)
|
|
||||||
|
# Extract public key in standard uncompressed point format encoded in URL-safe base64
|
||||||
raw_pub = vapid.public_key.public_bytes(
|
raw_pub = vapid.public_key.public_bytes(
|
||||||
serialization.Encoding.X962,
|
serialization.Encoding.X962,
|
||||||
serialization.PublicFormat.UncompressedPoint
|
serialization.PublicFormat.UncompressedPoint
|
||||||
)
|
)
|
||||||
|
|
||||||
VAPID_PUBLIC_KEY = b64urlencode(raw_pub).decode('utf-8')
|
VAPID_PUBLIC_KEY = b64urlencode(raw_pub).decode('utf-8')
|
||||||
VAPID_PRIVATE_KEY = VAPID_PRIVATE_PEM
|
VAPID_PRIVATE_KEY = VAPID_PRIVATE_PEM # pywebpush accepts the file path to the private PEM
|
||||||
except Exception as e:
|
|
||||||
logger.error(f'Could not load or generate VAPID keys: {e}')
|
except Exception as e:
|
||||||
|
logger.error(f'Could not load or generate VAPID keys: {e}')
|
||||||
|
VAPID_PUBLIC_KEY = os.getenv('VAPID_PUBLIC_KEY', '')
|
||||||
|
VAPID_PRIVATE_KEY = os.getenv('VAPID_PRIVATE_KEY', '')
|
||||||
|
|
||||||
|
VAPID_SUBJECT = os.getenv('VAPID_SUBJECT', 'mailto:support@invario-software.de')
|
||||||
|
|
||||||
# Push service endpoint (typically Firebase or Web Push Service)
|
# Push service endpoint (typically Firebase or Web Push Service)
|
||||||
FCM_API_KEY = os.getenv('FCM_API_KEY', '') # Firebase API key
|
FCM_API_KEY = os.getenv('FCM_API_KEY', '')
|
||||||
|
|
||||||
|
|
||||||
|
def _get_vapid_public():
|
||||||
|
return VAPID_PUBLIC_KEY
|
||||||
|
|
||||||
def _get_username_hash(username):
|
def _get_username_hash(username):
|
||||||
"""Generates a deterministic hash for database lookups."""
|
"""Generates a deterministic hash for database lookups."""
|
||||||
if not username:
|
if not username:
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ const pushNotificationManager = new PushNotificationManager();
|
|||||||
/**
|
/**
|
||||||
* Show notification subscription UI (typically in settings)
|
* Show notification subscription UI (typically in settings)
|
||||||
*/
|
*/
|
||||||
function showPushNotificationSettings() {
|
function showPushNotificationSettings(vapidPublicKey) {
|
||||||
const container = document.getElementById('push-notification-settings');
|
const container = document.getElementById('push-notification-settings');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
@@ -350,9 +350,9 @@ function showPushNotificationSettings() {
|
|||||||
|
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
|
|
||||||
// Set up button handler
|
// Set up button handler and pass the VAPID public key to init()
|
||||||
const toggleBtn = document.getElementById('toggle-push-btn');
|
const toggleBtn = document.getElementById('toggle-push-btn');
|
||||||
pushNotificationManager.init().then(() => {
|
pushNotificationManager.init(vapidPublicKey).then(() => {
|
||||||
updatePushStatus();
|
updatePushStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1371,8 +1371,8 @@
|
|||||||
await loadLibraryItems(); // Daten neu laden
|
await loadLibraryItems(); // Daten neu laden
|
||||||
// renderTable(); // Ggf. Tabelle neu rendern
|
// renderTable(); // Ggf. Tabelle neu rendern
|
||||||
} else {
|
} else {
|
||||||
closeEditLibraryModal();
|
|
||||||
await loadLibraryItems();
|
await loadLibraryItems();
|
||||||
|
closeEditLibraryModal();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Update failed:', error);
|
console.error('Update failed:', error);
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
if (typeof showPushNotificationSettings === 'function') {
|
if (typeof showPushNotificationSettings === 'function') {
|
||||||
showPushNotificationSettings();
|
showPushNotificationSettings('{{ vapid_public_key }}');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+2
-30
@@ -82,7 +82,6 @@ while i < len(lines):
|
|||||||
stripped = line.lstrip(" ")
|
stripped = line.lstrip(" ")
|
||||||
indent = leading_spaces(line)
|
indent = leading_spaces(line)
|
||||||
|
|
||||||
# Check if we're starting the app service
|
|
||||||
if not in_app_service and re.match(r"^\s*app:\s*$", line):
|
if not in_app_service and re.match(r"^\s*app:\s*$", line):
|
||||||
in_app_service = True
|
in_app_service = True
|
||||||
app_indent = indent
|
app_indent = indent
|
||||||
@@ -92,9 +91,7 @@ while i < len(lines):
|
|||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check if we've exited the app service (found another top-level service)
|
|
||||||
if in_app_service and indent <= app_indent and re.match(r"^[A-Za-z0-9_-]+:\s*$", stripped):
|
if in_app_service and indent <= app_indent and re.match(r"^[A-Za-z0-9_-]+:\s*$", stripped):
|
||||||
# We've left the app service - insert image if we haven't already
|
|
||||||
if build_found and app_service_indent is not None:
|
if build_found and app_service_indent is not None:
|
||||||
out.append(f"{' ' * app_service_indent}image: {target_image}\n")
|
out.append(f"{' ' * app_service_indent}image: {target_image}\n")
|
||||||
in_app_service = False
|
in_app_service = False
|
||||||
@@ -102,24 +99,20 @@ while i < len(lines):
|
|||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Process lines within the app service
|
|
||||||
if in_app_service:
|
if in_app_service:
|
||||||
if app_service_indent is None and indent > app_indent:
|
if app_service_indent is None and indent > app_indent:
|
||||||
app_service_indent = indent
|
app_service_indent = indent
|
||||||
|
|
||||||
# Check for image key (already has an image, don't add)
|
|
||||||
if re.match(rf"^\s+image:\s*", line):
|
if re.match(rf"^\s+image:\s*", line):
|
||||||
in_app_service = False
|
in_app_service = False
|
||||||
out.append(line)
|
out.append(line)
|
||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check for build block
|
|
||||||
if re.match(rf"^\s+build:\s*$", line):
|
if re.match(rf"^\s+build:\s*$", line):
|
||||||
build_found = True
|
build_found = True
|
||||||
out.append(line)
|
out.append(line)
|
||||||
i += 1
|
i += 1
|
||||||
# Skip all lines that are part of the build block (indented more than app_service_indent)
|
|
||||||
while i < len(lines):
|
while i < len(lines):
|
||||||
next_line = lines[i]
|
next_line = lines[i]
|
||||||
next_indent = leading_spaces(next_line)
|
next_indent = leading_spaces(next_line)
|
||||||
@@ -130,12 +123,10 @@ while i < len(lines):
|
|||||||
break
|
break
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Insert image after build block before first property
|
|
||||||
if build_found and app_service_indent is not None and indent == app_service_indent:
|
if build_found and app_service_indent is not None and indent == app_service_indent:
|
||||||
# Check if this is a property line (not build)
|
|
||||||
if not re.match(rf"^\s+build:", line):
|
if not re.match(rf"^\s+build:", line):
|
||||||
out.append(f"{' ' * app_service_indent}image: {target_image}\n")
|
out.append(f"{' ' * app_service_indent}image: {target_image}\n")
|
||||||
build_found = False # Mark that we've inserted the image
|
build_found = False
|
||||||
|
|
||||||
out.append(line)
|
out.append(line)
|
||||||
i += 1
|
i += 1
|
||||||
@@ -144,7 +135,6 @@ while i < len(lines):
|
|||||||
out.append(line)
|
out.append(line)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
# If we ended while still in the app service, append image at the end
|
|
||||||
if in_app_service and build_found and app_service_indent is not None:
|
if in_app_service and build_found and app_service_indent is not None:
|
||||||
out.append(f"{' ' * app_service_indent}image: {target_image}\n")
|
out.append(f"{' ' * app_service_indent}image: {target_image}\n")
|
||||||
|
|
||||||
@@ -377,18 +367,12 @@ with open(meta_file, 'r', encoding='utf-8') as f:
|
|||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
tag = data.get('tag_name', '').strip()
|
tag = data.get('tag_name', '').strip()
|
||||||
url = ''
|
url = ''
|
||||||
image_url = ''
|
|
||||||
for asset in data.get('assets', []):
|
for asset in data.get('assets', []):
|
||||||
if asset.get('name') == asset_name:
|
if asset.get('name') == asset_name:
|
||||||
url = asset.get('browser_download_url', '').strip()
|
url = asset.get('browser_download_url', '').strip()
|
||||||
break
|
break
|
||||||
for asset in data.get('assets', []):
|
|
||||||
if asset.get('name') == f'inventarsystem-image-{tag}.tar.gz':
|
|
||||||
image_url = asset.get('browser_download_url', '').strip()
|
|
||||||
break
|
|
||||||
print(tag)
|
print(tag)
|
||||||
print(url)
|
print(url)
|
||||||
print(image_url)
|
|
||||||
PY
|
PY
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +385,7 @@ main() {
|
|||||||
need_cmd python3
|
need_cmd python3
|
||||||
need_cmd curl
|
need_cmd curl
|
||||||
|
|
||||||
local meta_file tag bundle_url image_url
|
local meta_file tag bundle_url
|
||||||
TMP_DIR="$(mktemp -d)"
|
TMP_DIR="$(mktemp -d)"
|
||||||
meta_file="$TMP_DIR/release.json"
|
meta_file="$TMP_DIR/release.json"
|
||||||
trap cleanup_tmp_dir EXIT
|
trap cleanup_tmp_dir EXIT
|
||||||
@@ -409,7 +393,6 @@ main() {
|
|||||||
mapfile -t release_info < <(latest_tag_and_bundle_url "$meta_file")
|
mapfile -t release_info < <(latest_tag_and_bundle_url "$meta_file")
|
||||||
tag="${release_info[0]:-}"
|
tag="${release_info[0]:-}"
|
||||||
bundle_url="${release_info[1]:-}"
|
bundle_url="${release_info[1]:-}"
|
||||||
image_url="${release_info[2]:-}"
|
|
||||||
|
|
||||||
if [ -z "$tag" ] || [ -z "$bundle_url" ]; then
|
if [ -z "$tag" ] || [ -z "$bundle_url" ]; then
|
||||||
echo "Error: latest release metadata is incomplete."
|
echo "Error: latest release metadata is incomplete."
|
||||||
@@ -425,17 +408,6 @@ main() {
|
|||||||
|
|
||||||
pin_compose_app_image "$tag"
|
pin_compose_app_image "$tag"
|
||||||
|
|
||||||
if [ -z "$image_url" ]; then
|
|
||||||
echo "Error: release image asset is missing"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
curl -fL "$image_url" -o "$TMP_DIR/inventarsystem-image-$tag.tar.gz"
|
|
||||||
sudo docker load -i "$TMP_DIR/inventarsystem-image-$tag.tar.gz" >/dev/null
|
|
||||||
|
|
||||||
# Tagge das geladene Gitea-Image als latest
|
|
||||||
sudo docker tag "git.invario-software.eu/invario/inventarsystem:$tag" "git.invario-software.eu/invario/inventarsystem:latest" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
if [ ! -f "$PROJECT_DIR/start.sh" ]; then
|
if [ ! -f "$PROJECT_DIR/start.sh" ]; then
|
||||||
echo "Error: release bundle is missing start.sh"
|
echo "Error: release bundle is missing start.sh"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ cd "$SCRIPT_DIR"
|
|||||||
|
|
||||||
ENV_FILE="$SCRIPT_DIR/.docker-build.env"
|
ENV_FILE="$SCRIPT_DIR/.docker-build.env"
|
||||||
APP_IMAGE_REPO="git.invario-software.eu/invario/inventarsystem"
|
APP_IMAGE_REPO="git.invario-software.eu/invario/inventarsystem"
|
||||||
DIST_DIR="$SCRIPT_DIR/dist"
|
|
||||||
RUNTIME_COMPOSE_OVERRIDE_FILE="$SCRIPT_DIR/.docker-compose.runtime.override.yml"
|
RUNTIME_COMPOSE_OVERRIDE_FILE="$SCRIPT_DIR/.docker-compose.runtime.override.yml"
|
||||||
|
|
||||||
SUDO=""
|
SUDO=""
|
||||||
@@ -279,52 +278,19 @@ EOF
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_app_image_loaded() {
|
ensure_app_image_ready() {
|
||||||
if docker image inspect "$APP_IMAGE_VALUE" >/dev/null 2>&1; then
|
if docker image inspect "$APP_IMAGE_VALUE" >/dev/null 2>&1; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local image_archive
|
echo "Attempting to pull registry image: $APP_IMAGE_VALUE"
|
||||||
image_archive="$(find_local_dist_image_archive || true)"
|
if docker pull "$APP_IMAGE_VALUE" >/dev/null 2>&1; then
|
||||||
if [ -n "$image_archive" ]; then
|
|
||||||
echo "Loading app image from local dist artifact: $image_archive"
|
|
||||||
if docker load -i "$image_archive" >/dev/null 2>&1 && docker image inspect "$APP_IMAGE_VALUE" >/dev/null 2>&1; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo "Warning: failed to load expected app image from $image_archive"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Error: local app image not found: $APP_IMAGE_VALUE"
|
|
||||||
echo "Run ./update.sh so the nightly updater loads the release image first."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
find_local_dist_image_archive() {
|
|
||||||
local tag archive
|
|
||||||
|
|
||||||
if [ ! -d "$DIST_DIR" ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
tag="${APP_IMAGE_VALUE##*:}"
|
|
||||||
for archive in \
|
|
||||||
"$DIST_DIR/inventarsystem-image-$tag.tar.gz" \
|
|
||||||
"$DIST_DIR/inventarsystem-image-$tag.tar" \
|
|
||||||
"$DIST_DIR/inventarsystem-image.tar.gz" \
|
|
||||||
"$DIST_DIR/inventarsystem-image.tar"; do
|
|
||||||
if [ -f "$archive" ]; then
|
|
||||||
echo "$archive"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
archive="$(find "$DIST_DIR" -maxdepth 1 -type f \( -name 'inventarsystem-image-*.tar.gz' -o -name 'inventarsystem-image-*.tar' \) | sort | tail -n1)"
|
|
||||||
if [ -n "$archive" ]; then
|
|
||||||
echo "$archive"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 1
|
echo "Error: app image not found locally and pull failed: $APP_IMAGE_VALUE"
|
||||||
|
echo "Run ./update.sh to pull the latest release image from the registry."
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
configure_nuitka_mode() {
|
configure_nuitka_mode() {
|
||||||
@@ -605,7 +571,6 @@ verify_stack_health() {
|
|||||||
fi
|
fi
|
||||||
compose_args+=(--env-file "$ENV_FILE")
|
compose_args+=(--env-file "$ENV_FILE")
|
||||||
|
|
||||||
# Try health check with optional restart on first failure
|
|
||||||
while [[ $retry_count -lt 2 ]]; do
|
while [[ $retry_count -lt 2 ]]; do
|
||||||
echo "Waiting for containers to become healthy... (attempt $((retry_count + 1))/2)"
|
echo "Waiting for containers to become healthy... (attempt $((retry_count + 1))/2)"
|
||||||
for _ in $(seq 1 60); do
|
for _ in $(seq 1 60); do
|
||||||
@@ -623,7 +588,6 @@ verify_stack_health() {
|
|||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
# First failure: attempt recovery by restarting containers
|
|
||||||
if [[ $retry_count -eq 0 ]]; then
|
if [[ $retry_count -eq 0 ]]; then
|
||||||
echo "Health check failed. Attempting to restart containers..."
|
echo "Health check failed. Attempting to restart containers..."
|
||||||
docker compose "${compose_args[@]}" ps || true
|
docker compose "${compose_args[@]}" ps || true
|
||||||
@@ -636,7 +600,6 @@ verify_stack_health() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Final failure
|
|
||||||
echo "Error: stack health check failed after restart attempt."
|
echo "Error: stack health check failed after restart attempt."
|
||||||
docker compose "${compose_args[@]}" ps || true
|
docker compose "${compose_args[@]}" ps || true
|
||||||
docker compose "${compose_args[@]}" logs --tail=120 app redis mongodb || true
|
docker compose "${compose_args[@]}" logs --tail=120 app redis mongodb || true
|
||||||
@@ -654,7 +617,7 @@ resolve_app_image
|
|||||||
configure_host_ports
|
configure_host_ports
|
||||||
ensure_min_docker_disk_space
|
ensure_min_docker_disk_space
|
||||||
detect_server_capacity
|
detect_server_capacity
|
||||||
ensure_app_image_loaded
|
ensure_app_image_ready
|
||||||
write_env_file
|
write_env_file
|
||||||
write_runtime_compose_override
|
write_runtime_compose_override
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Release-only updater for Docker deployment.
|
# Release-only updater for Docker deployment.
|
||||||
# Updates are pulled exclusively from Gitea Releases assets.
|
# Updates pull the deployment bundle from Gitea and the Docker Image via 'docker pull'.
|
||||||
|
|
||||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
LOG_DIR="$PROJECT_DIR/logs"
|
LOG_DIR="$PROJECT_DIR/logs"
|
||||||
@@ -11,13 +11,10 @@ STATE_FILE="$PROJECT_DIR/.release-version"
|
|||||||
REPO_SLUG="Invario/Inventarsystem"
|
REPO_SLUG="Invario/Inventarsystem"
|
||||||
API_URL="https://git.invario-software.eu/api/v1/repos/$REPO_SLUG/releases/latest"
|
API_URL="https://git.invario-software.eu/api/v1/repos/$REPO_SLUG/releases/latest"
|
||||||
BUNDLE_ASSET="inventarsystem-docker-bundle.tar.gz"
|
BUNDLE_ASSET="inventarsystem-docker-bundle.tar.gz"
|
||||||
APP_IMAGE_ASSET_PREFIX="inventarsystem-image-"
|
|
||||||
ENV_FILE="$PROJECT_DIR/.docker-build.env"
|
ENV_FILE="$PROJECT_DIR/.docker-build.env"
|
||||||
APP_IMAGE_REPO="git.invario-software.eu/invario/inventarsystem"
|
APP_IMAGE_REPO="git.invario-software.eu/invario/inventarsystem"
|
||||||
DIST_DIR="$PROJECT_DIR/dist"
|
|
||||||
COMPOSE_FILE="docker-compose-multitenant.yml"
|
COMPOSE_FILE="docker-compose-multitenant.yml"
|
||||||
MIN_ROOT_FREE_MB="${INVENTAR_MIN_ROOT_FREE_MB:-2048}"
|
MIN_ROOT_FREE_MB="${INVENTAR_MIN_ROOT_FREE_MB:-2048}"
|
||||||
DIST_KEEP_COUNT="${INVENTAR_DIST_KEEP_COUNT:-2}"
|
|
||||||
MODE="release"
|
MODE="release"
|
||||||
|
|
||||||
mkdir -p "$LOG_DIR"
|
mkdir -p "$LOG_DIR"
|
||||||
@@ -109,36 +106,6 @@ ensure_min_root_disk_space() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_old_dist_artifacts() {
|
|
||||||
local keep_count
|
|
||||||
keep_count="$DIST_KEEP_COUNT"
|
|
||||||
|
|
||||||
if [ ! -d "$DIST_DIR" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! [[ "$keep_count" =~ ^[0-9]+$ ]]; then
|
|
||||||
keep_count=2
|
|
||||||
fi
|
|
||||||
|
|
||||||
mapfile -t archives < <(find "$DIST_DIR" -maxdepth 1 -type f \( -name 'inventarsystem-image-*.tar.gz' -o -name 'inventarsystem-image-*.tar' \) -printf '%T@ %p\n' | sort -nr | awk '{print $2}')
|
|
||||||
if [ "${#archives[@]}" -le "$keep_count" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local index old_archive deleted=0
|
|
||||||
for (( index=keep_count; index<${#archives[@]}; index++ )); do
|
|
||||||
old_archive="${archives[$index]}"
|
|
||||||
if rm -f "$old_archive"; then
|
|
||||||
deleted=$((deleted + 1))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$deleted" -gt 0 ]; then
|
|
||||||
log_message "Cleaned up $deleted old dist image archive(s)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup_docker_dangling_images() {
|
cleanup_docker_dangling_images() {
|
||||||
if docker image prune -f >> "$LOG_FILE" 2>&1; then
|
if docker image prune -f >> "$LOG_FILE" 2>&1; then
|
||||||
log_message "Cleaned up dangling Docker images"
|
log_message "Cleaned up dangling Docker images"
|
||||||
@@ -153,7 +120,6 @@ Usage: $0 [options]
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
--multitenant Use docker-compose-multitenant.yml (default)
|
--multitenant Use docker-compose-multitenant.yml (default)
|
||||||
development Install development build from Gitea Registry or local dist
|
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -165,10 +131,6 @@ parse_args() {
|
|||||||
COMPOSE_FILE="docker-compose-multitenant.yml"
|
COMPOSE_FILE="docker-compose-multitenant.yml"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
development|dev)
|
|
||||||
MODE="development"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-h|--help)
|
-h|--help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
@@ -216,14 +178,12 @@ create_backup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetch_release_metadata() {
|
fetch_release_metadata() {
|
||||||
local meta_file
|
local meta_file="$1"
|
||||||
meta_file="$1"
|
|
||||||
curl -fsSL "$API_URL" -o "$meta_file"
|
curl -fsSL "$API_URL" -o "$meta_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_latest_tag() {
|
parse_latest_tag() {
|
||||||
local meta_file
|
local meta_file="$1"
|
||||||
meta_file="$1"
|
|
||||||
python3 - <<'PY' "$meta_file"
|
python3 - <<'PY' "$meta_file"
|
||||||
import json, sys
|
import json, sys
|
||||||
with open(sys.argv[1], 'r', encoding='utf-8') as f:
|
with open(sys.argv[1], 'r', encoding='utf-8') as f:
|
||||||
@@ -233,9 +193,8 @@ PY
|
|||||||
}
|
}
|
||||||
|
|
||||||
parse_asset_url() {
|
parse_asset_url() {
|
||||||
local meta_file asset_name
|
local meta_file="$1"
|
||||||
meta_file="$1"
|
local asset_name="$2"
|
||||||
asset_name="$2"
|
|
||||||
python3 - <<'PY' "$meta_file" "$asset_name"
|
python3 - <<'PY' "$meta_file" "$asset_name"
|
||||||
import json, sys
|
import json, sys
|
||||||
meta_file, asset_name = sys.argv[1], sys.argv[2]
|
meta_file, asset_name = sys.argv[1], sys.argv[2]
|
||||||
@@ -248,31 +207,6 @@ for asset in data.get('assets', []):
|
|||||||
PY
|
PY
|
||||||
}
|
}
|
||||||
|
|
||||||
load_release_image() {
|
|
||||||
local meta_file tag image_asset image_url tmp_dir archive
|
|
||||||
|
|
||||||
meta_file="$1"
|
|
||||||
tag="$2"
|
|
||||||
image_asset="${APP_IMAGE_ASSET_PREFIX}${tag}.tar.gz"
|
|
||||||
image_url="$(parse_asset_url "$meta_file" "$image_asset")"
|
|
||||||
|
|
||||||
if [ -z "$image_url" ]; then
|
|
||||||
log_message "ERROR: Release image asset not found: $image_asset"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
tmp_dir="$(mktemp -d)"
|
|
||||||
archive="$tmp_dir/$image_asset"
|
|
||||||
trap 'rm -rf "${tmp_dir:-}"' RETURN
|
|
||||||
|
|
||||||
log_message "Loading app image from release asset $image_asset"
|
|
||||||
curl -fL "$image_url" -o "$archive"
|
|
||||||
docker load -i "$archive" >> "$LOG_FILE" 2>&1
|
|
||||||
docker tag "$APP_IMAGE_REPO:$tag" "$APP_IMAGE_REPO:latest" >> "$LOG_FILE" 2>&1 || true
|
|
||||||
|
|
||||||
trap - RETURN
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh_runtime_scripts_from_main() {
|
refresh_runtime_scripts_from_main() {
|
||||||
local start_url stop_url restart_url update_url
|
local start_url stop_url restart_url update_url
|
||||||
start_url="https://git.invario-software.eu/$REPO_SLUG/raw/branch/main/start.sh"
|
start_url="https://git.invario-software.eu/$REPO_SLUG/raw/branch/main/start.sh"
|
||||||
@@ -285,61 +219,13 @@ refresh_runtime_scripts_from_main() {
|
|||||||
curl -fsSL "$restart_url" -o "$PROJECT_DIR/restart.sh" || log_message "WARNING: Could not refresh restart.sh from main"
|
curl -fsSL "$restart_url" -o "$PROJECT_DIR/restart.sh" || log_message "WARNING: Could not refresh restart.sh from main"
|
||||||
curl -fsSL "$update_url" -o "$PROJECT_DIR/update.sh" || log_message "WARNING: Could not refresh update.sh from main"
|
curl -fsSL "$update_url" -o "$PROJECT_DIR/update.sh" || log_message "WARNING: Could not refresh update.sh from main"
|
||||||
|
|
||||||
chmod +x "$PROJECT_DIR/start.sh" "$PROJECT_DIR/stop.sh" "$PROJECT_DIR/restart.sh" "$PROJECT_DIR/update.sh"
|
chmod +x "$PROJECT_DIR/start.sh" "$PROJECT_DIR/stop.sh" "$PROJECT_DIR/restart.sh" "$PROJECT_DIR/update.sh" 2>/dev/null || true
|
||||||
}
|
|
||||||
|
|
||||||
find_local_dist_image_archive() {
|
|
||||||
local tag="$1"
|
|
||||||
local archive
|
|
||||||
|
|
||||||
if [ ! -d "$DIST_DIR" ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for archive in \
|
|
||||||
"$DIST_DIR/inventarsystem-image-$tag.tar.gz" \
|
|
||||||
"$DIST_DIR/inventarsystem-image-$tag.tar" \
|
|
||||||
"$DIST_DIR/inventarsystem-image.tar.gz" \
|
|
||||||
"$DIST_DIR/inventarsystem-image.tar"; do
|
|
||||||
if [ -f "$archive" ]; then
|
|
||||||
echo "$archive"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
archive="$(find "$DIST_DIR" -maxdepth 1 -type f \( -name 'inventarsystem-image-*.tar.gz' -o -name 'inventarsystem-image-*.tar' \) | sort | tail -n1)"
|
|
||||||
if [ -n "$archive" ]; then
|
|
||||||
echo "$archive"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
load_local_dist_image() {
|
|
||||||
local tag="$1"
|
|
||||||
local archive
|
|
||||||
|
|
||||||
archive="$(find_local_dist_image_archive "$tag" || true)"
|
|
||||||
if [ -z "$archive" ]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_message "Loading app image from local dist artifact: $archive"
|
|
||||||
if docker load -i "$archive" >> "$LOG_FILE" 2>&1; then
|
|
||||||
docker tag "$APP_IMAGE_REPO:$tag" "$APP_IMAGE_REPO:latest" >> "$LOG_FILE" 2>&1 || true
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_message "WARNING: Failed to load local dist artifact: $archive"
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
download_and_extract_bundle() {
|
download_and_extract_bundle() {
|
||||||
local url tmp_dir archive
|
local url="$1"
|
||||||
url="$1"
|
local tmp_dir="$2"
|
||||||
tmp_dir="$2"
|
local archive="$tmp_dir/$BUNDLE_ASSET"
|
||||||
archive="$tmp_dir/$BUNDLE_ASSET"
|
|
||||||
|
|
||||||
curl -fL "$url" -o "$archive"
|
curl -fL "$url" -o "$archive"
|
||||||
tar -xzf "$archive" -C "$tmp_dir"
|
tar -xzf "$archive" -C "$tmp_dir"
|
||||||
@@ -375,19 +261,15 @@ download_and_extract_bundle() {
|
|||||||
|
|
||||||
# Ensure executable permissions on all copied scripts
|
# Ensure executable permissions on all copied scripts
|
||||||
chmod +x "$PROJECT_DIR/start.sh" "$PROJECT_DIR/stop.sh" "$PROJECT_DIR/restart.sh" "$PROJECT_DIR/update.sh" "$PROJECT_DIR/backup.sh" "$PROJECT_DIR/manage-tenant.sh" "$PROJECT_DIR/run-tenant-cmd.sh" 2>/dev/null || true
|
chmod +x "$PROJECT_DIR/start.sh" "$PROJECT_DIR/stop.sh" "$PROJECT_DIR/restart.sh" "$PROJECT_DIR/update.sh" "$PROJECT_DIR/backup.sh" "$PROJECT_DIR/manage-tenant.sh" "$PROJECT_DIR/run-tenant-cmd.sh" 2>/dev/null || true
|
||||||
chmod +x "$PROJECT_DIR"/manage-tenant.sh "$PROJECT_DIR"/run-tenant-cmd.sh 2>/dev/null || true
|
|
||||||
|
|
||||||
if [ ! -f "$PROJECT_DIR/config.json" ] && [ -f "$tmp_dir/config.json" ]; then
|
if [ ! -f "$PROJECT_DIR/config.json" ] && [ -f "$tmp_dir/config.json" ]; then
|
||||||
cp -f "$tmp_dir/config.json" "$PROJECT_DIR/config.json"
|
cp -f "$tmp_dir/config.json" "$PROJECT_DIR/config.json"
|
||||||
log_message "Installed default config.json from release bundle"
|
log_message "Installed default config.json from release bundle"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chmod +x "$PROJECT_DIR/start.sh" "$PROJECT_DIR/stop.sh" "$PROJECT_DIR/restart.sh" "$PROJECT_DIR/update.sh" "$PROJECT_DIR/backup.sh" "$PROJECT_DIR/manage-tenant.sh" "$PROJECT_DIR/run-tenant-cmd.sh" 2>/dev/null || true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deploy() {
|
deploy() {
|
||||||
local tag="$1"
|
local tag="$1"
|
||||||
local meta_file="$2"
|
|
||||||
local app_image="${APP_IMAGE_REPO}:${tag}"
|
local app_image="${APP_IMAGE_REPO}:${tag}"
|
||||||
local compose_path
|
local compose_path
|
||||||
|
|
||||||
@@ -410,17 +292,14 @@ EOF
|
|||||||
printf '\nINVENTAR_APP_IMAGE=%s\n' "$app_image" >> "$ENV_FILE"
|
printf '\nINVENTAR_APP_IMAGE=%s\n' "$app_image" >> "$ENV_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! load_local_dist_image "$tag"; then
|
log_message "Pulling Gitea Registry image $app_image"
|
||||||
if ! load_release_image "$meta_file" "$tag"; then
|
if ! docker pull "$app_image" >> "$LOG_FILE" 2>&1; then
|
||||||
log_message "Falling back to tagged Gitea Registry image $app_image"
|
log_message "Falling back to local Docker build for $app_image"
|
||||||
if ! docker pull "$app_image" >> "$LOG_FILE" 2>&1; then
|
docker build -t "$app_image" "$PROJECT_DIR" >> "$LOG_FILE" 2>&1
|
||||||
log_message "Falling back to local Docker build for $app_image"
|
|
||||||
docker build -t "$app_image" "$PROJECT_DIR" >> "$LOG_FILE" 2>&1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker compose -f "$compose_path" --env-file "$ENV_FILE" pull app mongodb >> "$LOG_FILE" 2>&1
|
# Image wurde oben gepullt, Stack hochfahren
|
||||||
|
docker compose -f "$compose_path" --env-file "$ENV_FILE" pull mongodb redis >> "$LOG_FILE" 2>&1 || true
|
||||||
docker compose -f "$compose_path" --env-file "$ENV_FILE" up -d --remove-orphans >> "$LOG_FILE" 2>&1
|
docker compose -f "$compose_path" --env-file "$ENV_FILE" up -d --remove-orphans >> "$LOG_FILE" 2>&1
|
||||||
docker tag "$app_image" "$APP_IMAGE_REPO:latest" >> "$LOG_FILE" 2>&1 || true
|
docker tag "$app_image" "$APP_IMAGE_REPO:latest" >> "$LOG_FILE" 2>&1 || true
|
||||||
}
|
}
|
||||||
@@ -460,9 +339,12 @@ cleanup_server_space() {
|
|||||||
else
|
else
|
||||||
log_message "WARNING: Docker system prune failed"
|
log_message "WARNING: Docker system prune failed"
|
||||||
fi
|
fi
|
||||||
# Clean up old dist artifacts
|
# Delete legacy dist folder if it exists
|
||||||
cleanup_old_dist_artifacts
|
if [ -d "$PROJECT_DIR/dist" ]; then
|
||||||
# Clean up log files older than 7 days
|
rm -rf "$PROJECT_DIR/dist" || true
|
||||||
|
log_message "Legacy dist folder removed"
|
||||||
|
fi
|
||||||
|
# Clean up old log files older than 7 days
|
||||||
if find "$LOG_DIR" -type f -name '*.log' -mtime +7 -exec rm -f {} +; then
|
if find "$LOG_DIR" -type f -name '*.log' -mtime +7 -exec rm -f {} +; then
|
||||||
log_message "Old log files (older than 7 days) cleaned up"
|
log_message "Old log files (older than 7 days) cleaned up"
|
||||||
else
|
else
|
||||||
@@ -511,17 +393,14 @@ EOF
|
|||||||
printf '\nINVENTAR_APP_IMAGE=%s\n' "$app_image" >> "$ENV_FILE"
|
printf '\nINVENTAR_APP_IMAGE=%s\n' "$app_image" >> "$ENV_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Try local dist first, then pull from Gitea Registry
|
log_message "Attempting to pull development image $app_image"
|
||||||
if ! load_local_dist_image "$tag"; then
|
if ! docker pull "$app_image" >> "$LOG_FILE" 2>&1; then
|
||||||
log_message "Attempting to pull development image $app_image"
|
log_message "ERROR: Could not obtain development image $app_image"
|
||||||
if ! docker pull "$app_image" >> "$LOG_FILE" 2>&1; then
|
exit 1
|
||||||
log_message "ERROR: Could not obtain development image $app_image"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Bring up stack
|
# Bring up stack
|
||||||
docker compose -f "$compose_path" --env-file "$ENV_FILE" pull app mongodb >> "$LOG_FILE" 2>&1 || true
|
docker compose -f "$compose_path" --env-file "$ENV_FILE" pull mongodb redis >> "$LOG_FILE" 2>&1 || true
|
||||||
docker compose -f "$compose_path" --env-file "$ENV_FILE" up -d --remove-orphans >> "$LOG_FILE" 2>&1
|
docker compose -f "$compose_path" --env-file "$ENV_FILE" up -d --remove-orphans >> "$LOG_FILE" 2>&1
|
||||||
|
|
||||||
if ! verify_stack_health; then
|
if ! verify_stack_health; then
|
||||||
@@ -581,7 +460,7 @@ EOF
|
|||||||
|
|
||||||
if [ "$current_tag" = "$latest_tag" ]; then
|
if [ "$current_tag" = "$latest_tag" ]; then
|
||||||
log_message "Already on latest release ($latest_tag). Refreshing containers from prebuilt image."
|
log_message "Already on latest release ($latest_tag). Refreshing containers from prebuilt image."
|
||||||
deploy "$latest_tag" "$meta_file"
|
deploy "$latest_tag"
|
||||||
if verify_stack_health; then
|
if verify_stack_health; then
|
||||||
log_message "Container refresh completed"
|
log_message "Container refresh completed"
|
||||||
else
|
else
|
||||||
@@ -611,18 +490,19 @@ EOF
|
|||||||
log_message "Updating from release $latest_tag"
|
log_message "Updating from release $latest_tag"
|
||||||
download_and_extract_bundle "$bundle_url" "$tmp_dir"
|
download_and_extract_bundle "$bundle_url" "$tmp_dir"
|
||||||
refresh_runtime_scripts_from_main
|
refresh_runtime_scripts_from_main
|
||||||
deploy "$latest_tag" "$meta_file"
|
deploy "$latest_tag"
|
||||||
if ! verify_stack_health; then
|
if ! verify_stack_health; then
|
||||||
log_message "ERROR: Updated stack failed health check"
|
log_message "ERROR: Updated stack failed health check"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$latest_tag" > "$STATE_FILE"
|
echo "$latest_tag" > "$STATE_FILE"
|
||||||
cleanup_old_dist_artifacts
|
|
||||||
cleanup_docker_dangling_images
|
cleanup_docker_dangling_images
|
||||||
log_message "Update completed successfully to release $latest_tag"
|
log_message "Update completed successfully to release $latest_tag"
|
||||||
|
|
||||||
sudo ./opt/Inventarsystem/restart.sh
|
if [ -x "./opt/Inventarsystem/restart.sh" ]; then
|
||||||
|
sudo ./opt/Inventarsystem/restart.sh
|
||||||
|
fi
|
||||||
echo "Restart of the Server Completed"
|
echo "Restart of the Server Completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user