From b5702e423aab50fa4cf96ec849473f7beac5f57d Mon Sep 17 00:00:00 2001 From: Benjamin Diedrichsen Date: Tue, 1 Sep 2026 12:48:55 +0200 Subject: [PATCH] [fix] cubes: service:autostart reads its data, and its README describes it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes DOCS-AUDIT §6.1 and §5.1. **The script could not run.** It read `APP` off `host.data` and then used `SERVICE_NAME` and `AUTOSTART` as if they were in scope, so the very first statement — `if AUTOSTART:` — raised `NameError`; `server` was used in the else branch but never imported. Three lines: import `server` alongside `systemd`, read the two names next to `APP`. The logic underneath was always right. `python3 -m py_compile` passes. **The README documented a different cube.** It was titled "TypeStack Install Cube" and described cloning a git repository, `yarn install`, `yarn build`, `docker compose up -d` and PM2 — none of which this cube does, and it listed parameters (`USER`, `REPO`, `ENV`, `NODE_PATH`) the manifest does not have, carrying someone's private repository URL and username as defaults. Rewritten from the manifest and the now-working script: the three parameters that exist, and the thing the old text obscured by describing a deploy pipeline — this cube does not create the unit file, it enables and starts one that is already installed. `SERVICE_NAME` is documented as what it is, a label that never reaches systemd, so getting it wrong is cosmetic rather than a cube managing the wrong unit. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01DCzYTAm9QUhvLNr2EpdagJ --- .../cubes/service/autostart/README.md | 205 ++++-------------- .../cubes/service/autostart/deploy.py | 6 +- 2 files changed, 49 insertions(+), 162 deletions(-) diff --git a/packages/nopy-cubes-core/cubes/service/autostart/README.md b/packages/nopy-cubes-core/cubes/service/autostart/README.md index 3791986..caae137 100644 --- a/packages/nopy-cubes-core/cubes/service/autostart/README.md +++ b/packages/nopy-cubes-core/cubes/service/autostart/README.md @@ -1,176 +1,61 @@ -# TypeStack Install Cube +# autostart -Deploys a Node.js/TypeScript application from a Git repository as a systemd service with Docker Compose and PM2 support. +**Enable and start an existing systemd service** -## Features +## Purpose -- Clones Git repository -- Installs dependencies with Yarn -- Builds the application -- Starts Docker Compose services -- Creates a systemd service for automatic startup -- Configures PM2 for process management -- Automatic restart on failure +Takes a systemd unit that is already installed on the host and decides whether +it runs: `systemctl enable` plus `systemctl start`, or neither. -## Requirements - -- Git (for cloning repository) -- Yarn (for dependency management) -- Docker and Docker Compose -- PM2 (for process management) -- Node.js/NVM installed -- SSH key access to the repository (if using private repos) - -## Configuration Parameters - -### Required - -- **USER**: System user to run the application (default: `teclabmin`) -- **REPO**: Git repository URL (default: `git@github.com:bennidi/teclab-flintstone.git`) -- **APP**: Application name/directory name (default: `flintstone`) - -### Optional - -- **ENV**: Application environment (default: `production`) -- **AUTOSTART**: Enable and start service immediately (default: `True`) -- **NODE_PATH**: Path to Node.js binaries (default: `/home/teclabmin/.nvm/versions/node/v21.7.3/bin`) - -## Example Usage - -### Basic Configuration - -```json -{ - "USER": "myuser", - "REPO": "git@github.com:myorg/myapp.git", - "APP": "myapp" -} -``` - -### Advanced Configuration - -```json -{ - "USER": "appuser", - "REPO": "git@github.com:myorg/myapp.git", - "APP": "myapp", - "ENV": "staging", - "AUTOSTART": false, - "NODE_PATH": "/home/appuser/.nvm/versions/node/v20.0.0/bin" -} -``` +It does **not** create the unit. Something else — a package, another cube, a +`files.template` — has to have put `.service` on the host first. This cube +is the switch, not the wiring. ## What This Cube Does -1. **Clone Repository**: Clones the specified Git repository to `/home//` -2. **Install Dependencies**: Runs `yarn install` to install all dependencies -3. **Build Application**: Runs `yarn build` to compile the application -4. **Start Docker Services**: Runs `docker compose up -d` to start containerized services -5. **Create Startup Script**: Creates `/home//.service.sh` that: - - Starts Docker Compose services - - Starts PM2 with ecosystem.config.js -6. **Create Systemd Service**: Creates `/etc/systemd/system/.service` that: - - Runs after Docker service - - Uses the specified user - - Configures proper environment (HOME, PATH) - - Auto-restarts on failure -7. **Enable & Start Service**: Enables and starts the service (if AUTOSTART=True) +With `AUTOSTART=True` (the default), two `systemd.service` operations against +``: one setting `enabled=True` so the unit comes up on boot, one setting +`running=True` so it comes up now. Both are idempotent — a unit already enabled +and running is left alone. -## Service Management +With `AUTOSTART=False`, nothing is changed. The cube prints the two commands you +would run by hand and exits, which is the point of the flag: install now, decide +later. -### Check service status +## Configuration + +| Variable | Default | What it does | +| --- | --- | --- | +| `APP` | *(required)* | the systemd unit name, without the `.service` suffix — `flintstone` for `/etc/systemd/system/flintstone.service`. This is what `systemctl` is actually pointed at. | +| `SERVICE_NAME` | `Application` | a display name, used only in the operation labels pyinfra prints and in the `AUTOSTART=False` message. Changing it changes what you read, not what happens. | +| `AUTOSTART` | `true` | whether to enable and start the unit at all. | + +`APP` has no default, so `nopy -D` (`--use-defaults`) fails by name rather than +guessing. Supply it under `env` in `.nopyrc.json`, from a dependency, or at the +prompt. + +## Dependencies + +None declared, and none implied beyond the unit file itself. `systemd.service` +is a pyinfra built-in; there is nothing to install. + +## Post-Installation ```bash -sudo systemctl status +systemctl status # is it running? +systemctl is-enabled # will it come back after a reboot? +journalctl -u -f # follow its log ``` -### Start the service - -```bash -sudo systemctl start -``` - -### Stop the service - -```bash -sudo systemctl stop -``` - -### Restart the service - -```bash -sudo systemctl restart -``` - -### View service logs - -```bash -sudo journalctl -u -f -``` - -### Disable autostart - -```bash -sudo systemctl disable -``` - -## File Structure - -After deployment: - -``` -/home// -├── / # Application directory -│ ├── ecosystem.config.js # PM2 configuration -│ ├── docker-compose.yml # Docker services -│ └── ... # Application files -├── .service.sh # Startup script -/etc/systemd/system/ -└── .service # Systemd service file -``` - -## Troubleshooting - -### Service fails to start - -1. Check service logs: - ```bash - sudo journalctl -u -n 50 - ``` - -2. Verify Docker is running: - ```bash - sudo systemctl status docker - ``` - -3. Check if Node.js path is correct: - ```bash - which node - which pm2 - ``` - -### Repository clone fails - -- Ensure SSH keys are properly configured for the user -- Test SSH access: `ssh -T git@github.com` -- Check repository URL is correct - -### Docker Compose fails - -- Verify Docker is installed and running -- Check docker-compose.yml exists in the application directory -- Ensure user has Docker permissions: `sudo usermod -aG docker ` - -### PM2 not starting - -- Verify PM2 is installed: `pm2 --version` -- Check ecosystem.config.js exists -- Verify NODE_PATH includes PM2 binary location +If the run fails with *Unit `.service` could not be found*, the unit was +never installed — see Purpose. `systemctl daemon-reload` is worth trying if the +file was written after systemd last read the directory. ## Notes -- The service type is set to `forking` to support PM2's daemon mode -- Service will auto-restart on failure with a 5-second delay -- Maximum 5 restart attempts in the burst period -- The service waits for Docker to be ready before starting -- Environment variables can be configured in the ecosystem.config.js file +- Enabling and starting are separate systemd concepts and this cube always does + both or neither. If you need one without the other, call `systemd.service` + from your own deploy script. +- `SERVICE_NAME` is deliberately not passed to systemd. The unit is identified + by `APP` alone, so a wrong `SERVICE_NAME` is a cosmetic mistake rather than a + cube that manages the wrong service. diff --git a/packages/nopy-cubes-core/cubes/service/autostart/deploy.py b/packages/nopy-cubes-core/cubes/service/autostart/deploy.py index e6f7f32..3c17732 100644 --- a/packages/nopy-cubes-core/cubes/service/autostart/deploy.py +++ b/packages/nopy-cubes-core/cubes/service/autostart/deploy.py @@ -1,8 +1,10 @@ -from pyinfra.operations import systemd +from pyinfra.operations import server, systemd from pyinfra import host APP = host.data.APP +SERVICE_NAME = host.data.SERVICE_NAME +AUTOSTART = host.data.AUTOSTART # Enable and start the service based on AUTOSTART flag if AUTOSTART: @@ -24,4 +26,4 @@ else: name=f'Service {SERVICE_NAME} created but not enabled (AUTOSTART=False)', commands=[f'echo "Service {SERVICE_NAME} is ready but not started. Enable with: sudo systemctl enable {APP} && sudo systemctl start {APP}"'], _sudo=False - ) \ No newline at end of file + )