bigpowers 2.76.2 → 2.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.pi/package.json +2 -2
  2. package/.pi/prompts/build-epic.md +10 -0
  3. package/.pi/prompts/commit-message.md +1 -0
  4. package/.pi/prompts/generate-allure-report.md +284 -0
  5. package/.pi/prompts/harden-vps.md +287 -0
  6. package/.pi/prompts/plan-release.md +3 -8
  7. package/.pi/prompts/plan-work.md +14 -1
  8. package/.pi/prompts/release-branch.md +3 -0
  9. package/.pi/skills/build-epic/SKILL.md +10 -0
  10. package/.pi/skills/commit-message/SKILL.md +1 -0
  11. package/.pi/skills/generate-allure-report/SKILL.md +285 -0
  12. package/.pi/skills/harden-vps/SKILL.md +288 -0
  13. package/.pi/skills/plan-release/SKILL.md +3 -8
  14. package/.pi/skills/plan-work/SKILL.md +14 -1
  15. package/.pi/skills/release-branch/SKILL.md +3 -0
  16. package/CHANGELOG.md +15 -0
  17. package/README.md +1 -1
  18. package/SKILL-INDEX.md +1 -1
  19. package/allure-results/categories.json +23 -0
  20. package/allure-results/executor.json +6 -0
  21. package/allure-results/junit-results.xml +2743 -0
  22. package/hooks/pre-tool-use.sh +5 -0
  23. package/package.json +1 -1
  24. package/scripts/generate-allure-report.sh +146 -0
  25. package/scripts/land-branch.sh +10 -0
  26. package/scripts/record-cycle-time.sh +28 -0
  27. package/scripts/sync-status-from-epics.sh +247 -24
  28. package/scripts/validate-skill-catalog.sh +1 -1
  29. package/skills/build-epic/SKILL.md +10 -0
  30. package/skills/commit-message/SKILL.md +1 -0
  31. package/skills/generate-allure-report/REFERENCE.md +243 -0
  32. package/skills/generate-allure-report/SKILL.md +40 -0
  33. package/skills/harden-vps/REFERENCE.md +179 -0
  34. package/skills/harden-vps/SKILL.md +107 -0
  35. package/skills/plan-release/SKILL.md +3 -8
  36. package/skills/plan-work/REFERENCE.md +13 -0
  37. package/skills/plan-work/SKILL.md +1 -1
  38. package/skills/release-branch/SKILL.md +3 -0
  39. package/skills-lock.json +15 -5
@@ -44,7 +44,7 @@ If this plan touches an existing module, run `assess-impact` first to understand
44
44
 
45
45
  2. **Draft steps** — Break implementation into the smallest possible steps where each step leaves the codebase working, has one observable outcome, and can be verified with a single command. Red-flag check: name any rationalization you caught before moving to step 3.
46
46
 
47
- 3. **Write capsule story spec + tasks** — Output two files inside the active epic capsule. See [REFERENCE.md](REFERENCE.md) for file formats and the plan-template. Each task MUST include a `risk:` field (`P0` | `P1` | `P2` | `P3`) based on BCP + story type heuristics (see REFERENCE.md). If a test plan artifact (`specs/tech-architecture/eNN-TEST_PLAN_LATEST.md`) exists, read it and inherit its P0/P1 risk classifications and scenario IDs (`SC-eNNsYY-P0-NN`). Each task optionally includes a `security:` field (`none` / `low` / `medium` / `high`) sourced from the epic's `specs/security/epics/<id>/THREAT_MODEL.md`. Tasks with `security: medium` or `security: high` MUST include "no new security findings in affected paths" in their verify steps.
47
+ 3. **Write capsule story spec + tasks** — Output two files inside the active epic capsule. See [REFERENCE.md](REFERENCE.md) for file formats and the plan-template. Each task MUST include a `risk:` field (`P0` | `P1` | `P2` | `P3`) based on BCP + story type heuristics (see REFERENCE.md). If a test plan artifact (`specs/tech-architecture/eNN-TEST_PLAN_LATEST.md`) exists, read it and inherit its P0/P1 risk classifications and scenario IDs (`SC-eNNsYY-P0-NN`). Each task optionally includes a `security:` field (`none` / `low` / `medium` / `high`) sourced from the epic's `specs/security/epics/<id>/THREAT_MODEL.md`. Tasks with `security: medium` or `security: high` MUST include "no new security findings in affected paths" in their verify steps. Each task should also include an `allure:` block to drive test reporting: `severity:` maps risk to Allure severity (`P0→critical`, `P1→high`, `P2→normal`, `P3→minor`), and `categories:` is a list of relevant tags like `"Security Review"`, wave names, or test categories (see REFERENCE.md for the full template).
48
48
 
49
49
  **Requirement delta tags (e45s29):** When a story modifies existing behavior, the story spec § Requirements MUST use OpenSpec-style delta tags with mandatory before/after content:
