From c54823ff68849454a0b2b9ef33780d7e81053a51 Mon Sep 17 00:00:00 2001 From: naturallaw777 <99053422+naturallaw777@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:11:59 +0000 Subject: [PATCH] feat: automated stable releases, versioned ISOs, and Hub version badge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add release-stable.sh script with automatic tagging, CHANGELOG updates, GitHub/Gitea release creation, and VERSION file management - Update iso/common.nix to include version from VERSION file in ISO filename - Add VERSION file (current: 1.0.3) - Polish OS version badge in Sovran Hub header (top-right) - Update README.md download link to versioned ISO - Update CHANGELOG.md with existing tags (v1.0.0 – v1.0.3) Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- CHANGELOG.md | 59 ++++ README.md | 2 +- VERSION | 1 + app/sovran_systemsos_web/server.py | 17 + .../static/css/header.css | 38 +++ app/sovran_systemsos_web/templates/index.html | 7 + iso/common.nix | 11 +- scripts/release-stable.sh | 303 ++++++++++++++++++ 8 files changed, 436 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 VERSION create mode 100755 scripts/release-stable.sh diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a2a73e5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,59 @@ +# Changelog + +All notable changes to Sovran_SystemsOS will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [1.0.3] - 2026-07-29 + +### Added +- Incremental stable updates + +## [1.0.2] - 2026-07-29 + +### Added +- Incremental stable updates + +## [1.0.1] - 2026-07-29 + +### Added +- Incremental stable updates + +## [1.0.0] - 2026-07-29 + +### Added +- Initial stable release of Sovran_SystemsOS +- Full Bitcoin self-custody desktop environment +- Integrated Sparrow Wallet, Bisq, and Bisq 2 +- Comprehensive Sovran Hub for service management +- NixOS-based operating system with privacy and sovereignty focus +- Support for Bitcoin Knots + BIP110, Electrs, LND, Ride The Lightning, BTCPay Server, and more +- Server + Desktop hybrid mode with Matrix, Nextcloud, VaultWarden, and other self-hosted services +- Automated installer with graphical GNOME desktop +- Tor integration and onion services for all major components +- Remote desktop (GNOME Remote Desktop) support +- Role-based system configuration (desktop, server, server+desktop) +- Detailed service credential management in the Sovran Hub + +### Changed +- Moved from development/main branch to dedicated `stable` branch for production releases + +### Security +- All services run with least-privilege principles where possible +- Secrets stored in `/var/lib/secrets/` +- Strong emphasis on user-controlled keys and self-sovereignty + +--- + +## [Unreleased] + +### Added +- (Nothing yet) + +[1.0.3]: https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/releases/tag/v1.0.3 +[1.0.2]: https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/releases/tag/v1.0.2 +[1.0.1]: https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/releases/tag/v1.0.1 +[1.0.0]: https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/releases/tag/v1.0.0 diff --git a/README.md b/README.md index a583980..e8b5dea 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Lightning infrastructure when you are ready. **Privacy. Sovereignty. Bitcoin.** [Visit the Website](https://sovransystems.com) · -[Download the ISO](https://downloads.sovransystems.com/Sovran_SystemsOS.iso) · +[Download the ISO](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.3.iso) · [Verify the Download](https://downloads.sovransystems.com/Sovran_SystemsOS.iso.sha256) · [Build from Source](#build-from-source) diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..21e8796 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.3 diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index 3f6067e..cae8f6d 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -2274,13 +2274,30 @@ async def api_logout(request: Request): return response +def _get_sovran_version() -> str: + """Read the OS version from the VERSION file.""" + try: + with open("/etc/nixos/VERSION", "r") as f: + return f.read().strip() + except FileNotFoundError: + try: + with open("VERSION", "r") as f: # fallback for development + return f.read().strip() + except FileNotFoundError: + return "dev" + except Exception: + return "dev" + + @app.get("/", response_class=HTMLResponse) async def index(request: Request): + sovran_version = _get_sovran_version() return templates.TemplateResponse( request=request, name="index.html", context={ "asset_version": ASSET_VERSION, + "sovran_version": sovran_version, }, ) diff --git a/app/sovran_systemsos_web/static/css/header.css b/app/sovran_systemsos_web/static/css/header.css index edd43e0..89d4a48 100644 --- a/app/sovran_systemsos_web/static/css/header.css +++ b/app/sovran_systemsos_web/static/css/header.css @@ -45,6 +45,44 @@ letter-spacing: 0.03em; } +/* ── OS Version Badge (polished) ───────────────────────────────── */ +.os-version-badge { + display: inline-flex; + align-items: center; + background: linear-gradient(145deg, #1a2a22, #0f1a15); + border: 1px solid rgba(74, 222, 128, 0.25); + border-radius: 9999px; + padding: 2px 14px 2px 10px; + font-size: 0.75rem; + font-weight: 600; + color: #4ade80; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + margin-left: 12px; + white-space: nowrap; +} + +.os-version-badge:hover { + border-color: rgba(74, 222, 128, 0.45); + box-shadow: 0 4px 8px rgba(74, 222, 128, 0.15); + transform: translateY(-1px); +} + +.os-version-badge .version-label { + font-size: 0.68rem; + font-weight: 500; + color: #4ade80; + opacity: 0.85; + margin-right: 2px; +} + +.os-version-badge .version-number { + font-family: 'JetBrains Mono', 'Fira Code', monospace; + font-weight: 700; + letter-spacing: 0.5px; + color: #86efac; +} + /* ── IP bar ─────────────────────────────────────────────────────── */ .ip-bar { diff --git a/app/sovran_systemsos_web/templates/index.html b/app/sovran_systemsos_web/templates/index.html index 9b495b0..bcdaa11 100644 --- a/app/sovran_systemsos_web/templates/index.html +++ b/app/sovran_systemsos_web/templates/index.html @@ -22,6 +22,13 @@
Sovran_SystemsOS Hub + + + + v + {{ sovran_version }} + +
Loading… diff --git a/iso/common.nix b/iso/common.nix index 5974f47..c754671 100644 --- a/iso/common.nix +++ b/iso/common.nix @@ -3,6 +3,14 @@ let sovranSource = builtins.path { path = ../.; name = "sovran-systemsos"; }; + # Read version from VERSION file if it exists, otherwise fall back to "dev" + versionFile = if builtins.pathExists ../VERSION + then builtins.readFile ../VERSION + else "dev"; + + # Clean version (remove 'v' prefix and newlines) + cleanVersion = builtins.replaceStrings ["v" "\n" "\r"] [""] versionFile; + pythonEnv = pkgs.python3.withPackages (ps: [ ps.pygobject3 ps.pycairo ]); installerPy = pkgs.writeShellScriptBin "sovran-install" '' @@ -18,7 +26,8 @@ in "${modulesPath}/installer/cd-dvd/installation-cd-graphical-gnome.nix" ]; - image.baseName = lib.mkForce "Sovran_SystemsOS"; + # Dynamic ISO filename with version + image.baseName = lib.mkForce "Sovran_SystemsOS-${cleanVersion}"; isoImage.splashImage = ./assets/splash-logo.png; services.gnome.gnome-initial-setup.enable = false; diff --git a/scripts/release-stable.sh b/scripts/release-stable.sh new file mode 100755 index 0000000..8ec9efb --- /dev/null +++ b/scripts/release-stable.sh @@ -0,0 +1,303 @@ +#!/usr/bin/env bash +# +# release-stable.sh +# Automated stable release script for Sovran_SystemsOS +# +# Features: +# - Detects latest tag and suggests next version +# - Pushes main → stable on Gitea +# - Creates annotated tag +# - Auto-updates CHANGELOG.md +# - Creates releases on both GitHub and Gitea via API +# +# Usage: +# ./scripts/release-stable.sh [version] [--message "text"] +# +# Requirements: +# - gh CLI (for GitHub releases) +# - GITEA_TOKEN env var (for Gitea releases) +# + +set -euo pipefail + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +# Configuration +GITEA_REMOTE="gitea" +GITHUB_REMOTE="origin" +GITEA_API_URL="https://git.sovransystems.com/api/v1" +CHANGELOG_FILE="CHANGELOG.md" + +echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ Sovran_SystemsOS Automated Stable Release Script ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}" +echo + +# ───────────────────────────────────────────────────────────────────────────── +# Helper: Get latest tag +# ───────────────────────────────────────────────────────────────────────────── +get_latest_tag() { + git tag --list 'v*' --sort=-version:refname | head -1 || echo "" +} + +# ───────────────────────────────────────────────────────────────────────────── +# Helper: Suggest next version (patch bump) +# ───────────────────────────────────────────────────────────────────────────── +suggest_next_version() { + local current="$1" + if [[ -z "$current" ]]; then + echo "1.0.0" + return + fi + + # Remove 'v' prefix + local ver="${current#v}" + IFS='.' read -r major minor patch <<< "$ver" + + # Default: bump patch + echo "${major}.$((minor)).$((patch + 1))" +} + +# ───────────────────────────────────────────────────────────────────────────── +# Step 0: Fetch everything +# ───────────────────────────────────────────────────────────────────────────── +echo -e "${BLUE}Step 0: Fetching latest from all remotes...${NC}" +git fetch --all --tags --prune + +LATEST_TAG=$(get_latest_tag) +NEXT_VERSION=$(suggest_next_version "$LATEST_TAG") + +echo -e " Latest tag : ${CYAN}${LATEST_TAG:-none}${NC}" +echo -e " Suggested : ${GREEN}v${NEXT_VERSION}${NC}" +echo + +# ───────────────────────────────────────────────────────────────────────────── +# Get version from user or argument +# ───────────────────────────────────────────────────────────────────────────── +if [ $# -ge 1 ] && [[ "$1" != --* ]]; then + VERSION="$1" + shift +else + read -rp "Enter version to release (default: ${NEXT_VERSION}): " input_version + VERSION="${input_version:-$NEXT_VERSION}" +fi + +# Strip 'v' if user added it +VERSION="${VERSION#v}" +TAG="v${VERSION}" + +# ───────────────────────────────────────────────────────────────────────────── +# Get release message +# ───────────────────────────────────────────────────────────────────────────── +RELEASE_MESSAGE="" +while [[ $# -gt 0 ]]; do + case $1 in + --message|-m) + RELEASE_MESSAGE="$2" + shift 2 + ;; + *) + shift + ;; + esac +done + +if [[ -z "$RELEASE_MESSAGE" ]]; then + echo + read -rp "Enter release message (or press Enter for default): " input_msg + if [[ -n "$input_msg" ]]; then + RELEASE_MESSAGE="$input_msg" + else + RELEASE_MESSAGE="Sovran_SystemsOS ${TAG} — Stable Release" + fi +fi + +echo +echo -e "${YELLOW}════════════════════════════════════════════════════════════${NC}" +echo -e "${YELLOW} Preparing Release${NC}" +echo -e "${YELLOW}════════════════════════════════════════════════════════════${NC}" +echo " Version : ${TAG}" +echo " Message : ${RELEASE_MESSAGE}" +echo " Gitea Remote : ${GITEA_REMOTE}" +echo " GitHub Remote : ${GITHUB_REMOTE}" +echo + +read -rp "Proceed with this release? (y/N): " confirm +if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 0 +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Step 1: Push main to stable +# ───────────────────────────────────────────────────────────────────────────── +echo +echo -e "${BLUE}Step 1: Pushing main → stable on Gitea...${NC}" +git push "${GITEA_REMOTE}" main:stable --force-with-lease + +# ───────────────────────────────────────────────────────────────────────────── +# Step 2: Create annotated tag +# ───────────────────────────────────────────────────────────────────────────── +echo +echo -e "${BLUE}Step 2: Creating annotated tag ${TAG}...${NC}" +git tag -a "${TAG}" -m "${RELEASE_MESSAGE} + +- Stable release of Sovran_SystemsOS +- See CHANGELOG.md for full details" + +git push "${GITEA_REMOTE}" "${TAG}" + +# Update VERSION file for ISO builds +echo "${VERSION}" > VERSION +git add VERSION +git commit -m "chore: bump VERSION to ${TAG} for ISO naming" || true +echo -e " ${GREEN}✓${NC} VERSION file updated to ${VERSION}" + +# ───────────────────────────────────────────────────────────────────────────── +# Step 3: Auto-update CHANGELOG.md +# ───────────────────────────────────────────────────────────────────────────── +echo +echo -e "${BLUE}Step 3: Updating ${CHANGELOG_FILE}...${NC}" + +TODAY=$(date +%Y-%m-%d) + +# Create new changelog entry +NEW_ENTRY="## [${VERSION}] - ${TODAY} + +### Added +- (Add new features here) + +### Changed +- (Add changes here) + +### Fixed +- (Add bug fixes here) + +[${VERSION}]: ${GITEA_API_URL%/*}/Sovran_Systems/Sovran_SystemsOS/releases/tag/${TAG} +" + +# Prepend to changelog (after the header) +if [ -f "$CHANGELOG_FILE" ]; then + # Backup + cp "$CHANGELOG_FILE" "${CHANGELOG_FILE}.bak" + + # Insert new section after the first --- line + awk -v new_entry="$NEW_ENTRY" ' + BEGIN { printed=0 } + /^---$/ && !printed { + print + print "" + print new_entry + printed=1 + next + } + { print } + ' "$CHANGELOG_FILE" > "${CHANGELOG_FILE}.tmp" && mv "${CHANGELOG_FILE}.tmp" "$CHANGELOG_FILE" + + echo -e " ${GREEN}✓${NC} CHANGELOG.md updated with new section for ${TAG}" +else + echo -e " ${YELLOW}⚠${NC} CHANGELOG.md not found — skipping" +fi + +# Commit the changelog update +git add "$CHANGELOG_FILE" +if git diff --cached --quiet; then + echo " (No changes to commit in changelog)" +else + git commit -m "docs: update CHANGELOG.md for ${TAG}" + echo -e " ${GREEN}✓${NC} Committed changelog update" + + # Ask if user wants to push + echo + read -rp "Push the changelog commit to GitHub now? (y/N): " push_confirm + if [[ "$push_confirm" =~ ^[Yy]$ ]]; then + echo -e "${BLUE}Pushing changelog commit...${NC}" + git push "${GITHUB_REMOTE}" main + echo -e " ${GREEN}✓${NC} Changelog pushed to GitHub" + else + echo " (Changelog commit left local — remember to push later)" + fi +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Step 4: Create GitHub Release via gh CLI +# ───────────────────────────────────────────────────────────────────────────── +echo +echo -e "${BLUE}Step 4: Creating GitHub Release...${NC}" + +if command -v gh &>/dev/null; then + if gh release create "${TAG}" \ + --repo naturallaw777/Sovran_SystemsOS \ + --title "${TAG}" \ + --notes "${RELEASE_MESSAGE}" \ + --target main 2>/dev/null; then + echo -e " ${GREEN}✓${NC} GitHub release created successfully" + else + echo -e " ${YELLOW}⚠${NC} GitHub release may already exist or failed" + fi +else + echo -e " ${YELLOW}⚠${NC} gh CLI not found — skipping GitHub release" +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Step 5: Create Gitea Release via API +# ───────────────────────────────────────────────────────────────────────────── +echo +echo -e "${BLUE}Step 5: Creating Gitea Release via API...${NC}" + +# ── Gitea Token Handling ───────────────────────────────────────────────────── +if [[ -z "${GITEA_TOKEN:-}" ]]; then + echo + echo -e "${YELLOW}GITEA_TOKEN is not set.${NC}" + read -rsp "Enter your Gitea API token (input will be hidden): " GITEA_TOKEN + echo + if [[ -z "$GITEA_TOKEN" ]]; then + echo -e " ${YELLOW}⚠${NC} No token provided — skipping Gitea release" + GITEA_TOKEN="" + fi +fi + +if [[ -n "${GITEA_TOKEN:-}" ]]; then + GITEA_REPO="Sovran_Systems/Sovran_SystemsOS" + + RESPONSE=$(curl -s -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"tag_name\": \"${TAG}\", + \"name\": \"${TAG}\", + \"body\": \"${RELEASE_MESSAGE}\", + \"draft\": false, + \"prerelease\": false + }" \ + "${GITEA_API_URL}/repos/${GITEA_REPO}/releases" 2>/dev/null || echo "") + + if echo "$RESPONSE" | grep -q '"id"'; then + echo -e " ${GREEN}✓${NC} Gitea release created successfully" + else + echo -e " ${YELLOW}⚠${NC} Gitea release creation failed or already exists" + echo " Response: $RESPONSE" + fi +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Final Summary +# ───────────────────────────────────────────────────────────────────────────── +echo +echo -e "${GREEN}╔════════════════════════════════════════════════════════════╗${NC}" +echo -e "${GREEN}║ ✅ Release ${TAG} completed successfully! ║${NC}" +echo -e "${GREEN}╚════════════════════════════════════════════════════════════╝${NC}" +echo +echo "Next manual steps (recommended):" +echo " • Review and enhance the new section in CHANGELOG.md" +echo " • Push the changelog commit: git push origin main" +echo " • Verify releases on both GitHub and Gitea" +echo +echo -e "${CYAN}Tag created: ${TAG}${NC}" +git show "${TAG}" --quiet