Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Hass-Cleaner is a Home Assistant App for inspecting storage, stale entities and registry relationships before anything is cleaned up. Safety, evidence and recovery come before reclaimed space.

> **Release status:** version 1.0.0 is the first stable release and is available for controlled early testing. Hass-Cleaner provides facts, advice, backup options and recovery guidance; the user makes the final cleanup decision.
> **Release status:** version 1.0.1 is the current maintenance release for controlled early testing. Hass-Cleaner provides facts, advice, backup options and recovery guidance; the user makes the final cleanup decision.

> [!WARNING]
> **Testversie — voorzichtig gebruiken / Test version — handle with care.** Hass-Cleaner kan bestanden, Home Assistant-registerobjecten en Recorder-gegevens wijzigen. Controleer iedere selectie en maak vooraf bij voorkeur een volledige Home Assistant-back-up. Test bij voorkeur eerst op een niet-kritische installatie. Gebruik is op eigen risico; quarantaine en herstelvoorzieningen verkleinen het risico, maar kunnen niet garanderen dat iedere integratie of gebruikersconfiguratie zonder gevolgen blijft werken.
Expand Down Expand Up @@ -32,7 +32,7 @@ Een gevonden item is nooit automatisch verwijderbewijs. De app toont waarom iets
- Wisbare lokale scan- en afgeronde quarantainelogboeken voor een schone start.
- Hersteltest en terugplaatsen zonder bestaande bestanden te overschrijven.

### Veiligheidsmodel van 1.0.0
### Veiligheidsmodel van 1.0.x

Een bestand kan alleen naar quarantaine als aan alle voorwaarden wordt voldaan:

Expand Down Expand Up @@ -63,7 +63,7 @@ Persoonlijke of onzekere inhoud vereist een extra inhoudsbevestiging. Registerop
4. Start de app en open de webinterface.
5. Voer eerst alleen een scan uit en beoordeel het rapport.

GitHub Actions bouwt versie 1.0.0 voor `amd64` en `aarch64`. Na publicatie van de container kan Home Assistant de release via deze repository installeren of bijwerken.
GitHub Actions bouwt versie 1.0.1 voor `amd64` en `aarch64`. Na publicatie van de container kan Home Assistant de release via deze repository installeren of bijwerken.

### Lokaal ontwikkelen en testen

Expand Down Expand Up @@ -109,7 +109,7 @@ A finding is never treated as deletion evidence by itself. The App explains why
- Clearable local scan and completed-quarantine logs for a clean start.
- Restore testing and recovery without overwriting existing files.

### 1.0.0 safety model
### 1.0.x safety model

A file can enter quarantine only when all conditions are satisfied:

Expand Down Expand Up @@ -140,7 +140,7 @@ Personal or uncertain content requires an additional content acknowledgement. Re
4. Start the App and open its web interface.
5. Run a scan first and review the report before preparing an action.

GitHub Actions builds version 1.0.0 for `amd64` and `aarch64`. After the container is published, Home Assistant can install or update the release through this repository.
GitHub Actions builds version 1.0.1 for `amd64` and `aarch64`. After the container is published, Home Assistant can install or update the release through this repository.

### Local development and tests

Expand Down
6 changes: 4 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ Veiligheid gaat vóór extra opruimwinst. Een release gaat pas door wanneer risi
| 0.9.0 | Afgerond | Supervisor-back-upverificatie, hersteltest, bewaartermijn en expliciete verwijdering na verval. |
| 0.9.1 | Afgerond | Toegestane back-upcontrole, hergebruik van back-upbewijs, bewuste optionele back-upkeuze en selecteerbare entitybeoordeling. |
| 1.0.0 | Uitgebracht | Gebruikersgestuurde cleanup, registeruitvoering, persoonlijke inhoud, schone start en behoud van harde systeembescherming. |
| 1.0.1 | Voorbereid | Technische hardening: crashbestendige uitvoering, paginering, atomaire opslag, toegankelijkheid en betrouwbaar frontendcachebeleid. |

### Praktijkcontrole voor vroege testers

