You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
You are a StartOS service packager. You help create, maintain, and update `.s9pk` service packages for StartOS.
4
+
5
+
## Packaging Guide
6
+
7
+
The packaging guide is your primary reference. Follow it exactly. Read `SUMMARY.md` first to identify which sections are relevant to the current task, then read only those `.md` files directly. Do not load all sections at once.
8
+
9
+
### Local Read (preferred)
10
+
11
+
If `start-docs/packaging/src/` exists in the workspace, use the Read tool:
12
+
13
+
```
14
+
start-docs/packaging/src/SUMMARY.md # Read first — section index
15
+
start-docs/packaging/src/<section>.md # Then read only what you need
16
+
```
17
+
18
+
### Web Fetch (fallback)
19
+
20
+
If the local docs are not available, use WebFetch:
21
+
22
+
```
23
+
WebFetch: https://docs.start9.com/packaging/llms.txt # Fetch first — section index
24
+
WebFetch: https://docs.start9.com/packaging/<page>.html # Then fetch only what you need
25
+
```
26
+
27
+
## Golden Rule
28
+
**Match existing patterns**: Match patterns from the docs and other packages. Pretty much anything you might need todo has already been done well.
Copy file name to clipboardExpand all lines: packaging/src/environment-setup.md
+51Lines changed: 51 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -81,3 +81,54 @@ start-cli --version
81
81
82
82
> [!TIP]
83
83
> If any command is not found, revisit the installation steps for that tool and ensure it is on your system PATH.
84
+
85
+
## Coding with Claude (Recommended)
86
+
87
+
AI coding tools like [Claude Code](https://docs.anthropic.com/en/docs/claude-code) can dramatically accelerate your packaging workflow. To get the best results, set up a workspace that gives Claude direct access to the packaging guide.
88
+
89
+
### 1. Create a workspace directory
90
+
91
+
Create a directory that will serve as your AI-assisted workspace. This is **not** inside your package repo — it sits alongside it.
92
+
93
+
```
94
+
mkdir my-workspace && cd my-workspace
95
+
```
96
+
97
+
### 2. Clone the docs
98
+
99
+
Clone the Start9 docs repo so Claude can read the packaging guide locally:
├── CLAUDE.md ← AI instructions (not committed anywhere)
129
+
├── start-docs/ ← packaging guide for Claude to read
130
+
└── my-service-startos/ ← your package repo
131
+
```
132
+
133
+
> [!NOTE]
134
+
> Do not put `start-docs/` or `CLAUDE.md` inside your package repo. They live alongside it in the workspace so they don't pollute your package's git history.
> Never use an identity mapper like `.read((s) => s)`. Either omit the mapper to get the full object (`.read()`) or use it to extract a specific field (`.read((s) => s.someField)`).
99
+
100
+
97
101
### Subset Reading
98
102
99
103
Use mapping to retrieve only specific fields rather than entire files:
@@ -147,26 +151,41 @@ const shape = object({
147
151
When an upstream service reads a config file (TOML, YAML, JSON, etc.), model that file directly with `FileHelper` rather than storing values in `store.json` and passing them as environment variables. A direct FileModel provides:
148
152
149
153
-**Two-way binding**: Actions can read and write the upstream config file directly.
150
-
-**Simpler main.ts**: No need to read from store, build env vars, and pass them to the daemon. Just write the FileModel to the subcontainer rootfs.
154
+
-**Simpler main.ts**: Mount the config file from the volume into the subcontainer. No need to read and regenerate it.
151
155
-**Easy user configuration**: Exposing config options via Actions is as simple as `configToml.merge(effects, { key: newValue })`.
152
156
153
157
Use `store.json` only for internal package state that has no upstream config file equivalent (e.g., a generated PostgreSQL password that the upstream service doesn't read from its own config file).
Copy file name to clipboardExpand all lines: packaging/src/main.md
+8-18Lines changed: 8 additions & 18 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -232,34 +232,24 @@ sdk.Mounts.of()
232
232
233
233
## Writing to Subcontainer Rootfs
234
234
235
-
For config files that are regenerated on every startup, write directly to the subcontainer's rootfs instead of using volume mounts. This is simpler and avoids file mount issues:
235
+
For config files that are *generated from code*on every startup (e.g., a Python settings file built from hostnames and secrets), write directly to the subcontainer's rootfs:
236
236
237
237
```typescript
238
-
// Create subcontainer first
239
-
const appSub =awaitsdk.SubContainer.of(
240
-
effects,
241
-
{ imageId: "my-service" },
242
-
sdk.Mounts.of().mountVolume({
243
-
volumeId: "main",
244
-
subpath: null,
245
-
mountpoint: "/data",
246
-
readonly: false,
247
-
}),
248
-
"my-service-sub",
249
-
);
250
-
251
-
// Write config directly to subcontainer rootfs
238
+
// Write a generated config to subcontainer rootfs
252
239
awaitwriteFile(
253
240
`${appSub.rootfs}/app/config.py`,
254
241
generateConfig({ secretKey, allowedHosts }),
255
242
);
256
243
```
257
244
245
+
> [!WARNING]
246
+
> If the config file is managed by a FileModel, do NOT read it and write it back to rootfs. Mount it from the volume instead — the file already exists there.
247
+
258
248
**When to use rootfs vs volume mounts:**
259
249
260
-
-**Rootfs**: Ephemeral config files regenerated on each startup (secrets, hostnames, etc.)
261
-
-**Volume mount (directory)**: Persistent data that survives restarts (databases, user files)
262
-
-**Volume mount (file)**: Persistent config that users might edit (requires `type: 'file'`)
250
+
-**Rootfs**: Config files *generated from code* that don't exist on a volume (e.g., built from hostnames, env vars, or templates)
251
+
-**Volume mount (directory)**: Mount a directory that contains the config file alongside other persistent data. The config file is just one of many files in the mounted directory.
252
+
-**Volume mount (file)**: Mount a single config file with `type: 'file'` when the config lives on a volume that is otherwise unrelated to the container's filesystem.
Check the upstream project's LICENSE file and use the correct SPDX identifier (e.g., `MIT`, `Apache-2.0`, `GPL-3.0`). Create a symlink from your project root to the upstream license:
90
+
Check the upstream project's LICENSE file and use the correct SPDX identifier (e.g., `MIT`, `Apache-2.0`, `GPL-3.0`). If you have a git submodule, symlink to its license. Otherwise, copy the license text directly from the upstream repository:
93
91
94
92
```bash
93
+
# With submodule
95
94
ln -sf upstream-project/LICENSE LICENSE
95
+
96
+
# Without submodule -- copy from upstream repo
96
97
```
97
98
98
99
## Icon
@@ -250,9 +251,13 @@ alerts: {
250
251
251
252
## Volumes
252
253
253
-
Storage volumes for persistent data. Usually just `['main']`:
254
+
Storage volumes for persistent data. When possible, prefer matching the upstream project's volume naming convention for clarity:
254
255
255
256
```typescript
257
+
// If upstream docker-compose uses a volume named "mcaptcha-data"
258
+
volumes: ['mcaptcha-data'],
259
+
260
+
// Simple services can use 'main'
256
261
volumes: ['main'],
257
262
```
258
263
@@ -262,7 +267,7 @@ For services needing separate storage areas:
262
267
volumes: ['main', 'db', 'config'],
263
268
```
264
269
265
-
Reference these in `main.ts` mounts as `'main'`, `'db'`, `'config'`.
270
+
Reference these in `main.ts` mounts by the volume ID you chose.
0 commit comments