50
50
 
@@ -132,8 +132,21 @@ tasks:
132
132
  verify: "npm test -- login-form.test.tsx"
133
133
  risk: P1
134
134
  status: failing # flip to passing only after verify exits 0 (e45s06)
135
+ allure:
136
+ severity: high # P0→critical, P1→high, P2→normal, P3→minor
137
+ categories:
138
+ - "Auth"
139
+ - "Security Review"
135
140
  ```
136
141
 
142
+ **Allure severity mapping:**
143
+ - `P0` → `critical`
144
+ - `P1` → `high`
145
+ - `P2` → `normal`
146
+ - `P3` → `minor`
147
+
148
+ `categories` is a list of relevant tags — wave names, test categories (e.g. `"unit"`, `"integration"`), or thematic groupings (e.g. `"Security Review"`).
149
+
137
150
  Update `specs/epics/<capsule>/epic.yaml` manifest to list the story and its BCPs. Run `bash scripts/sync-status-from-epics.sh` after structural changes.
138
151
 
139
152
  ## Plan template
@@ -40,9 +40,12 @@ If unsure, prefer **solo-local**.
40
40
  ```bash
41
41
  <full test command> && <typecheck command> && <lint command>
42
42
  git log main...HEAD --oneline | grep -vE "^[a-f0-9]+ (feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+$" && echo "❌ Non-conventional commits found" || echo "✅ Commits verified"