- 1.0.0 installeren of bijwerken via de echte GitHub/GHCR-workflow.
- 1.0.1 installeren of bijwerken via de echte GitHub/GHCR-workflow.
- Volledige back-up starten en voltooiing op Home Assistant OS verifiëren.
- Zowel een veilige als een bewust gekozen reviewkandidaat naar quarantaine verplaatsen.
- Hersteltest uitvoeren en hetzelfde bestand werkelijk terugplaatsen.
Expand Down Expand Up @@ -67,10 +68,11 @@ Safety takes priority over reclaimed space. A release proceeds only when risky s
| 0.9.0 | Completed | Supervisor backup verification, restore testing, retention and explicit post-expiry deletion. |
| 0.9.1 | Completed | Permitted backup verification, reusable evidence, an explicit optional backup choice and selectable entity reviews. |
| 1.0.0 | Released | User-directed cleanup, registry execution, personal content, clean-start controls and hard core-system protection. |
| 1.0.1 | Prepared | Technical hardening: crash-safe execution, pagination, atomic storage, accessibility and reliable frontend caching. |

### Practical checks for early testers

- Install or update 1.0.0 through the real GitHub/GHCR workflow.
- Install or update 1.0.1 through the real GitHub/GHCR workflow.
- Start a full backup and verify completion on Home Assistant OS.
- Move both a safe file and an explicitly accepted review candidate into quarantine.
- Run the restore test and restore that file to its original location.
Expand Down
10 changes: 10 additions & 0 deletions hass_cleaner/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 1.0.1 — 2026-08-26

- Quarantaine en registeropschoning crashbestendiger en volledig geaudit gemaakt.
- Oude selecties bij een nieuwe scan gewist en alleen quarantaine als bestandsroute behouden.
- Grote scans laden bestanden, entities en bundels nu per pagina.
- Lokale instellingen, metingen en keuzes atomisch opgeslagen met zichtbare fouten.
- Scanthreads eindigen altijd met een definitieve status.
- Engelse dynamische teksten, toetsenbordtabs en benoemde dialogen verbeterd.
- Versiegebonden frontendassets en expliciete cacheheaders toegevoegd.

## 1.0.0 — 2026-08-14

- Eerste stabiele release voor gecontroleerde vroege tests.
Expand Down
2 changes: 1 addition & 1 deletion hass_cleaner/DOCS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Hass-Cleaner

Versie 1.0.0 toont geregistreerde entities en runtime-only states apart. Status en meetduur zijn advies en filters, geen verwijdertoestemming. De gebruiker kan geregistreerde entities selecteren en na risico- en back-upkeuze via de officiële Home Assistant-API verwijderen. Runtime-only states hebben geen registeritem en blijven uitgesloten.
Versie 1.0.1 toont geregistreerde entities en runtime-only states apart. Status en meetduur zijn advies en filters, geen verwijdertoestemming. De gebruiker kan geregistreerde entities selecteren en na risico- en back-upkeuze via de officiële Home Assistant-API verwijderen. Runtime-only states hebben geen registeritem en blijven uitgesloten.

Deze versie inventariseert veilig bestanden en Home Assistant-registers en kan afzonderlijk de officiële Recorder-purgeactie uitvoeren.

