From 646ba3be813778c97c514c12395337acb673fb1d Mon Sep 17 00:00:00 2001 From: Franzz Date: Sun, 1 Nov 2026 23:51:54 +0100 Subject: [PATCH] Auto create data folder --- .gitea/workflows/deploy.yml | 5 +++++ README.md | 23 +++++++++-------------- tests/test_deploy.py | 14 +++++++++++++- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index b31b72f..2e228b1 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -86,6 +86,11 @@ jobs: fi "$DEPLOY_PATH/.venv/bin/python" -c 'import sys; assert sys.version_info >= (3, 11)' "$DEPLOY_PATH/.venv/bin/python" -m pip install --only-binary=pyarrow -r "$DEPLOY_PATH/requirements.txt" + # ReadWritePaths must exist before systemd sets up the service's mount namespace. + ( + umask 0077 + mkdir -p "$DEPLOY_PATH/data/archive" + ) sudo -n "$(command -v systemctl)" restart "$SERVICE" # Catch an invalid config or missing token that triggers an immediate restart loop. sleep 10 diff --git a/README.md b/README.md index 1bec983..77847e8 100644 --- a/README.md +++ b/README.md @@ -207,26 +207,20 @@ poll_interval_seconds = 300 retention_days = 30 ``` -The service uses the existing `www-data` account. If replacing an existing service, -stop it before changing data ownership: +The service uses the existing `www-data` account. Deployment creates `data/archive` +as the `www-data` runner before restarting the service. For a manual installation, +create the directory as that account before starting the service: ```bash -sudo systemctl stop solar-relay -``` - -Create the writable data directory once. The ownership change includes -any existing archive files and leaves their contents intact: - -```bash -sudo install -d -o www-data -g www-data -m 0700 \ +sudo -u www-data install -d -m 0700 \ /var/www/html/solar-relay/data /var/www/html/solar-relay/data/archive -sudo chown -R www-data:www-data /var/www/html/solar-relay/data ``` The unit keeps the checkout read-only with `ProtectSystem=strict` and allows writes to `/var/www/html/solar-relay/data` through `ReadWritePaths`. The data directory must -exist before starting the service. Do not run archive-writing CLI commands as another -user; use `sudo -u www-data` to preserve ownership. +exist before starting the service; otherwise systemd exits with `226/NAMESPACE` +before Python can create it. Existing data must be writable by `www-data`. Use +`sudo -u www-data` for archive-writing CLI commands to preserve ownership. Set `[upload].url` to the main website's `https://your-solar-host/index.php?a=relay`. Put `SOLAR_RELAY_TOKEN=your-shared-token` in `/etc/solar-relay/solar-relay.env` with @@ -304,7 +298,8 @@ www-data ALL=(root) NOPASSWD: /usr/bin/systemctl restart solar-relay The workflow installs dependencies and runs tests in `.venv-ci`, checks the installed unit, serializes deployment with `flock`, syncs source, installs runtime dependencies -inside `.venv`, restarts and checks the service, then probes the public HTTPS health +inside `.venv`, creates the archive directory if needed, restarts and checks the +service, then probes the public HTTPS health endpoint with certificate verification. Runtime venv/configuration, `data/` and Parquet files are preserved. The archive under `data/archive` is protected by the `/data/` rsync exclusion, and the credential environment file under `/etc` is outside diff --git a/tests/test_deploy.py b/tests/test_deploy.py index 1f0688c..d152a95 100644 --- a/tests/test_deploy.py +++ b/tests/test_deploy.py @@ -14,6 +14,12 @@ import unittest @unittest.skipUnless(shutil.which("rsync") and shutil.which("flock"), "deployment tools are not installed") class DeploymentTests(unittest.TestCase): def test_deploy_preserves_runtime_and_archive_and_restarts_service(self): + self._run_deployment(archive_exists=True) + + def test_deploy_creates_archive_before_restarting_service(self): + self._run_deployment(archive_exists=False) + + def _run_deployment(self, *, archive_exists): workflow = (Path(__file__).resolve().parents[1] / ".gitea/workflows/deploy.yml").read_text() deploy_step = workflow.split("- name: Deploy and restart\n", 1)[1] script = textwrap.dedent(deploy_step.split("run: |\n", 1)[1]) @@ -34,6 +40,8 @@ class DeploymentTests(unittest.TestCase): ".venv/keep.txt": "runtime venv", ".git/keep.txt": "existing checkout metadata", } + if not archive_exists: + del protected["data/archive/date=2026-11-01/samples.parquet"] for relative, content in protected.items(): target = deployment / relative target.parent.mkdir(parents=True, exist_ok=True) @@ -47,7 +55,7 @@ class DeploymentTests(unittest.TestCase): runtime.chmod(0o755) mocks = { "sudo": '#!/bin/sh\n[ "$1" = "-n" ] || exit 99\nshift\nexec "$@"\n', - "systemctl": '#!/bin/sh\nprintf "%s\\n" "$*" >> "$SOLAR_DEPLOY_TEST_LOG"\nif [ "$1" = show ]; then printf "0\\n"; fi\n', + "systemctl": '#!/bin/sh\nif [ "$1" = restart ] && [ ! -d "$DEPLOY_PATH/data/archive" ]; then echo "Archive directory is missing before service restart" >&2; exit 1; fi\nprintf "%s\\n" "$*" >> "$SOLAR_DEPLOY_TEST_LOG"\nif [ "$1" = show ]; then printf "0\\n"; fi\n', "sleep": "#!/bin/sh\nexit 0\n", "curl": '#!/bin/sh\nprintf "curl %s\\n" "$*" >> "$SOLAR_DEPLOY_TEST_LOG"\n', } @@ -61,6 +69,10 @@ class DeploymentTests(unittest.TestCase): "HEALTH_URL": "https://rpioffice.nest.lutran.ch/solar-relay/healthz"} result = subprocess.run(["bash", "-c", script], cwd=checkout, env=env, capture_output=True, text=True, timeout=15) self.assertEqual(result.returncode, 0, result.stderr) + self.assertTrue((deployment / "data/archive").is_dir()) + if not archive_exists: + for relative in ("data", "data/archive"): + self.assertEqual((deployment / relative).stat().st_mode & 0o777, 0o700) self.assertTrue((deployment / "solar_relay/new.py").exists()) self.assertFalse((deployment / "obsolete.py").exists()) for relative, content in protected.items():