43
+ # Block AI agent attribution (P1)
44
+ git log main...HEAD --format="%B" | grep -qiE 'co[- ]authored[- ]by' && echo "❌ Co-authored-by footer found — blocked" || echo "✅ No AI attribution"
43
45
  ```
44
46
 
45
47
  - [ ] All tests pass, no type errors, no lint violations, all commits follow Conventional Commits
48
+ - [ ] **NO `Co-authored-by` or `Co-Authored-By`** in any commit body — P1 rule (CONVENTIONS.md § Git Attribution). `land-branch.sh` blocks the merge if found.
46
49
 
47
50
  ### 2. Coverage check
48
51
 
@@ -33,6 +33,7 @@ Orchestrates the **build** flow for a single epic: survey → plan tasks → kic
33
33
  ## Process
34
34
 
35
35
  1. Read `specs/state.yaml`, `specs/execution-status.yaml`, `specs/release-plan.yaml`, active `specs/epics/eNN-slug/epic.yaml`.
36
+ - **On story start (step 1):** Write `started_at` ISO 8601 timestamp to `execution-status.yaml` under the story's key in the `stories:` section. The `bp-timing.sh` script already tracks skill-level timings — this is story-level.
36
37
  2. **Step 0 — Threat Model:** Run `security-review` against the epic's scope (read from the epic capsule). Output `specs/security/epics/<epic-id>/THREAT_MODEL.md` with surface area, vulnerability categories, risk level, and mitigation guidance.
37
38
  3. **Assess Impact (Step 2):** Before writing tasks, run `assess-impact --lightweight` on the proposed change. If the risk score exceeds 7, gate — require a `grill-me` session. Write the impact report to `specs/IMPACT-<epic>-<story>.md`. For net-new code with no existing dependents, skip.
38
39
  4. **BCP Tracking (Step 2):** After `plan-work` completes, read the `bcps:` count (Business Complexity Points story size) from the epic capsule and carry it into `state.yaml` as `epic_cycle.story_bcps = N`.
@@ -41,6 +42,15 @@ Orchestrates the **build** flow for a single epic: survey → plan tasks → kic
41
42
  6. Run **only the current step** (resume mode) unless user asked for full auto-run.
42
43
  7. After step verify passes, increment `epic_cycle.step` in `state.yaml` (or `bash scripts/bp-yaml-set.sh` if available).
43
44
  8. On story complete, set `execution-status.yaml` story key to `done`; run `bash scripts/sync-status-from-epics.sh`.
45
+ - **On story complete (step 8):** Write `completed_at` ISO 8601 timestamp to `execution-status.yaml` under the same story key. Format:
46
+
47
+ ```yaml
48
+ stories:
49
+ e01s01:
50
+ status: done
51
+ started_at: "2026-07-12T16:00:00-03:00"
52
+ completed_at: "2026-07-12T18:45:00-03:00"
53
+ ```
44
54
  9. **Traceability refresh:** Before step 8 (release-branch), run `bash scripts/trace-stories.sh` to regenerate `specs/traceability-matrix.json`, `specs/TRACEABILITY_LATEST.md`, and the `specs/codebase-wiki/` OKF bundle. Surface dark/orphan/stale findings for the just-built epic in your verify summary. If `scripts/trace-stories.sh` is missing or fails, note "trace skipped" and continue — trace failure must be visible, not silent (blocking is gate-trace's job, e38s06).
45
55
 
46
56
  9b. **OKF wiki refresh (e39s08):** Before step 8, run maintain-wiki INGEST to refresh the OKF wiki bundle:
@@ -38,6 +38,7 @@ description: "Reviews working-tree changes, then drafts a Conventional Commits t
38
38
  - [ ] **Scope** is a short noun in parentheses if it helps (e.g. `fix(api): …`).
39
39
  - [ ] Breaking changes are explicit (`!` and/or `BREAKING CHANGE:` in the body/footer).
40
40
  - [ ] Description is imperative, lowercase start after the prefix, no trailing period in the title line.
41
+ - [ ] **NO `Co-authored-by` or `Co-Authored-By` footers** — P1 rule (CONVENTIONS.md § Git Attribution). All commits must appear as if authored solely by the human user. The git hook and `land-branch.sh` both block these.
41
42
 
42
43
  ## When not to invent a bump
43
44
 
@@ -0,0 +1,285 @@
1
+ ---
2
+ name: generate-allure-report
3
+ model: sonnet
4
+ description: "Generate Allure-ready reports from bigpowers YAML metadata. Reads execution-status.yaml, release-plan.yaml, epic capsules, task YAMLs, cycle-times.yaml, and bug registry to produce allure-results/junit-results.xml, categories.json, and executor.json. Use when preparing progress dashboards, integrating with Allure TestOps, or generating CI reports."
5
+ ---
6
+
7
+ # Generate Allure Report
8
+
9
+ Generate Allure TestOps-compatible reports from bigpowers project metadata. Produces JUnit XML for story-level test results, custom categories for filtering, and executor metadata — all in the `allure-results/` directory.
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ bash scripts/generate-allure-report.sh
15
+ ```
16
+
17
+ ## What It Produces
18
+
19
+ Three files in `allure-results/`:
20
+
21
+ | File | Description |
22
+ |------|-------------|
23
+ | `junit-results.xml` | One `<testcase>` per story with `<properties>` for risk, security, WSJF, tier, wave, and status. Incomplete stories get a `<failure>` element. |
24
+ | `categories.json` | Custom Allure categories for filtering by epic, risk level (P0), and security reviews. |
25
+ | `executor.json` | Build metadata — name, type, version from release-plan.yaml, build order. |
26
+
27
+ ## Data Sources
28
+
29
+ See [REFERENCE.md](REFERENCE.md)
30
+
31
+ ## Verify
32
+
33
+ ```bash
34
+ test -f allure-results/junit-results.xml && test -f allure-results/categories.json && test -f allure-results/executor.json
35
+ ```
36
+
37
+ ## Handoff
38
+
39
+ - next_skill: null (terminal skill — no downstream workflow step)
40
+
41
+ ---
42
+
43
+ # generate-allure-report — Reference
44
+
45
+ ## Data Sources
46
+
47
+ The script reads five YAML sources from the project:
48
+
49
+ | Source | Path | Fields Used |
50
+ |--------|------|-------------|
51
+ | Execution status | `specs/execution-status.yaml` | `epics`, `stories`, `development_status` |
52
+ | Release plan | `specs/release-plan.yaml` | `release.version`, `release.status`, `bugs` summary |
53
+ | Epic capsules | `specs/epics/**/epic.yaml` + `-tasks.yaml` | Epic metadata, task pass/fail counts |
54
+ | Cycle times | `specs/metrics/cycle-times.yaml` | Story-level `cycle_minutes`, `bcp_per_hour`, `source` |
55
+ | Bug registry | `specs/bugs/registry.yaml` | Bug counts by status and severity |
56
+
57
+ ## Script Body
58
+
59
+ `scripts/generate-allure-report.sh`:
60
+
61
+ ```bash
62
+ #!/usr/bin/env bash
63
+ set -euo pipefail
64
+ source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
65
+ ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || ROOT="$(dirname "${BASH_SOURCE[0]}")/.."
66
+
67
+ mkdir -p "$ROOT/allure-results"
68
+
69
+ $PYTHON - "$ROOT" <<'PY'
70
+ import json
71
+ import sys
72
+ import xml.etree.ElementTree as ET
73
+ from pathlib import Path
74
+
75
+ root = Path(sys.argv[1])
76
+ out = root / "allure-results"
77
+
78
+ # 1. Read execution-status.yaml
79
+ exec_status_file = root / "specs" / "execution-status.yaml"
80
+ release_plan_file = root / "specs" / "release-plan.yaml"
81
+ cycle_times_file = root / "specs" / "metrics" / "cycle-times.yaml"
82
+ bugs_registry_file = root / "specs" / "bugs" / "registry.yaml"
83
+
84
+ sys.path.insert(0, str(root / "scripts" / "lib"))
85
+ from simple_yaml import parse_simple_yaml
86
+
87
+ exec_status = parse_simple_yaml(exec_status_file.read_text()) if exec_status_file.exists() else {}
88
+ release_plan = parse_simple_yaml(release_plan_file.read_text()) if release_plan_file.exists() else {}
89
+ cycle_times = parse_simple_yaml(cycle_times_file.read_text()) if cycle_times_file.exists() else {"stories": []}
90
+ bugs_registry = parse_simple_yaml(bugs_registry_file.read_text()) if bugs_registry_file.exists() else {"bugs": []}
91
+
92
+ # Build cycle-times lookup
93
+ ct_lookup = {}
94
+ for ct in cycle_times.get("stories", []):
95
+ if isinstance(ct, dict):
96
+ ct_lookup[ct.get("id", "")] = ct
97
+
98
+ # 2. Build JUnit XML
99
+ stories = exec_status.get("stories", {})
100
+
101
+ # Counts for testsuite attributes
102
+ total_stories = len(stories)
103
+ incomplete = sum(1 for s in stories.values() if isinstance(s, dict) and s.get("status") != "done")
104
+
105
+ testsuite = ET.Element("testsuite", {
106
+ "name": "bigpowers-epic-progress",
107
+ "tests": str(total_stories),
108
+ "failures": str(incomplete),
109
+ "errors": "0",
110
+ "skipped": "0",
111
+ })
112
+
113
+ for story_id in sorted(stories.keys()):
114
+ story = stories[story_id]
115
+ if not isinstance(story, dict):
116
+ continue
117
+
118
+ epic_id = story.get("epic", "unknown")
119
+ title = story.get("title", story_id)
120
+ bcps = story.get("bcps", 0)
121
+ status = story.get("status", "backlog")
122
+ risk_max = story.get("risk_max", "none")
123
+ security_max = story.get("security_max", "none")
124
+
125
+ # Enrich with cycle-times data
126
+ ct_data = ct_lookup.get(story_id, {})
127
+ cycle_minutes = ct_data.get("cycle_minutes", 0)
128
+ bcp_per_hour = ct_data.get("bcp_per_hour", 0)
129
+
130
+ # Time: cycle_minutes * 60 for seconds in Allure display
131
+ time_seconds = cycle_minutes * 60.0 if cycle_minutes else 0.0
132
+
133
+ testcase = ET.SubElement(testsuite, "testcase", {
134
+ "classname": epic_id,
135
+ "name": f"{story_id}: {title}",
136
+ "time": str(round(time_seconds, 3)),
137
+ })
138
+
139
+ props = ET.SubElement(testcase, "properties")
140
+ ET.SubElement(props, "property", {"name": "risk", "value": risk_max})
141
+ ET.SubElement(props, "property", {"name": "security", "value": security_max})
142
+ ET.SubElement(props, "property", {"name": "bcps", "value": str(bcps)})
143
+ ET.SubElement(props, "property", {"name": "status", "value": status})
144
+ ET.SubElement(props, "property", {"name": "bcp_per_hour", "value": str(bcp_per_hour)})
145
+ ET.SubElement(props, "property", {"name": "lead_time_minutes", "value": str(cycle_minutes)})
146
+
147
+ if status != "done":
148
+ ET.SubElement(testcase, "failure", {
149
+ "message": f"Story {story_id} is {status} [risk={risk_max}, security={security_max}]",
150
+ "type": "StoryIncomplete"
151
+ })
152
+
153
+ tree = ET.ElementTree(testsuite)
154
+ ET.indent(tree, space=" ")
155
+ tree.write(str(out / "junit-results.xml"), encoding="utf-8", xml_declaration=True)
156
+
157
+ # 3. Build categories.json
158
+ epics = exec_status.get("epics", {})
159
+ categories = []
160
+
161
+ for epic_id in sorted(epics.keys()):
162
+ epic = epics[epic_id]
163
+ if isinstance(epic, dict) and epic.get("status") != "done":
164
+ categories.append({
165
+ "name": f"Epic: {epic.get('title', epic_id)}",
166
+ "matchedStatuses": ["failed"],
167
+ "messageRegex": f".*{epic_id}:.*"
168
+ })
169
+
170
+ categories.append({
171
+ "name": "P0 Risk",
172
+ "matchedStatuses": ["failed"],
173
+ "messageRegex": ".*risk.*P0.*"
174
+ })
175
+ categories.append({
176
+ "name": "Security Review",
177
+ "matchedStatuses": ["failed"],
178
+ "messageRegex": ".*security.*(?:medium|high).*"
179
+ })
180
+
181
+ # Add bug-based categories
182
+ bug_list = bugs_registry.get("bugs", [])
183
+ bug_count = len(bug_list) if isinstance(bug_list, list) else 0
184
+ open_bugs = sum(1 for b in bug_list if isinstance(b, dict) and b.get("status") not in ("fixed", "closed", None))
185
+ if open_bugs > 0:
186
+ categories.append({
187
+ "name": "Open Bugs",
188
+ "matchedStatuses": ["failed"],
189
+ "messageRegex": ".*Bug.*"
190
+ })
191
+
192
+ (out / "categories.json").write_text(json.dumps(categories, indent=2))
193
+
194
+ # 4. Build executor.json
195
+ rl = release_plan.get("release", {}) if isinstance(release_plan.get("release"), dict) else {}
196
+ executor = {
197
+ "name": "bigpowers",
198
+ "type": "bigpowers",
199
+ "buildName": rl.get("version", "unknown") if isinstance(rl, dict) else "unknown",
200
+ "buildOrder": len(exec_status.get("development_status", {})),
201
+ }
202
+ (out / "executor.json").write_text(json.dumps(executor, indent=2))
203
+
204
+ # Summary
205
+ epic_count = len([e for e in epics.values() if isinstance(e, dict) and e.get("status") == "done"])
206
+ total_epics = len(epics)
207
+ print(f"generate-allure-report: {total_stories} stories, {epic_count}/{total_epics} epics done, {bug_count} bugs")
208
+ print(f" -> {out}/junit-results.xml")
209
+ print(f" -> {out}/categories.json")
210
+ print(f" -> {out}/executor.json")
211
+ PY
212
+ ```
213
+
214
+ ## JUnit XML Schema
215
+
216
+ ```xml
217
+ <?xml version="1.0" encoding="utf-8"?>
218
+ <testsuite name="bigpowers-epic-progress" tests="N" failures="F" errors="0" skipped="0">
219
+ <testcase classname="e01" name="e01s01: Security slopcheck tags" time="0.75">
220
+ <properties>
221
+ <property name="risk" value="none"/>
222
+ <property name="security" value="none"/>
223
+ <property name="bcps" value="1"/>
224
+ <property name="status" value="done"/>
225
+ <property name="bcp_per_hour" value="1.3"/>
226
+ <property name="lead_time_minutes" value="45"/>
227
+ </properties>
228
+ </testcase>
229
+ <testcase classname="e01" name="e01s99: Some incomplete story" time="0.0">
230
+ <properties>...</properties>
231
+ <failure message="Story e01s99 is backlog [risk=P0, security=high]" type="StoryIncomplete"/>
232
+ </testcase>
233
+ </testsuite>
234
+ ```
235
+
236
+ ## Categories JSON Schema
237
+
238
+ ```json
239
+ [
240
+ {
241
+ "name": "Epic: Quality Core - Skill Hardening",
242
+ "matchedStatuses": ["failed"],
243
+ "messageRegex": ".*e45:.*"
244
+ },
245
+ {
246
+ "name": "P0 Risk",
247
+ "matchedStatuses": ["failed"],
248
+ "messageRegex": ".*risk.*P0.*"
249
+ },
250
+ {
251
+ "name": "Security Review",
252
+ "matchedStatuses": ["failed"],
253
+ "messageRegex": ".*security.*(?:medium|high).*"
254
+ }
255
+ ]
256
+ ```
257
+
258
+ ## Executor JSON Schema
259
+
260
+ ```json
261
+ {
262
+ "name": "bigpowers",
263
+ "type": "bigpowers",
264
+ "buildName": "2.76.2",
265
+ "buildOrder": 400
266
+ }
267
+ ```
268
+
269
+ ## Example Usage
270
+
271
+ ```bash
272
+ # Generate reports
273
+ bash scripts/generate-allure-report.sh
274
+
275
+ # Verify output
276
+ test -f allure-results/junit-results.xml && echo "JUnit OK"
277
+ test -f allure-results/categories.json && echo "Categories OK"
278
+ test -f allure-results/executor.json && echo "Executor OK"
279
+
280
+ # Serve with Allure
281
+ allure serve allure-results/
282
+
283
+ # Or open the Allure TestOps UI
284
+ allure open allure-results/
285
+ ```
@@ -0,0 +1,288 @@
1
+ ---
2
+ name: harden-vps
3
+ model: haiku
4
+ description: "Harden a production Linux VPS for BigBase across three layers — BigBase app (systemd hardening, monitoring alerts, backup automation), Ubuntu OS (UFW firewall, fail2ban SSH, unattended-upgrades, SSH hardening), and Contabo VPS (health checks, daily backups, monthly snapshots). Use when the user wants to secure a production server, harden a VPS, audit server security, or mentions production hardening, VPS security, or harden the server."
5
+ ---
6
+
7
+ # Harden VPS
8
+
9
+ Three-layer production hardening for BigBase on Contabo VPS. Each layer independently verifiable. Apply OS first (firewall blocks attacks immediately), then BigBase (alerts + backups), then Contabo (snapshots). See [REFERENCE.md](REFERENCE.md) for full script bodies, systemd unit template, and gotchas.
10
+
11
+ ## Quick start
12
+
13
+ SSH as root into the VPS. Find credentials in the Contabo Customer Control Panel.
14
+
15
+ > **HARD GATE** — Run `ufw status` first. No firewall = layer 1 takes priority over everything.
16
+
17
+ ## Layer 1 — Ubuntu OS
18
+
19
+ ```bash
20
+ # UFW
21
+ ufw default deny incoming && ufw default allow outgoing
22
+ ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw enable
23
+ # → verify: ufw status | grep -q active
24
+
25
+ # fail2ban
26
+ apt install -y fail2ban
27
+ # Configure /etc/fail2ban/jail.local: sshd, maxretry=3, bantime=3600, findtime=600
28
+ systemctl restart fail2ban
29
+ # → verify: fail2ban-client status sshd
30
+
31
+ # unattended-upgrades
32
+ apt install -y unattended-upgrades && dpkg-reconfigure -plow unattended-upgrades
33
+ # → verify: systemctl is-active unattended-upgrades | grep -q active
34
+
35
+ # SSH: PermitRootLogin no, PasswordAuthentication no, PubkeyAuthentication yes
36
+ # → verify: sshd -T | grep -E 'permitrootlogin no|passwordauthentication no'
37
+
38
+ # Deploy healthcheck.sh → /opt/bigbase/scripts/healthcheck.sh
39
+ # Crontab: */5 * * * * /opt/bigbase/scripts/healthcheck.sh
40
+ ```
41
+
42
+ ## Layer 2 — BigBase application
43
+
44
+ ```bash
45
+ # systemd: User=bigbase, NoNewPrivileges=yes, ProtectSystem=full,
46
+ # ProtectKernelTunables=yes, ProtectKernelModules=yes,
47
+ # ProtectControlGroups=yes, RestrictAddressFamilies=AF_INET AF_INET6,
48
+ # RestrictRealtime=yes, PrivateTmp=yes, LimitNOFILE=65536
49
+ # → verify: systemctl show bigbase -p NoNewPrivileges -p ProtectSystem -p User
50
+
51
+ # Alerts (BigBase requires auth; insert via SQLite)
52
+ sqlite3 /opt/bigbase/data/bigbase.db "
53
+ INSERT INTO monitoring_alerts (id,name,metric,threshold,operator,enabled,duration_seconds)
54
+ VALUES ('a1','Disk >80%','disk_used_percent',80,'gt',1,300);
55
+ INSERT INTO monitoring_alerts (id,name,metric,threshold,operator,enabled,duration_seconds)
56
+ VALUES ('a2','CPU >90%','cpu_percent',90,'gt',1,60);
57
+ INSERT INTO monitoring_alerts (id,name,metric,threshold,operator,enabled,duration_seconds)
58
+ VALUES ('a3','RAM >85%','mem_used_percent',85,'gt',1,120);
59
+ "
60
+ systemctl restart bigbase
61
+
62
+ # Backup crontab (root):
63
+ # 0 2 * * * cp /opt/bigbase/data/bigbase.db /backup/bigbase-$(date +\%Y\%m\%d).db
64
+ # 0 3 * * * find /backup/ -name "bigbase-*.db" -mtime +90 -delete
65
+ ```
66
+
67
+ ## Layer 3 — Contabo VPS
68
+
69
+ ```bash
70
+ # cntb CLI
71
+ curl -sL "$(curl -sL https://api.github.com/repos/contabo/cntb/releases/latest \
72
+ | grep browser_download_url.*linux_amd64.tar.gz | head -1 | cut -d'"' -f4)" \
73
+ | tar xz -C /usr/local/bin
74
+
75
+ # Snapshot script → /opt/bigbase/scripts/contabo-snapshot.sh (reads from /opt/bigbase/.env)
76
+ # Credentials as env vars in /opt/bigbase/.env (deployed by GitHub Actions):
77
+ # CONTABO_CLIENT_ID, CONTABO_CLIENT_SECRET, CONTABO_API_USER, CONTABO_API_PASSWORD
78
+ # Crontab: 0 4 1 * * /opt/bigbase/scripts/contabo-snapshot.sh
79
+
80
+ # > HARD GATE — Snapshot cron silently fails until env vars are set in .env.
81
+ # Credentials source: Contabo Customer Panel → API Details.
82
+ # Local dev: add to .envrc. Production: GitHub Secrets → deploy → /opt/bigbase/.env
83
+ ```
84
+
85
+ ## CRITICAL GOTCHAS
86
+
87
+ 1. **Shell escaping in Orca terminals:** `$VAR`, `$(…)`, and `%` get eaten by the local shell. Always use base64: `echo '<base64>' | base64 -d > script.sh`
88
+ 2. **Crontab `%`:** cron interprets `%` as newline. Escape as `\%` in `$(date +\%Y\%m\%d)`
89
+ 3. **fail2ban exit 255:** means a jail references a missing log file. Remove the broken jail, restart.
90
+ 4. **BigBase alerts need auth:** POST to `/api/monitoring/alerts` requires Bearer token. Workaround: insert directly into SQLite, then restart BigBase.
91
+
92
+ ## Verify all 8 gates
93
+
94
+ ```bash
95
+ ufw status|grep -q active||echo FAIL:ufw
96
+ fail2ban-client status sshd>/dev/null 2>&1||echo FAIL:fail2ban
97
+ systemctl is-active unattended-upgrades|grep -q active||echo FAIL:unattended
98
+ sshd -T|grep -q 'permitrootlogin no'||echo FAIL:sshd
99
+ systemctl show bigbase -p NoNewPrivileges|grep -q yes||echo FAIL:systemd
100
+ systemctl is-active bigbase|grep -q active||echo FAIL:bigbase
101
+ sqlite3 /opt/bigbase/data/bigbase.db "SELECT count(*) FROM monitoring_alerts"|grep -q 3||echo FAIL:alerts
102
+ crontab -l|grep -q healthcheck&&crontab -l|grep -q bigbase.db&&crontab -l|grep -q contabo-snapshot||echo FAIL:crontab
103
+ echo ALL 8 GATES PASSED
104
+ ```
105
+
106
+ → verify: run the 8-gate one-liner on the VPS.
107
+
108
+ ---
109
+
110
+ # Harden VPS — Reference
111
+
112
+ ## Health check script
113
+
114
+ Deploy to `/opt/bigbase/scripts/healthcheck.sh`:
115
+
116
+ ```bash
117
+ #!/bin/bash
118
+ LOG=/var/log/bigbase/health.log
119
+ mkdir -p /var/log/bigbase
120
+ DISK=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
121
+ RAM=$(free | awk '/Mem:/ {printf "%.0f", $3/$2*100}')
122
+ NOW=$(date -Iseconds)
123
+ [ "$DISK" -gt 85 ] && echo "$NOW ALERT: Disk ${DISK}%" >> "$LOG"
124
+ [ "$RAM" -gt 90 ] && echo "$NOW ALERT: RAM ${RAM}%" >> "$LOG"
125
+ systemctl is-active --quiet bigbase || { echo "$NOW ALERT: BigBase DOWN" >> "$LOG"; systemctl restart bigbase; }
126
+ [ -f /var/run/reboot-required ] && echo "$NOW WARN: kernel reboot pending" >> "$LOG"
127
+ ```
128
+
129
+ Make executable: `chmod +x /opt/bigbase/scripts/healthcheck.sh`
130
+
131
+ ## BigBase systemd unit
132
+
133
+ Full hardened unit at `/etc/systemd/system/bigbase.service`:
134
+
135
+ ```ini
136
+ [Unit]
137
+ Description=BigBase BaaS Platform
138
+ Documentation=https://github.com/danielvm-git/bigbase
139
+ After=network.target caddy.service
140
+ Wants=caddy.service
141
+
142
+ [Service]
143
+ Type=simple
144
+ User=bigbase
145
+ Group=bigbase
146
+ WorkingDirectory=/opt/bigbase
147
+ ExecStart=/opt/bigbase/bin/bigbase serve \
148
+ --port 8080 \
149
+ --db /opt/bigbase/data/bigbase.db \
150
+ --sites-domain <your-domain.example.com>
151
+ EnvironmentFile=-/opt/bigbase/.env
152
+ Environment=BIGBASE_HOME=/opt/bigbase
153
+ Environment=HOME=/opt/bigbase
154
+ Environment=NPM_CONFIG_CACHE=/opt/bigbase/.npm
155
+ Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
156
+ Restart=always
157
+ RestartSec=5
158
+ StartLimitIntervalSec=60
159
+ StartLimitBurst=3
160
+ TimeoutStopSec=30
161
+ KillSignal=SIGTERM
162
+ NoNewPrivileges=yes
163
+ PrivateTmp=yes
164
+ ProtectSystem=full
165
+ ProtectHome=yes
166
+ ProtectKernelTunables=yes
167
+ ProtectKernelModules=yes
168
+ ProtectControlGroups=yes
169
+ RestrictAddressFamilies=AF_INET AF_INET6
170
+ RestrictRealtime=yes
171
+ ReadWritePaths=/opt/bigbase/data
172
+ ReadWritePaths=/opt/bigbase/backups
173
+ ReadWritePaths=/opt/bigbase/secrets
174
+ ReadWritePaths=/opt/bigbase/.npm
175
+ ReadWritePaths=/opt/bigbase/logs
176
+ LimitNOFILE=65536
177
+
178
+ [Install]
179
+ WantedBy=multi-user.target
180
+ ```
181
+
182
+ ## fail2ban jail.local
183
+
184
+ ```ini
185
+ [sshd]
186
+ enabled = true
187
+ port = ssh
188
+ filter = sshd
189
+ logpath = /var/log/auth.log
190
+ maxretry = 3
191
+ bantime = 3600
192
+ findtime = 600
193
+ ```
194
+
195
+ Note: BigBase auth jail deferred — BigBase logs to journald, not a file. To enable, use `backend = systemd` with a journald filter.
196
+
197
+ ## contabo-snapshot.sh
198
+
199
+ Deploy to `/opt/bigbase/scripts/contabo-snapshot.sh`. Reads credentials from `/opt/bigbase/.env` (same file used by BigBase systemd unit):
200
+
201
+ ```bash
202
+ #!/bin/bash
203
+ set -e
204
+ LOG=/var/log/bigbase/snapshot.log
205
+ ENVFILE=/opt/bigbase/.env
206
+ INSTANCE_ID=<your-contabo-instance-id>
207
+
208
+ [ -f "$ENVFILE" ] || { echo "$(date -Iseconds) ERROR: $ENVFILE missing" >> "$LOG"; exit 1; }
209
+ set -a; source "$ENVFILE"; set +a
210
+
211
+ [ -z "$CONTABO_CLIENT_ID" ] && { echo "$(date -Iseconds) ERROR: CONTABO_CLIENT_ID not set" >> "$LOG"; exit 1; }
212
+
213
+ TOKEN=$(curl -s -d "client_id=$CONTABO_CLIENT_ID" \
214
+ -d "client_secret=$CONTABO_CLIENT_SECRET" \
215
+ --data-urlencode "username=$CONTABO_API_USER" \
216
+ --data-urlencode "password=$CONTABO_API_PASSWORD" \
217
+ -d 'grant_type=password' \
218
+ 'https://auth.contabo.com/auth/realms/contabo/protocol/openid-connect/token' \
219
+ | jq -r '.access_token')
220
+
221
+ [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ] && { echo "$(date -Iseconds) ERROR: auth failed" >> "$LOG"; exit 1; }
222
+
223
+ curl -s -X POST \
224
+ -H "Authorization: Bearer $TOKEN" \
225
+ -H "Content-Type: application/json" \
226
+ -H "x-request-id: $(uuidgen 2>/dev/null || echo $RANDOM)" \
227
+ "https://api.contabo.com/v1/compute/instances/${INSTANCE_ID}/snapshots" \
228
+ | tee -a "$LOG"
229
+
230
+ echo >> "$LOG"
231
+ echo "$(date -Iseconds) Snapshot requested for $INSTANCE_ID" >> "$LOG"
232
+ ```
233
+
234
+ ## Contabo credentials
235
+
236
+ Add to `/opt/bigbase/.env` (same file used by BigBase systemd unit, deployed by GitHub Actions):
237
+
238
+ ```
239
+ CONTABO_CLIENT_ID=
240
+ CONTABO_CLIENT_SECRET=
241
+ CONTABO_API_USER=
242
+ CONTABO_API_PASSWORD=
243
+ ```
244
+
245
+ Source: Contabo Customer Control Panel → API Details. ClientId and ClientSecret are generated there. API User is your email. API Password is set separately in the panel.
246
+
247
+ **Pipeline:** GitHub Secrets → deploy workflow → `/opt/bigbase/.env` on VPS. For local dev (`cntb get instances`), add to `.envrc`.
248
+
249
+ ## Contabo instance info template
250
+
251
+ Fill in your instance details from the Contabo Customer Control Panel:
252
+
253
+ ```
254
+ IP: <your-instance-ip>
255
+ IPv6: <your-instance-ipv6>
256
+ Region: <your-region>
257
+ OS: <your-os-version>
258
+ Disk: <your-disk-size>
259
+ Default user: root
260
+ Customer ID: <your-customer-id>
261
+ ```
262
+
263
+ ## BigBase monitoring alert SQL
264
+
265
+ ```sql
266
+ INSERT INTO monitoring_alerts (id, name, metric, threshold, operator, enabled, duration_seconds)
267
+ VALUES ('alert-001', 'VPS Disk above 80%', 'disk_used_percent', 80, 'gt', 1, 300);
268
+
269
+ INSERT INTO monitoring_alerts (id, name, metric, threshold, operator, enabled, duration_seconds)
270
+ VALUES ('alert-002', 'VPS CPU above 90%', 'cpu_percent', 90, 'gt', 1, 60);
271
+
272
+ INSERT INTO monitoring_alerts (id, name, metric, threshold, operator, enabled, duration_seconds)
273
+ VALUES ('alert-003', 'VPS RAM above 85%', 'mem_used_percent', 85, 'gt', 1, 120);
274
+ ```
275
+
276
+ Alerts are loaded at BigBase startup. Restart with `systemctl restart bigbase` after inserting.
277
+
278
+ ## Base64 encoding workaround
279
+
280
+ When sending scripts through Orca terminal `--text`, the local bash shell interprets `$`, `(`, and `%`. Encode locally and decode remotely:
281
+
282
+ ```bash
283
+ # Local
284
+ cat script.sh | base64
285
+
286
+ # Remote terminal
287
+ echo '<base64-output>' | base64 -d > script.sh
288
+ ```