Expand Down
4 changes: 1 addition & 3 deletions hass_cleaner/config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Hass-Cleaner
version: "1.0.0"
version: "1.0.1"
slug: hass_cleaner
description: Veilige audit en gecontroleerde opschoning voor Home Assistant OS
url: https://github.com/dkwolf1/Hass-Cleaner
Expand All @@ -19,15 +19,13 @@ map:
options:
min_temp_age_days: 30
min_log_age_days: 14
deletion_mode: quarantine
retention_days: 7
advanced_mode: false
report_retention_count: 10
language: auto
schema:
min_temp_age_days: int(1,365)
min_log_age_days: int(1,365)
deletion_mode: list(permanent|quarantine)
retention_days: int(1,10)
advanced_mode: bool
report_retention_count: int(1,50)
Expand Down
2 changes: 1 addition & 1 deletion hass_cleaner/hass_cleaner/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Hass-Cleaner Home Assistant App."""

__version__ = "1.0.0"
__version__ = "1.0.1"
60 changes: 32 additions & 28 deletions hass_cleaner/hass_cleaner/availability.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from .registry_audit import RegistryAudit
from .storage import StorageError, atomic_write_json, json_file_lock, read_json_object


LONG_UNAVAILABLE_DAYS = 30
Expand Down Expand Up @@ -33,12 +33,23 @@ def apply_availability_history(audit: RegistryAudit, path: Path, *, now: datetim
if observation is not None:
observations[str(entity.get("entity_id", ""))] = observation

_save(path, observations)
persistence_errors: list[str] = []
try:
_save(path, observations)
except StorageError as exc:
persistence_errors.append(str(exc))
print(f"Hass-Cleaner opslagwaarschuwing: {exc}", flush=True)
_append_bundle_anomalies(audit)
audit.entity_workspace = _build_entity_workspace(audit)
_apply_decisions(audit.entity_workspace, decisions, current)
_apply_scan_diff(audit.entity_workspace, previous_snapshot)
_save(snapshot_path, _workspace_snapshot(audit.entity_workspace))
try:
_save(snapshot_path, _workspace_snapshot(audit.entity_workspace))
except StorageError as exc:
persistence_errors.append(str(exc))
print(f"Hass-Cleaner opslagwaarschuwing: {exc}", flush=True)
if persistence_errors:
audit.entity_workspace["persistence_errors"] = persistence_errors


def _annotate_entity(
Expand Down Expand Up @@ -379,22 +390,23 @@ def update_entity_decision(
raise ValueError("Ongeldige entitykeuze")
if not entity_id or len(entity_id) > 255:
raise ValueError("Ongeldige entity-id")
decisions = _load(path)
if action == "clear":
decisions.pop(entity_id, None)
with json_file_lock(path):
decisions = _load(path)
if action == "clear":
decisions.pop(entity_id, None)
_save(path, decisions)
return {"entity_id": entity_id, "action": "clear"}
current = now or datetime.now(timezone.utc)
days = {"snooze_7": 7, "snooze_30": 30, "snooze_90": 90}.get(action)
until = ""
if days:
from datetime import timedelta

until = (current + timedelta(days=days)).isoformat()
record = {"action": action, "set_at": current.isoformat(), "until": until}
decisions[entity_id] = record
_save(path, decisions)
return {"entity_id": entity_id, "action": "clear"}
current = now or datetime.now(timezone.utc)
days = {"snooze_7": 7, "snooze_30": 30, "snooze_90": 90}.get(action)
until = ""
if days:
from datetime import timedelta

until = (current + timedelta(days=days)).isoformat()
record = {"action": action, "set_at": current.isoformat(), "until": until}
decisions[entity_id] = record
_save(path, decisions)
return {"entity_id": entity_id, **record}
return {"entity_id": entity_id, **record}


def apply_saved_decisions(workspace: dict[str, Any], path: Path, *, now: datetime | None = None) -> None:
Expand Down Expand Up @@ -489,19 +501,11 @@ def _entity_reason(status: str, signal_problem: bool) -> str:


def _load(path: Path) -> dict[str, dict[str, Any]]:
try:
value = json.loads(path.read_text(encoding="utf-8"))
return value if isinstance(value, dict) else {}
except (OSError, json.JSONDecodeError):
return {}
return read_json_object(path)


def _save(path: Path, observations: dict[str, dict[str, Any]]) -> None:
try:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(observations, ensure_ascii=False, indent=2), encoding="utf-8")
except OSError:
pass
atomic_write_json(path, observations)


def _parse(value: str) -> datetime | None:
Expand Down
11 changes: 5 additions & 6 deletions hass_cleaner/hass_cleaner/plans.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,20 @@ def create(
files = []
for item_id in selected_ids:
item = file_map[item_id]
requested_action = settings.deletion_mode
files.append(
{
"id": item.id,
"path": item.path,
"before": {"exists": True, "size_bytes": item.size_bytes, "category": item.category, "sha256": item.sha256},
"proposed_action": requested_action,
"proposed_action": "quarantine",
"after": {
"source_exists": False,
"quarantine_copy": settings.deletion_mode == "quarantine",
"retention_days": settings.retention_days if settings.deletion_mode == "quarantine" else 0,
"quarantine_copy": True,
"retention_days": settings.retention_days,
},
"advice": item.advice,
"risk": item.risk,
"execution_allowed": settings.deletion_mode == "quarantine",
"execution_allowed": True,
}
)
bundles = []
Expand Down Expand Up @@ -146,7 +145,7 @@ def create(
"entity_count": len(entities),
"device_count": len(devices),
"planned_bytes": sum(item["before"]["size_bytes"] for item in files),
"executable_actions": (len(files) if settings.deletion_mode == "quarantine" else 0) + len(entities) + len(devices),
"executable_actions": len(files) + len(entities) + len(devices),
},
"global_recovery": [
"Annuleer bij twijfel; dit plan voert zelf niets uit.",
Expand Down
Loading