Compare commits
28
Commits
dc68a477b6
..
v1.0.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b169a04e3 | ||
|
|
d0c2ea933c | ||
|
|
b66c50c195 | ||
|
|
1737e6c7d8 | ||
|
|
782b279e0a | ||
|
|
564e9835ea | ||
|
|
3b8f809e7c | ||
|
|
9df52256d4 | ||
|
|
de481db97c | ||
|
|
9051aed737 | ||
|
|
65376ec8fb | ||
|
|
899f570ded | ||
|
|
d3b41b14fb | ||
|
|
56db634900 | ||
|
|
3522270373 | ||
|
|
efda70c187 | ||
|
|
a32b353eda | ||
|
|
4555764d87 | ||
|
|
c38d4be851 | ||
|
|
5130076400 | ||
|
|
1dbbd6a943 | ||
|
|
f0a640519d | ||
|
|
a14ef03d40 | ||
|
|
a0cc7e6bec | ||
|
|
1ecf245a07 | ||
|
|
f8730cc0ab | ||
|
|
8a766181de | ||
|
|
bd0d2cd812 |
No files matched your search
@@ -7,6 +7,94 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
---
|
||||
|
||||
## [1.0.6] - 2026-08-07
|
||||
|
||||
### Added
|
||||
- Iso: fix replaceStrings length mismatch in cleanVersion
|
||||
- Promote virtual machine trial option in README
|
||||
- Improve installer VM compatibility
|
||||
- Trim redundant dev-vs-stable Gitea explanations in README
|
||||
- Add Arean.Ai to AI tools used for development
|
||||
- Prefix OS version badge with 'v' (v1.0.5)
|
||||
- Match OS version badge styling to service modal version badges
|
||||
- Place OS version badge inline right after the Hub title
|
||||
- Center OS version badge under the Hub title
|
||||
- Strengthen entropy section: add DYOR emphasis and hardware wallet verification note
|
||||
- Add 'About Bitcoin wallet entropy' section to README
|
||||
- README: balance intro between Bitcoin sovereignty and sovereign computing
|
||||
|
||||
### Changed
|
||||
- Removed temp patch file
|
||||
|
||||
### Fixed
|
||||
- Resolve tag range detection and improve release diagnostics
|
||||
- Add token scope diagnostics and un-silence gh release errors
|
||||
- Harden error handling and sanitize exception details across security endpoints (CWE-209)
|
||||
- Sanitize exception handling in verify-integrity and security-reset (CWE-209)
|
||||
- Sanitize api_security_reset errors to prevent exception information exposure (CWE-209)
|
||||
- Use canonical prefix containment check for CodeQL path-injection
|
||||
- Pass sanitized abs_path to os.chown to resolve CodeQL path injection at 4338
|
||||
- Add CodeQL-recognized path sanitization for domain_name
|
||||
- Remove domain substring check for CodeQL incomplete-url
|
||||
- Use sentinel for njalla header check (CodeQL incomplete-url-substring)
|
||||
- Prevent reflected XSS in lnurl-qr print endpoint
|
||||
- Hash root password instead of storing in clear text (CWE-312)
|
||||
- Separate web auth hash from system password file
|
||||
- Fix Matrix Hub admin API credentials
|
||||
- Fix CWE-78: replace subprocess call with Synapse Admin API in create-user endpoint
|
||||
|
||||
### Documentation
|
||||
- Add SECURITY.md detailing security policy and best practices
|
||||
- Versioned CDN downloads + add CDN upload script
|
||||
- Correct active development workflow
|
||||
- Make repo references mirror-neutral for Gitea readers
|
||||
- Clarify GitHub is the dev mirror of the Gitea stable repo
|
||||
[1.0.6]: https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/releases/tag/v1.0.6
|
||||
|
||||
|
||||
## [1.0.6] - 2026-08-07
|
||||
|
||||
### Added
|
||||
- Iso: fix replaceStrings length mismatch in cleanVersion
|
||||
- Promote virtual machine trial option in README
|
||||
- Improve installer VM compatibility
|
||||
- Trim redundant dev-vs-stable Gitea explanations in README
|
||||
- Add Arean.Ai to AI tools used for development
|
||||
- Prefix OS version badge with 'v' (v1.0.5)
|
||||
- Match OS version badge styling to service modal version badges
|
||||
- Place OS version badge inline right after the Hub title
|
||||
- Center OS version badge under the Hub title
|
||||
- Strengthen entropy section: add DYOR emphasis and hardware wallet verification note
|
||||
- Add 'About Bitcoin wallet entropy' section to README
|
||||
- README: balance intro between Bitcoin sovereignty and sovereign computing
|
||||
|
||||
### Changed
|
||||
- Removed temp patch file
|
||||
|
||||
### Fixed
|
||||
- Harden error handling and sanitize exception details across security endpoints (CWE-209)
|
||||
- Sanitize exception handling in verify-integrity and security-reset (CWE-209)
|
||||
- Sanitize api_security_reset errors to prevent exception information exposure (CWE-209)
|
||||
- Use canonical prefix containment check for CodeQL path-injection
|
||||
- Pass sanitized abs_path to os.chown to resolve CodeQL path injection at 4338
|
||||
- Add CodeQL-recognized path sanitization for domain_name
|
||||
- Remove domain substring check for CodeQL incomplete-url
|
||||
- Use sentinel for njalla header check (CodeQL incomplete-url-substring)
|
||||
- Prevent reflected XSS in lnurl-qr print endpoint
|
||||
- Hash root password instead of storing in clear text (CWE-312)
|
||||
- Separate web auth hash from system password file
|
||||
- Fix Matrix Hub admin API credentials
|
||||
- Fix CWE-78: replace subprocess call with Synapse Admin API in create-user endpoint
|
||||
|
||||
### Documentation
|
||||
- Add SECURITY.md detailing security policy and best practices
|
||||
- Versioned CDN downloads + add CDN upload script
|
||||
- Correct active development workflow
|
||||
- Make repo references mirror-neutral for Gitea readers
|
||||
- Clarify GitHub is the dev mirror of the Gitea stable repo
|
||||
[1.0.6]: https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/releases/tag/v1.0.6
|
||||
|
||||
|
||||
## [1.0.5] - 2026-08-04
|
||||
|
||||
### Added
|
||||
|
||||
@@ -21,9 +21,9 @@ Lightning infrastructure, private cloud, and communications platform when you
|
||||
are ready.
|
||||
|
||||
[Visit the Website](https://sovransystems.com) ·
|
||||
[Download the ISO](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.5.iso) ·
|
||||
[Download the ISO](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.6.iso) ·
|
||||
[Try it safely in a VM](#try-it-first-in-a-virtual-machine) ·
|
||||
[Verify the Download](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.5.iso.sha256) ·
|
||||
[Verify the Download](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.6.iso.sha256) ·
|
||||
[Build from Source](#build-from-source)
|
||||
|
||||
<img src="assets/desktop-screenshot.png" alt="Sovran_SystemsOS private Bitcoin desktop" width="800" />
|
||||
@@ -342,8 +342,8 @@ with an imaging application such as [Balena Etcher](https://etcher.balena.io).
|
||||
|
||||
### 1. Download the ISO and checksum
|
||||
|
||||
- [Download Sovran_SystemsOS-1.0.5.iso](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.5.iso)
|
||||
- [Download Sovran_SystemsOS-1.0.5.iso.sha256](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.5.iso.sha256)
|
||||
- [Download Sovran_SystemsOS-1.0.6.iso](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.6.iso)
|
||||
- [Download Sovran_SystemsOS-1.0.6.iso.sha256](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.6.iso.sha256)
|
||||
|
||||
The download may take some time. Do not rename or modify the ISO before
|
||||
verifying it, and keep both files in the same folder.
|
||||
@@ -361,16 +361,16 @@ checksum exactly.
|
||||
Open a terminal in the download folder and run:
|
||||
|
||||
```bash
|
||||
sha256sum --check Sovran_SystemsOS-1.0.5.iso.sha256
|
||||
sha256sum --check Sovran_SystemsOS-1.0.6.iso.sha256
|
||||
```
|
||||
|
||||
A successful comparison reports:
|
||||
|
||||
```text
|
||||
Sovran_SystemsOS-1.0.5.iso: OK
|
||||
Sovran_SystemsOS-1.0.6.iso: OK
|
||||
```
|
||||
|
||||
You can also run `sha256sum Sovran_SystemsOS-1.0.5.iso` and compare the output
|
||||
You can also run `sha256sum Sovran_SystemsOS-1.0.6.iso` and compare the output
|
||||
against the checksum file manually.
|
||||
|
||||
</details>
|
||||
@@ -381,11 +381,11 @@ against the checksum file manually.
|
||||
Open Terminal in the download folder and run:
|
||||
|
||||
```bash
|
||||
shasum -a 256 Sovran_SystemsOS-1.0.5.iso
|
||||
shasum -a 256 Sovran_SystemsOS-1.0.6.iso
|
||||
```
|
||||
|
||||
Compare the value shown in Terminal with the value inside
|
||||
`Sovran_SystemsOS-1.0.5.iso.sha256`.
|
||||
`Sovran_SystemsOS-1.0.6.iso.sha256`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -395,7 +395,7 @@ Compare the value shown in Terminal with the value inside
|
||||
Open PowerShell in the download folder and run:
|
||||
|
||||
```powershell
|
||||
Get-FileHash .\Sovran_SystemsOS-1.0.5.iso -Algorithm SHA256
|
||||
Get-FileHash .\Sovran_SystemsOS-1.0.6.iso -Algorithm SHA256
|
||||
```
|
||||
|
||||
Compare the value under `Hash` with the published checksum.
|
||||
@@ -410,7 +410,7 @@ match exactly.
|
||||
|
||||
1. Download and install [Balena Etcher](https://etcher.balena.io), then
|
||||
connect the USB drive.
|
||||
2. Choose **Flash from file** and select `Sovran_SystemsOS-1.0.5.iso`.
|
||||
2. Choose **Flash from file** and select `Sovran_SystemsOS-1.0.6.iso`.
|
||||
3. Choose **Select target**, select the USB drive, and review your selection
|
||||
carefully.
|
||||
4. Choose **Flash** and wait for the writing and verification process to
|
||||
@@ -879,7 +879,7 @@ primary location for collaboration. Please read our
|
||||
## Privacy. Sovereignty. Bitcoin.
|
||||
|
||||
[Visit Sovran Systems](https://sovransystems.com) ·
|
||||
[Download Sovran_SystemsOS](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.5.iso) ·
|
||||
[Download Sovran_SystemsOS](https://downloads.sovransystems.com/Sovran_SystemsOS-1.0.6.iso) ·
|
||||
[View the License](LICENSE)
|
||||
|
||||
</div>
|
||||
@@ -1554,7 +1554,11 @@ def _resolve_credential(cred: dict) -> dict | None:
|
||||
else:
|
||||
return None
|
||||
|
||||
value = prefix + raw + suffix
|
||||
# Detect scrypt hash (salt_hex:hash_hex) — passwords stored securely
|
||||
if re.match(r'^[0-9a-f]{32}:[0-9a-f]{64,}$', raw):
|
||||
value = "(stored securely — use Security Reset to set a new one)"
|
||||
else:
|
||||
value = prefix + raw + suffix
|
||||
result = {"label": label, "value": value, "multiline": multiline}
|
||||
|
||||
if qrcode:
|
||||
@@ -4325,10 +4329,16 @@ def _ensure_domains_dir() -> None:
|
||||
|
||||
def _chown_to_caddy(path: str) -> None:
|
||||
"""Set the owner of a file to caddy:root (best-effort)."""
|
||||
# CodeQL path-injection: ensure path is inside DOMAINS_DIR or is NJALLA_SCRIPT
|
||||
try:
|
||||
base_dir = os.path.abspath(DOMAINS_DIR)
|
||||
njalla_file = os.path.abspath(NJALLA_SCRIPT)
|
||||
abs_path = os.path.abspath(path)
|
||||
if abs_path != njalla_file and not abs_path.startswith(base_dir + os.sep):
|
||||
return
|
||||
pw = pwd.getpwnam("caddy")
|
||||
os.chown(path, pw.pw_uid, 0)
|
||||
except KeyError:
|
||||
os.chown(abs_path, pw.pw_uid, 0)
|
||||
except (KeyError, ValueError, OSError):
|
||||
pass
|
||||
|
||||
|
||||
@@ -4337,6 +4347,9 @@ def _validate_safe_name(name: str) -> bool:
|
||||
return bool(name) and _SAFE_NAME_RE.match(name) is not None
|
||||
|
||||
|
||||
_NJALLA_HEADER_SENTINEL = "# SOVRAN_NJALLA_HEADER"
|
||||
|
||||
|
||||
def _ensure_njalla_script() -> None:
|
||||
"""Create the base njalla.sh (shebang + public-IP lookup) if it is missing.
|
||||
|
||||
@@ -4355,10 +4368,28 @@ def _ensure_njalla_script() -> None:
|
||||
existing = f.read()
|
||||
except OSError:
|
||||
pass
|
||||
if "myip.opendns.com" in existing:
|
||||
# Use a unique sentinel instead of substring domain check — avoids
|
||||
# CodeQL py/incomplete-url-substring-sanitization false positive and
|
||||
# is more robust than matching "myip.opendns.com" anywhere in file.
|
||||
if _NJALLA_HEADER_SENTINEL in existing:
|
||||
return # base header already present
|
||||
# Backwards compat: old files have the dig line but no sentinel.
|
||||
# Check for the dig marker without using a domain substring to avoid
|
||||
# CodeQL py/incomplete-url-substring-sanitization.
|
||||
if "IP=$(dig" in existing:
|
||||
# Migrate old file by prepending sentinel for future checks
|
||||
try:
|
||||
with open(NJALLA_SCRIPT, "r") as f:
|
||||
old_content = f.read()
|
||||
with open(NJALLA_SCRIPT, "w") as f:
|
||||
f.write(f"{_NJALLA_HEADER_SENTINEL}\n" + old_content)
|
||||
os.chmod(NJALLA_SCRIPT, 0o755)
|
||||
except OSError:
|
||||
pass
|
||||
return
|
||||
header = (
|
||||
"#!/usr/bin/env bash\n"
|
||||
f"{_NJALLA_HEADER_SENTINEL}\n"
|
||||
"IP=$(dig @resolver4.opendns.com myip.opendns.com +short -4)\n\n"
|
||||
"## Add DDNS entries below — one curl per line\n"
|
||||
"## Managed via Sovran Hub web interface\n"
|
||||
@@ -4503,7 +4534,16 @@ async def api_domains_set(req: DomainSetRequest):
|
||||
)
|
||||
|
||||
_ensure_domains_dir()
|
||||
domain_path = os.path.join(DOMAINS_DIR, req.domain_name)
|
||||
# --- CodeQL path-injection fix ---
|
||||
safe_name = os.path.basename(req.domain_name)
|
||||
if safe_name != req.domain_name or not _validate_safe_name(safe_name):
|
||||
raise HTTPException(status_code=400, detail="Invalid domain_name")
|
||||
|
||||
base_dir = os.path.abspath(DOMAINS_DIR)
|
||||
domain_path = os.path.abspath(os.path.join(base_dir, safe_name))
|
||||
if not domain_path.startswith(base_dir + os.sep):
|
||||
raise HTTPException(status_code=400, detail="Invalid domain_name")
|
||||
|
||||
with open(domain_path, "w") as f:
|
||||
f.write(normalized)
|
||||
_chown_to_caddy(domain_path)
|
||||
@@ -4963,9 +5003,16 @@ async def api_nwc_wallet_lnurl_qr_print(wallet_identifier: str):
|
||||
if err:
|
||||
return err
|
||||
address = wallet.get("lightning_address") or f"{wallet['alias']}@{domain}"
|
||||
name = _html_escape(str(wallet.get("name") or "Wallet"))
|
||||
addr_html = _html_escape(address)
|
||||
img_src = f"/api/nwc/wallets/{urllib.parse.quote(wallet_identifier, safe='')}/lnurl-qr.png?scale=24"
|
||||
name = _html_escape(str(wallet.get("name") or "Wallet"), quote=True)
|
||||
addr_html = _html_escape(address, quote=True)
|
||||
# ── XSS FIX: do not reflect raw wallet_identifier (attacker-controlled) ──
|
||||
# Use canonical ID from the resolved wallet (DB = trusted), then
|
||||
# URL-encode for URL context + HTML-escape for HTML attribute context.
|
||||
# urllib.parse.quote alone is NOT recognized by CodeQL as an HTML sanitizer.
|
||||
canonical_id = str(wallet.get("id") or wallet.get("pubkey") or wallet_identifier)
|
||||
safe_id = urllib.parse.quote(canonical_id, safe="")
|
||||
img_src_raw = f"/api/nwc/wallets/{safe_id}/lnurl-qr.png?scale=24"
|
||||
img_src = _html_escape(img_src_raw, quote=True)
|
||||
html_doc = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -5006,7 +5053,14 @@ async def api_nwc_wallet_lnurl_qr_print(wallet_identifier: str):
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
return HTMLResponse(content=html_doc, headers={"Cache-Control": "no-store"})
|
||||
return HTMLResponse(
|
||||
content=html_doc,
|
||||
headers={
|
||||
"Cache-Control": "no-store",
|
||||
"Content-Security-Policy": "default-src 'none'; img-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; base-uri 'none'; frame-ancestors 'none'",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -5071,7 +5125,8 @@ async def api_security_reset():
|
||||
import shutil as _shutil
|
||||
_shutil.rmtree(path, ignore_errors=True)
|
||||
except Exception as exc:
|
||||
errors.append(f"rm {path}: {exc}")
|
||||
logger.warning("Failed to wipe path %s: %s", path, exc)
|
||||
errors.append("wipe_path_failed")
|
||||
|
||||
# Drop PostgreSQL databases (matrix-synapse, nextcloud, etc.)
|
||||
try:
|
||||
@@ -5089,7 +5144,8 @@ async def api_security_reset():
|
||||
capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
except Exception as exc:
|
||||
errors.append(f"postgres wipe: {exc}")
|
||||
logger.warning("Postgres wipe failed: %s", exc)
|
||||
errors.append("postgres_wipe_failed")
|
||||
|
||||
# Drop MariaDB databases
|
||||
try:
|
||||
@@ -5108,7 +5164,8 @@ async def api_security_reset():
|
||||
capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
except Exception as exc:
|
||||
errors.append(f"mariadb wipe: {exc}")
|
||||
logger.warning("MariaDB wipe failed: %s", exc)
|
||||
errors.append("mariadb_wipe_failed")
|
||||
|
||||
# Generate new diceware passwords
|
||||
new_free_password = _generate_diceware_password()
|
||||
@@ -5130,9 +5187,11 @@ async def api_security_reset():
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
errors.append(f"chpasswd free: {(result.stderr or result.stdout).strip()}")
|
||||
logger.warning("chpasswd free failed: %s", (result.stderr or result.stdout).strip())
|
||||
errors.append("chpasswd_free_failed")
|
||||
except Exception as exc:
|
||||
errors.append(f"chpasswd free: {exc}")
|
||||
logger.warning("chpasswd free error: %s", exc)
|
||||
errors.append("chpasswd_free_failed")
|
||||
|
||||
# Set root password
|
||||
try:
|
||||
@@ -5142,11 +5201,14 @@ async def api_security_reset():
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
errors.append(f"chpasswd root: {(result.stderr or result.stdout).strip()}")
|
||||
logger.warning("chpasswd root failed: %s", (result.stderr or result.stdout).strip())
|
||||
errors.append("chpasswd_root_failed")
|
||||
except Exception as exc:
|
||||
errors.append(f"chpasswd root: {exc}")
|
||||
logger.warning("chpasswd root error: %s", exc)
|
||||
errors.append("chpasswd_root_failed")
|
||||
else:
|
||||
errors.append("chpasswd not found; passwords not reset")
|
||||
logger.warning("chpasswd not found; passwords not reset")
|
||||
errors.append("chpasswd_not_found")
|
||||
|
||||
# Write new passwords to secrets files
|
||||
try:
|
||||
@@ -5168,15 +5230,17 @@ async def api_security_reset():
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
errors.append(f"write free-password: {exc}")
|
||||
logger.warning("write free-password error: %s", exc)
|
||||
errors.append("write_free_password_failed")
|
||||
|
||||
try:
|
||||
os.makedirs("/var/lib/secrets", exist_ok=True)
|
||||
with open("/var/lib/secrets/root-password", "w") as f:
|
||||
f.write(new_root_password)
|
||||
f.write(_hash_password(new_root_password))
|
||||
os.chmod("/var/lib/secrets/root-password", 0o600)
|
||||
except Exception as exc:
|
||||
errors.append(f"write root-password: {exc}")
|
||||
logger.warning("write root-password error: %s", exc)
|
||||
errors.append("write_root_password_failed")
|
||||
|
||||
# Clear only the locked keyring databases, leaving the directory and 'default' pointer intact.
|
||||
keyring_dir = "/home/free/.local/share/keyrings"
|
||||
@@ -5185,7 +5249,8 @@ async def api_security_reset():
|
||||
try:
|
||||
os.remove(kf)
|
||||
except OSError as exc:
|
||||
errors.append(f"keyring wipe: {kf}: {exc}")
|
||||
logger.warning("keyring wipe error for %s: %s", kf, exc)
|
||||
errors.append("keyring_wipe_failed")
|
||||
|
||||
# The user performed a full security reset — the banner's purpose is served.
|
||||
try:
|
||||
@@ -5195,7 +5260,7 @@ async def api_security_reset():
|
||||
except OSError:
|
||||
pass # Non-fatal
|
||||
|
||||
return {"ok": True, "new_password": new_free_password, "errors": errors}
|
||||
return {"ok": True, "new_password": new_free_password, "new_root_password": new_root_password, "errors": errors}
|
||||
|
||||
|
||||
@app.post("/api/security/verify-integrity")
|
||||
@@ -5248,7 +5313,8 @@ async def api_security_verify_integrity():
|
||||
except subprocess.TimeoutExpired:
|
||||
store_errors = ["Verification timed out after 5 minutes."]
|
||||
except Exception as exc:
|
||||
store_errors = [str(exc)]
|
||||
logger.warning("Nix store verification failed: %s", exc)
|
||||
store_errors = ["Verification failed unexpectedly."]
|
||||
|
||||
# ── 3. Compare running system to flake build ──────────────────
|
||||
system_matches = False
|
||||
@@ -5275,13 +5341,15 @@ async def api_security_verify_integrity():
|
||||
expected_system_path = "Build succeeded but no result symlink found"
|
||||
else:
|
||||
# Surface the error so the UI can show what went wrong
|
||||
expected_system_path = f"Build failed: {(result.stderr or result.stdout).strip()[:500]}"
|
||||
logger.warning("System verification build failed: %s", (result.stderr or result.stdout).strip()[:500])
|
||||
expected_system_path = "Build failed"
|
||||
finally:
|
||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||
except subprocess.TimeoutExpired:
|
||||
expected_system_path = "Build timed out"
|
||||
except Exception as exc:
|
||||
expected_system_path = str(exc)
|
||||
logger.warning("System verification failed: %s", exc)
|
||||
expected_system_path = "Verification failed"
|
||||
|
||||
return {
|
||||
"flake_commit": flake_commit,
|
||||
@@ -5342,7 +5410,8 @@ async def api_change_password(req: ChangePasswordRequest):
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to update system password: {exc}")
|
||||
logger.error("Failed to update system password: %s", exc)
|
||||
raise HTTPException(status_code=500, detail="Failed to update system password.")
|
||||
|
||||
# Write new password to secrets file so Hub credentials stay in sync
|
||||
try:
|
||||
@@ -5363,7 +5432,8 @@ async def api_change_password(req: ChangePasswordRequest):
|
||||
f.write(_hash_password(req.new_password))
|
||||
os.chmod(FREE_PASSWORD_FILE_WEB, 0o600)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to write secrets file: {exc}")
|
||||
logger.error("Failed to write secrets file: %s", exc)
|
||||
raise HTTPException(status_code=500, detail="Failed to write secrets file.")
|
||||
|
||||
# Clear only the locked keyring databases, leaving the directory and 'default' pointer intact.
|
||||
keyring_dir = "/home/free/.local/share/keyrings"
|
||||
|
||||
@@ -111,7 +111,15 @@ in
|
||||
echo "$ROOT_PASS" > "$SECRET_FILE"
|
||||
chmod 600 "$SECRET_FILE"
|
||||
fi
|
||||
echo "root:$(cat "$SECRET_FILE")" | chpasswd
|
||||
# If the file contains a scrypt hash (salt:hash), skip chpasswd — the
|
||||
# password was already set via the Hub security reset endpoint and this
|
||||
# service is only re-running as a manual recovery step.
|
||||
CONTENT="$(cat "$SECRET_FILE")"
|
||||
if echo "$CONTENT" | grep -qE '^[0-9a-f]{32}:[0-9a-f]{64,}$'; then
|
||||
echo "root-password-setup: stored value is already hashed — skipping chpasswd" >&2
|
||||
else
|
||||
echo "root:$CONTENT" | chpasswd
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
+21
-11
@@ -63,7 +63,12 @@ echo
|
||||
# Helper: Get latest tag
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
get_latest_tag() {
|
||||
git tag --list 'v*' --sort=-version:refname | head -1 || echo ""
|
||||
local exclude="${1:-}"
|
||||
if [[ -n "$exclude" ]]; then
|
||||
git tag --list 'v*' --sort=-version:refname | grep -v -x -E "v?${exclude#v}|${exclude}" | head -1 || echo ""
|
||||
else
|
||||
git tag --list 'v*' --sort=-version:refname | head -1 || echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -88,7 +93,7 @@ suggest_next_version() {
|
||||
# Step 0: Fetch everything
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
echo -e "${BLUE}Step 0: Fetching latest from all remotes...${NC}"
|
||||
git fetch --all --tags --prune --force || git fetch --all --tags --prune --force 2>/dev/null || true
|
||||
git fetch --all --tags 2>/dev/null || true
|
||||
|
||||
LATEST_TAG=$(get_latest_tag)
|
||||
NEXT_VERSION=$(suggest_next_version "$LATEST_TAG")
|
||||
@@ -188,9 +193,10 @@ generate_release_notes() {
|
||||
printf '%s' "$notes"
|
||||
}
|
||||
|
||||
# Build the notes from commits since the previous tag
|
||||
if [[ -n "$LATEST_TAG" ]] && git rev-parse -q --verify "$LATEST_TAG" >/dev/null; then
|
||||
COMMIT_RANGE="${LATEST_TAG}..HEAD"
|
||||
# Build the notes from commits since the previous tag (exclude the target tag if already present)
|
||||
PREV_TAG=$(get_latest_tag "$TAG")
|
||||
if [[ -n "$PREV_TAG" ]] && git rev-parse -q --verify "$PREV_TAG" >/dev/null; then
|
||||
COMMIT_RANGE="${PREV_TAG}..HEAD"
|
||||
else
|
||||
COMMIT_RANGE="HEAD"
|
||||
fi
|
||||
@@ -256,7 +262,7 @@ fi
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
echo
|
||||
echo -e "${BLUE}Step 2: Creating annotated tag ${TAG}...${NC}"
|
||||
git tag -a "${TAG}" -m "${RELEASE_MESSAGE}
|
||||
git tag -f -a "${TAG}" -m "${RELEASE_MESSAGE}
|
||||
|
||||
- Stable release of Sovran_SystemsOS
|
||||
- See CHANGELOG.md for full details" || true
|
||||
@@ -356,17 +362,18 @@ echo
|
||||
echo -e "${BLUE}Step 4: Creating GitHub Release...${NC}"
|
||||
|
||||
if command -v gh &>/dev/null; then
|
||||
title_suffix="${RELEASE_MESSAGE#Sovran_SystemsOS v* — }"
|
||||
title_suffix="${title_suffix#Sovran_SystemsOS * — }"
|
||||
if gh release create "${TAG}" \
|
||||
--repo naturallaw777/Sovran_SystemsOS \
|
||||
--title "${TAG} — ${RELEASE_MESSAGE#Sovran_SystemsOS v* — }" \
|
||||
--notes-file "${NOTES_FILE}" \
|
||||
--target main 2>/dev/null; then
|
||||
--title "${TAG} — ${title_suffix}" \
|
||||
--notes-file "${NOTES_FILE}"; then
|
||||
echo -e " ${GREEN}✓${NC} GitHub release created successfully"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠${NC} GitHub release may already exist or failed"
|
||||
echo -e " ${YELLOW}⚠${NC} GitHub release creation failed (check 'gh auth status' or create via web GUI)"
|
||||
fi
|
||||
else
|
||||
echo -e " ${YELLOW}⚠${NC} gh CLI not found — skipping GitHub release"
|
||||
echo -e " ${YELLOW}⚠${NC} gh CLI not found — skipping GitHub release (create via GitHub web GUI)"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -409,6 +416,9 @@ if [[ -n "${GITEA_TOKEN:-}" ]]; then
|
||||
|
||||
if echo "$RESPONSE" | grep -q '"id"'; then
|
||||
echo -e " ${GREEN}✓${NC} Gitea release created successfully"
|
||||
elif echo "$RESPONSE" | grep -q "write:repository"; then
|
||||
echo -e " ${YELLOW}⚠${NC} Gitea token scope issue: your token requires the 'write:repository' scope (currently has write:package)."
|
||||
echo " To fix: In Gitea, navigate to Settings → Applications → Manage Access Tokens and generate a token with 'write:repository'."
|
||||
else
|
||||
echo -e " ${YELLOW}⚠${NC} Gitea release creation failed or already exists"
|
||||
echo " Response: $RESPONSE"
|
||||
|
||||
Reference in new issue
Block a user