happy2 0.0.15 → 0.0.17

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 (76) hide show
  1. package/Dockerfile +30 -0
  2. package/README.md +170 -0
  3. package/deploy/config.yaml +56 -0
  4. package/deploy/happy2-server.yaml +140 -0
  5. package/deploy/happy2-web.yaml +87 -0
  6. package/package.json +3 -2
  7. package/packages/happy2-server/README.md +70 -15
  8. package/packages/happy2-server/dist/backend.d.ts +12 -0
  9. package/packages/happy2-server/dist/backend.d.ts.map +1 -0
  10. package/packages/happy2-server/dist/backend.js +48 -0
  11. package/packages/happy2-server/dist/backend.js.map +1 -0
  12. package/packages/happy2-server/dist/cli.js +6 -25
  13. package/packages/happy2-server/dist/cli.js.map +1 -1
  14. package/packages/happy2-server/dist/index.d.ts +2 -0
  15. package/packages/happy2-server/dist/index.d.ts.map +1 -1
  16. package/packages/happy2-server/dist/index.js +2 -0
  17. package/packages/happy2-server/dist/index.js.map +1 -1
  18. package/packages/happy2-server/dist/modules/agents/daemon.d.ts +16 -0
  19. package/packages/happy2-server/dist/modules/agents/daemon.d.ts.map +1 -1
  20. package/packages/happy2-server/dist/modules/agents/daemon.js +128 -1
  21. package/packages/happy2-server/dist/modules/agents/daemon.js.map +1 -1
  22. package/packages/happy2-server/dist/modules/agents/index.d.ts +1 -1
  23. package/packages/happy2-server/dist/modules/agents/index.d.ts.map +1 -1
  24. package/packages/happy2-server/dist/modules/agents/index.js +1 -1
  25. package/packages/happy2-server/dist/modules/agents/index.js.map +1 -1
  26. package/packages/happy2-server/dist/modules/agents/service.d.ts +53 -0
  27. package/packages/happy2-server/dist/modules/agents/service.d.ts.map +1 -1
  28. package/packages/happy2-server/dist/modules/agents/service.js +39 -0
  29. package/packages/happy2-server/dist/modules/agents/service.js.map +1 -1
  30. package/packages/happy2-server/dist/modules/config/defaults.d.ts +4 -4
  31. package/packages/happy2-server/dist/modules/config/defaults.d.ts.map +1 -1
  32. package/packages/happy2-server/dist/modules/config/defaults.js +8 -8
  33. package/packages/happy2-server/dist/modules/config/defaults.js.map +1 -1
  34. package/packages/happy2-server/dist/modules/config/loader.d.ts +2 -2
  35. package/packages/happy2-server/dist/modules/config/loader.d.ts.map +1 -1
  36. package/packages/happy2-server/dist/modules/config/loader.js +83 -73
  37. package/packages/happy2-server/dist/modules/config/loader.js.map +1 -1
  38. package/packages/happy2-server/dist/modules/config/loader.test.js +35 -0
  39. package/packages/happy2-server/dist/modules/config/loader.test.js.map +1 -1
  40. package/packages/happy2-server/dist/modules/config/runtime.d.ts +1 -1
  41. package/packages/happy2-server/dist/modules/config/runtime.d.ts.map +1 -1
  42. package/packages/happy2-server/dist/modules/config/runtime.js +17 -4
  43. package/packages/happy2-server/dist/modules/config/runtime.js.map +1 -1
  44. package/packages/happy2-server/dist/modules/config/runtime.test.d.ts +2 -0
  45. package/packages/happy2-server/dist/modules/config/runtime.test.d.ts.map +1 -0
  46. package/packages/happy2-server/dist/modules/config/runtime.test.js +99 -0
  47. package/packages/happy2-server/dist/modules/config/runtime.test.js.map +1 -0
  48. package/packages/happy2-server/dist/routes/agents.d.ts +1 -1
  49. package/packages/happy2-server/dist/routes/agents.d.ts.map +1 -1
  50. package/packages/happy2-server/dist/routes/agents.js +132 -0
  51. package/packages/happy2-server/dist/routes/agents.js.map +1 -1
  52. package/packages/happy2-server/dist/runner.js +29 -12
  53. package/packages/happy2-server/dist/runner.js.map +1 -1
  54. package/packages/happy2-server/dist/runnerCommand.d.ts +21 -0
  55. package/packages/happy2-server/dist/runnerCommand.d.ts.map +1 -0
  56. package/packages/happy2-server/dist/runnerCommand.js +97 -0
  57. package/packages/happy2-server/dist/runnerCommand.js.map +1 -0
  58. package/packages/happy2-server/dist/runnerCommand.test.d.ts +2 -0
  59. package/packages/happy2-server/dist/runnerCommand.test.d.ts.map +1 -0
  60. package/packages/happy2-server/dist/runnerCommand.test.js +59 -0
  61. package/packages/happy2-server/dist/runnerCommand.test.js.map +1 -0
  62. package/packages/happy2-server/dist/standalone.d.ts +2 -5
  63. package/packages/happy2-server/dist/standalone.d.ts.map +1 -1
  64. package/packages/happy2-server/dist/standalone.js +14 -65
  65. package/packages/happy2-server/dist/standalone.js.map +1 -1
  66. package/packages/happy2-server/dist/web/assets/index-B51jYSrY.css +2 -0
  67. package/packages/happy2-server/dist/web/assets/index-szUjvBO1.js +39 -0
  68. package/packages/happy2-server/dist/web/index.html +2 -2
  69. package/packages/happy2-server/dist/web.d.ts +12 -0
  70. package/packages/happy2-server/dist/web.d.ts.map +1 -0
  71. package/packages/happy2-server/dist/web.js +89 -0
  72. package/packages/happy2-server/dist/web.js.map +1 -0
  73. package/packages/happy2-server/happy2.example.toml +3 -5
  74. package/packages/happy2-server/Dockerfile +0 -18
  75. package/packages/happy2-server/dist/web/assets/index-BT_VezYx.js +0 -39
  76. package/packages/happy2-server/dist/web/assets/index-Dys3sWv8.css +0 -2
package/Dockerfile ADDED
@@ -0,0 +1,30 @@
1
+ # Build from the repository root:
2
+ # docker build -t slopus/happy2 .
3
+ FROM node:24-bookworm-slim AS build
4
+ WORKDIR /workspace
5
+ RUN corepack enable
6
+ COPY .npmrc package.json pnpm-lock.yaml pnpm-workspace.yaml ./
7
+ COPY packages/happy2-server/package.json packages/happy2-server/package.json
8
+ COPY packages/happy2-state/package.json packages/happy2-state/package.json
9
+ COPY packages/happy2-ui/package.json packages/happy2-ui/package.json
10
+ COPY packages/happy2-app/package.json packages/happy2-app/package.json
11
+ COPY packages/happy2-web/package.json packages/happy2-web/package.json
12
+ COPY packages/happy2-gym/package.json packages/happy2-gym/package.json
13
+ RUN pnpm install --frozen-lockfile --filter happy2-server --filter happy2-web...
14
+ COPY tsconfig.base.json tsconfig.json ./
15
+ COPY packages/happy2-server packages/happy2-server
16
+ COPY packages/happy2-state packages/happy2-state
17
+ COPY packages/happy2-ui packages/happy2-ui
18
+ COPY packages/happy2-app packages/happy2-app
19
+ COPY packages/happy2-web packages/happy2-web
20
+ RUN pnpm --filter happy2-server build:server \
21
+ && VITE_HAPPY2_SERVER_URL=/ pnpm --filter happy2-web exec vite build --outDir ../happy2-server/dist/web --emptyOutDir false \
22
+ && pnpm --filter happy2-server --prod deploy --legacy /app
23
+
24
+ FROM node:24-bookworm-slim
25
+ ENV NODE_ENV=production
26
+ WORKDIR /app
27
+ COPY --from=build --chown=node:node /app ./
28
+ USER node
29
+ EXPOSE 3000
30
+ ENTRYPOINT ["node", "dist/runner.js"]
package/README.md CHANGED
@@ -19,6 +19,7 @@
19
19
 
20
20
  <p>
21
21
  <a href="#quick-start">Quick start</a> ·
22
+ <a href="#configuration">Configuration</a> ·
22
23
  <a href="#why-happy-2">Why Happy (2)?</a> ·
23
24
  <a href="#how-it-works">How it works</a> ·
24
25
  <a href="#project-components">Project components</a> ·
@@ -78,6 +79,175 @@ as its working directory and uses its `.happy2` state. When started through
78
79
  `npx`, the generated service runs `npx --yes happy2` instead of depending on an
79
80
  evictable `_npx` cache path.
80
81
 
82
+ ## Configuration
83
+
84
+ Happy (2) uses the following configuration precedence:
85
+
86
+ 1. `--config /path/to/happy2.toml`
87
+ 2. `HAPPY2_CONFIG=/path/to/happy2.toml`
88
+ 3. `./.happy2/happy2.toml`, when it exists
89
+ 4. Built-in defaults
90
+
91
+ TOML configuration is partial. Happy (2) recursively merges the supplied fields
92
+ over the built-in defaults, so this is enough to expose the public listener:
93
+
94
+ ```toml
95
+ [server]
96
+ host = "0.0.0.0"
97
+ ```
98
+
99
+ The managed path is relative to the directory where Happy (2) starts. Relative
100
+ database, file, plugin, key, and agent paths are also resolved from that working
101
+ directory, not from the TOML file's directory. The standalone `happy2` and
102
+ `happy2 backend` commands use this configuration; `happy2 web` has its own CLI
103
+ options because it only serves the SPA and proxies `/v0`.
104
+
105
+ ### Complete TOML reference
106
+
107
+ All supported keys are shown below. Uncomment optional keys only when needed.
108
+ The active values are equivalent to the built-in local defaults. For readability,
109
+ paths are shown relative to the working directory; generated defaults store those
110
+ same locations as absolute paths.
111
+
112
+ ```toml
113
+ [server]
114
+ # all: authentication + product API; auth: authentication only; api: validation only
115
+ role = "all"
116
+ host = "127.0.0.1"
117
+ port = 3000
118
+ public_url = "http://127.0.0.1:3000"
119
+ # Number of trusted proxies outside Happy (2). Keep 0 for direct connections.
120
+ trusted_proxy_hops = 0
121
+
122
+ [database]
123
+ url = "file:.happy2/happy2.db"
124
+ # Name of the environment variable containing a remote libSQL auth token.
125
+ # auth_token_env = "HAPPY2_DATABASE_AUTH_TOKEN"
126
+
127
+ [agents]
128
+ enabled = true
129
+ # These default to the private Rig runtime under .happy2/rig.
130
+ # socket_path = ".happy2/rig/server.sock"
131
+ # token_path = ".happy2/rig/token"
132
+ # command = "/absolute/path/to/rig"
133
+ default_cwd = ".happy2/workspaces"
134
+
135
+ [files]
136
+ provider = "local"
137
+ directory = ".happy2/files"
138
+ signed_url_expiry_seconds = 300
139
+ max_upload_bytes = 536870912
140
+ resumable_chunk_bytes = 8388608
141
+ # Zero disables the corresponding quota.
142
+ per_user_quota_bytes = 0
143
+ server_quota_bytes = 0
144
+ incomplete_upload_expiry_seconds = 86400
145
+ quarantine_retention_seconds = 2592000
146
+ # malware_scanner_command = "/usr/local/bin/clamscan"
147
+ malware_scanner_arguments = []
148
+ malware_scan_timeout_seconds = 120
149
+ malware_scan_failure_mode = "deny" # deny or allow
150
+
151
+ [plugins]
152
+ directory = ".happy2/plugins"
153
+ # Capability-only API used by plugin containers. Firewall it from untrusted networks.
154
+ host_api_host = "0.0.0.0"
155
+ host_api_port = 3001
156
+
157
+ [security]
158
+ # Name of the environment variable containing the integration encryption secret.
159
+ integration_secret_env = "HAPPY2_INTEGRATION_SECRET"
160
+
161
+ [security.rate_limit]
162
+ enabled = true
163
+ reads_per_minute = 1200
164
+ writes_per_minute = 300
165
+ auth_per_minute = 30
166
+
167
+ [security.idempotency]
168
+ enabled = true
169
+ lease_seconds = 30
170
+ retention_seconds = 86400
171
+
172
+ [jwt]
173
+ issuer = "http://127.0.0.1:3000"
174
+ audience = "happy2-desktop"
175
+ key_id = "local-generated"
176
+ expiry_days = 30
177
+ # PEM files may replace environment or generated keys.
178
+ # private_key_path = "/run/secrets/happy2-jwt-private.pem"
179
+ # public_key_path = "/run/secrets/happy2-jwt-public.pem"
180
+
181
+ # Password is the default. Set it to false before enabling magic link, OIDC,
182
+ # or Cloudflare Access; exactly one authentication method may be enabled.
183
+ [auth.password]
184
+ enabled = true
185
+
186
+ [auth.dev_tokens]
187
+ # This augments the selected authentication method; it is not a separate method.
188
+ enabled = false
189
+
190
+ [auth.magic_link]
191
+ enabled = false
192
+ # from = "Happy (2) <noreply@example.com>"
193
+ # redirect_url = "happy2://auth/magic-link"
194
+
195
+ # Replace "example" with a stable provider ID.
196
+ [auth.oidc.example]
197
+ enabled = false
198
+ # discovery_url = "https://id.example.com/.well-known/openid-configuration"
199
+ # client_id = "happy2"
200
+ # client_secret_env = "HAPPY2_OIDC_EXAMPLE_CLIENT_SECRET"
201
+ # scopes = ["openid", "email", "profile"]
202
+ # redirect_path = "/v0/auth/oidc/example/callback"
203
+
204
+ [auth.cloudflare_access]
205
+ enabled = false
206
+ # team_domain = "https://team.cloudflareaccess.com"
207
+ # audience = "cloudflare-access-application-aud"
208
+ ```
209
+
210
+ For a reverse-proxied deployment such as `https://happy.example.com`, the
211
+ listener can remain on `127.0.0.1`; set `server.public_url` and `jwt.issuer` to
212
+ the public HTTPS origin and set `server.trusted_proxy_hops` to the exact number
213
+ of trusted proxies in front of Happy (2). Binding `server.host = "0.0.0.0"` is
214
+ only necessary when another machine or container must connect directly.
215
+
216
+ ### Secrets and environment
217
+
218
+ Real process environment values take precedence over values loaded from the
219
+ private `.env` beside the selected TOML path. If no explicit config is selected,
220
+ that file is `./.happy2/.env`, whether or not `./.happy2/happy2.toml` exists.
221
+
222
+ For an `all` or `auth` server without configured JWT key files or environment
223
+ keys, Happy (2) generates a 3072-bit RS256 key pair and stores
224
+ `HAPPY2_JWT_PRIVATE_KEY_B64` and `HAPPY2_JWT_PUBLIC_KEY_B64` in that `.env`.
225
+ It also generates `HAPPY2_PASSWORD_PEPPER` when password auth is enabled and the
226
+ configured integration secret when missing. The file is created with mode
227
+ `0600`; back it up and keep it private. Replacing the JWT keys invalidates
228
+ existing session tokens, while replacing the password pepper prevents existing
229
+ password hashes from verifying. An `api`-only server must be given the matching
230
+ public key because it does not generate a signing pair.
231
+
232
+ Supported server and runner environment settings are:
233
+
234
+ | Variable | Purpose |
235
+ | --- | --- |
236
+ | `HAPPY2_CONFIG` | Selects a TOML file when `--config` is absent. |
237
+ | `HAPPY2_JWT_PRIVATE_KEY`, `HAPPY2_JWT_PUBLIC_KEY` | PEM keys; literal `\n` sequences are accepted. |
238
+ | `HAPPY2_JWT_PRIVATE_KEY_B64`, `HAPPY2_JWT_PUBLIC_KEY_B64` | Base64-encoded PEM keys used by generated local configuration. |
239
+ | `HAPPY2_PASSWORD_PEPPER` | Server-wide password pepper. |
240
+ | `HAPPY2_INTEGRATION_SECRET` | Default integration encryption secret; the variable name is configurable. |
241
+ | `RIG_HOME` | Absolute path for Happy's private Rig runtime. |
242
+ | `RIG_SERVER_SOCKET_PATH`, `RIG_SERVER_TOKEN_PATH`, `RIG_COMMAND` | Override omitted agent socket, token, and command fields. |
243
+ | `EMAIL_SMTP_HOST`, `EMAIL_SMTP_PORT`, `EMAIL_SMTP_USER`, `EMAIL_SMTP_PASSWORD` | Required SMTP credentials for magic-link auth. |
244
+ | `EMAIL_FROM` | Overrides `auth.magic_link.from`. |
245
+ | `HAPPY2_BACKEND_URL` | Backend origin for the separate `happy2 web` command. |
246
+ | `HAPPY2_WEB_HOST`, `HAPPY2_WEB_PORT`, `HAPPY2_WEB_TRUSTED_PROXY_HOPS` | Listener settings for `happy2 web`. |
247
+
248
+ `database.auth_token_env`, `security.integration_secret_env`, and each OIDC
249
+ provider's `client_secret_env` may name additional environment variables.
250
+
81
251
  ## Why Happy (2)?
82
252
 
83
253
  - **People and agents share the same workspace.** Conversations, threads,
@@ -0,0 +1,56 @@
1
+ apiVersion: v1
2
+ kind: ConfigMap
3
+ metadata:
4
+ name: happy2-web-config
5
+ data:
6
+ HAPPY2_BACKEND_URL: http://happy2-server:3000
7
+ HAPPY2_WEB_HOST: 0.0.0.0
8
+ HAPPY2_WEB_PORT: "3000"
9
+ HAPPY2_WEB_TRUSTED_PROXY_HOPS: "1"
10
+ ---
11
+ apiVersion: v1
12
+ kind: ConfigMap
13
+ metadata:
14
+ name: happy2-server-config
15
+ data:
16
+ happy2.toml: |
17
+ [server]
18
+ role = "all"
19
+ host = "0.0.0.0"
20
+ port = 3000
21
+ public_url = "{public_url}"
22
+ trusted_proxy_hops = 1
23
+
24
+ [database]
25
+ url = "file:/data/happy2.db"
26
+
27
+ [agents]
28
+ enabled = false
29
+ default_cwd = "/data/workspaces"
30
+
31
+ [files]
32
+ provider = "local"
33
+ directory = "/data/files"
34
+
35
+ [plugins]
36
+ directory = "/data/plugins"
37
+ host_api_host = "127.0.0.1"
38
+ host_api_port = 3001
39
+
40
+ [security]
41
+ integration_secret_env = "HAPPY2_INTEGRATION_SECRET"
42
+
43
+ [jwt]
44
+ issuer = "{public_url}"
45
+ audience = "happy2-desktop"
46
+ key_id = "kubernetes"
47
+ expiry_days = 30
48
+
49
+ [auth.password]
50
+ enabled = true
51
+
52
+ [auth.magic_link]
53
+ enabled = false
54
+
55
+ [auth.cloudflare_access]
56
+ enabled = false
@@ -0,0 +1,140 @@
1
+ apiVersion: apps/v1
2
+ kind: StatefulSet
3
+ metadata:
4
+ name: happy2-server
5
+ spec:
6
+ serviceName: happy2-server-headless
7
+ replicas: 1
8
+ selector:
9
+ matchLabels:
10
+ app: happy2-server
11
+ template:
12
+ metadata:
13
+ labels:
14
+ app: happy2-server
15
+ spec:
16
+ securityContext:
17
+ fsGroup: 1000
18
+ runAsGroup: 1000
19
+ runAsUser: 1000
20
+ seccompProfile:
21
+ type: RuntimeDefault
22
+ initContainers:
23
+ - name: copy-config
24
+ image: {version}
25
+ command:
26
+ - sh
27
+ - -c
28
+ - mkdir -p /data/config && cp /config-template/happy2.toml /data/config/happy2.toml
29
+ securityContext:
30
+ allowPrivilegeEscalation: false
31
+ capabilities:
32
+ drop:
33
+ - ALL
34
+ resources:
35
+ requests:
36
+ cpu: 25m
37
+ memory: 32Mi
38
+ limits:
39
+ cpu: 100m
40
+ memory: 128Mi
41
+ volumeMounts:
42
+ - name: happy2-data
43
+ mountPath: /data
44
+ - name: config-template
45
+ mountPath: /config-template
46
+ readOnly: true
47
+ containers:
48
+ - name: happy2-server
49
+ image: docker.korshakov.com/happy2:{version}
50
+ args:
51
+ - backend
52
+ - --config
53
+ - /data/config/happy2.toml
54
+ ports:
55
+ - containerPort: 3000
56
+ name: http
57
+ resources:
58
+ requests:
59
+ cpu: 250m
60
+ memory: 512Mi
61
+ limits:
62
+ cpu: "2"
63
+ memory: 2Gi
64
+ securityContext:
65
+ allowPrivilegeEscalation: false
66
+ capabilities:
67
+ drop:
68
+ - ALL
69
+ volumeMounts:
70
+ - name: happy2-data
71
+ mountPath: /data
72
+ livenessProbe:
73
+ httpGet:
74
+ path: /v0/health
75
+ port: http
76
+ initialDelaySeconds: 30
77
+ periodSeconds: 30
78
+ timeoutSeconds: 5
79
+ readinessProbe:
80
+ httpGet:
81
+ path: /v0/health
82
+ port: http
83
+ initialDelaySeconds: 10
84
+ periodSeconds: 10
85
+ timeoutSeconds: 3
86
+ startupProbe:
87
+ httpGet:
88
+ path: /v0/health
89
+ port: http
90
+ periodSeconds: 10
91
+ failureThreshold: 12
92
+ volumes:
93
+ - name: config-template
94
+ configMap:
95
+ name: happy2-server-config
96
+ volumeClaimTemplates:
97
+ - metadata:
98
+ name: happy2-data
99
+ spec:
100
+ accessModes:
101
+ - ReadWriteOnce
102
+ resources:
103
+ requests:
104
+ storage: 10Gi
105
+ ---
106
+ apiVersion: policy/v1
107
+ kind: PodDisruptionBudget
108
+ metadata:
109
+ name: happy2-server
110
+ spec:
111
+ minAvailable: 1
112
+ selector:
113
+ matchLabels:
114
+ app: happy2-server
115
+ ---
116
+ apiVersion: v1
117
+ kind: Service
118
+ metadata:
119
+ name: happy2-server-headless
120
+ spec:
121
+ clusterIP: None
122
+ selector:
123
+ app: happy2-server
124
+ ports:
125
+ - name: http
126
+ port: 3000
127
+ targetPort: http
128
+ ---
129
+ apiVersion: v1
130
+ kind: Service
131
+ metadata:
132
+ name: happy2-server
133
+ spec:
134
+ selector:
135
+ app: happy2-server
136
+ ports:
137
+ - name: http
138
+ port: 3000
139
+ targetPort: http
140
+ type: ClusterIP
@@ -0,0 +1,87 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: happy2-web
5
+ spec:
6
+ replicas: 3
7
+ selector:
8
+ matchLabels:
9
+ app: happy2-web
10
+ template:
11
+ metadata:
12
+ labels:
13
+ app: happy2-web
14
+ spec:
15
+ securityContext:
16
+ runAsGroup: 1000
17
+ runAsUser: 1000
18
+ seccompProfile:
19
+ type: RuntimeDefault
20
+ containers:
21
+ - name: happy2-web
22
+ image: docker.korshakov.com/happy2:{version}
23
+ args:
24
+ - web
25
+ envFrom:
26
+ - configMapRef:
27
+ name: happy2-web-config
28
+ ports:
29
+ - containerPort: 3000
30
+ name: http
31
+ resources:
32
+ requests:
33
+ cpu: 100m
34
+ memory: 128Mi
35
+ limits:
36
+ cpu: 500m
37
+ memory: 512Mi
38
+ livenessProbe:
39
+ httpGet:
40
+ path: /
41
+ port: http
42
+ initialDelaySeconds: 10
43
+ periodSeconds: 30
44
+ timeoutSeconds: 5
45
+ readinessProbe:
46
+ httpGet:
47
+ path: /
48
+ port: http
49
+ initialDelaySeconds: 5
50
+ periodSeconds: 10
51
+ timeoutSeconds: 3
52
+ startupProbe:
53
+ httpGet:
54
+ path: /
55
+ port: http
56
+ periodSeconds: 10
57
+ failureThreshold: 3
58
+ securityContext:
59
+ allowPrivilegeEscalation: false
60
+ capabilities:
61
+ drop:
62
+ - ALL
63
+ readOnlyRootFilesystem: true
64
+ runAsNonRoot: true
65
+ ---
66
+ apiVersion: policy/v1
67
+ kind: PodDisruptionBudget
68
+ metadata:
69
+ name: happy2-web
70
+ spec:
71
+ minAvailable: 2
72
+ selector:
73
+ matchLabels:
74
+ app: happy2-web
75
+ ---
76
+ apiVersion: v1
77
+ kind: Service
78
+ metadata:
79
+ name: happy2-web
80
+ spec:
81
+ selector:
82
+ app: happy2-web
83
+ ports:
84
+ - name: http
85
+ port: 3000
86
+ targetPort: http
87
+ type: ClusterIP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy2",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Happy (2) desktop web app and server bundle",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,8 @@
17
17
  "packages/happy2-server/happy2.example.toml",
18
18
  "packages/happy2-server/README.md",
19
19
  "packages/happy2-server/PLUGINS.md",
20
- "packages/happy2-server/Dockerfile"
20
+ "Dockerfile",
21
+ "deploy"
21
22
  ],
22
23
  "type": "module",
23
24
  "main": "./packages/happy2-server/dist/index.js",
@@ -14,6 +14,10 @@ npx happy2-server --config ./happy2.toml
14
14
  # Published all-in-one web app and API on http://127.0.0.1:3000
15
15
  npx happy2
16
16
 
17
+ # The same package can run only the backend or only the web gateway
18
+ npx happy2 backend --config ./happy2.toml
19
+ npx happy2 web --backend-url http://127.0.0.1:3001 --host 0.0.0.0 --port 3000
20
+
17
21
  # Install or remove automatic startup for the all-in-one app
18
22
  npx happy2 service start --config ./happy2.toml
19
23
  npx happy2 service stop
@@ -42,17 +46,35 @@ HTTP, uploads, and server-sent events. The configured `trusted_proxy_hops`
42
46
  continues to describe only proxies outside Happy (2); the private loopback hop is
43
47
  handled internally.
44
48
 
45
- Without a TOML file, the package starts an `all`-role app on `127.0.0.1:3000`
46
- with SQLite, password authentication, and generated JWT/pepper
47
- material. Database, files, generated secrets, agent workspaces, and Rig runtime
48
- state live under `.happy2` in the invoking directory. Add `.happy2` to the
49
- project's ignore rules and preserve it as private application state. The
50
- package starts its bundled `@slopus/rig` executable with a private Rig home under
49
+ `happy2 backend` starts the configured API directly without serving web assets.
50
+ `happy2 web` starts only the bundled SPA and a same-origin `/v0` reverse proxy;
51
+ it does not load a TOML file, open a database, or start Rig. Its backend must be
52
+ an HTTP(S) origin supplied by `--backend-url` or `HAPPY2_BACKEND_URL`. The web
53
+ listener can also be set with `--host`/`HAPPY2_WEB_HOST`,
54
+ `--port`/`HAPPY2_WEB_PORT`, and
55
+ `--trusted-proxy-hops`/`HAPPY2_WEB_TRUSTED_PROXY_HOPS`. The backend origin must
56
+ not contain credentials, a path, a query, or a fragment.
57
+
58
+ When the services are split, set the backend's `server.public_url` to the public
59
+ web origin. If the backend is reachable only through the Happy web gateway, set
60
+ its `server.trusted_proxy_hops` to `1`, because that gateway supplies one
61
+ sanitized forwarding hop. Configure the web gateway's trusted hop count for the
62
+ Ingress or load balancer in front of it.
63
+
64
+ The package starts an `all`-role app on `127.0.0.1:3000` with SQLite, password
65
+ authentication, and generated JWT/pepper material by default. It reads
66
+ `./.happy2/happy2.toml` when that file exists and recursively merges its fields
67
+ over the built-in defaults; the file may therefore contain only the values that
68
+ need to change. `--config /path/to/happy2.toml` or
69
+ `HAPPY2_CONFIG=/path/to/happy2.toml` selects a different partial configuration.
70
+
71
+ Database, files, generated secrets, agent workspaces, and Rig runtime state live
72
+ under `.happy2` in the invoking directory by default. Add `.happy2` to the
73
+ project's ignore rules and preserve it as private application state. The package
74
+ starts its bundled `@slopus/rig` executable with a private Rig home under
51
75
  `.happy2/rig` by default, containing configuration, runtime settings, session
52
76
  state, socket, and token. Set `RIG_HOME` to an absolute path to relocate it. The
53
- package never connects to the user's global Rig daemon. Provide
54
- `--config /path/to/happy2.toml` or
55
- `HAPPY2_CONFIG=/path/to/happy2.toml` to override the defaults.
77
+ package never connects to the user's global Rig daemon.
56
78
 
57
79
  `happy2 service start` keeps the all-in-one app running across restarts. On
58
80
  macOS it installs `~/Library/LaunchAgents/com.slopus.happy2.plist` without
@@ -197,13 +219,44 @@ environment variable named by `database.auth_token_env`.
197
219
  Build the image from the repository root:
198
220
 
199
221
  ```sh
200
- docker build -f packages/happy2-server/Dockerfile -t slopus/happy2 .
222
+ docker build -t slopus/happy2 .
201
223
  docker run --rm -p 3000:3000 -v "$PWD/happy2.toml:/app/happy2.toml:ro" slopus/happy2 --config /app/happy2.toml
202
224
  ```
203
225
 
204
226
  Mount a writable directory containing `happy2.toml` when using generated key
205
227
  material, because the adjacent `.env` file is the durable key store.
206
228
 
229
+ The image contains the same three runtime modes as the `happy2` executable. For
230
+ example, an independently deployed backend and web gateway can use:
231
+
232
+ ```sh
233
+ docker run --rm -p 3001:3001 -v "$PWD/happy2.toml:/app/happy2.toml:ro" \
234
+ slopus/happy2 backend --config /app/happy2.toml
235
+ docker run --rm -p 3000:3000 slopus/happy2 web \
236
+ --backend-url http://host.docker.internal:3001 --host 0.0.0.0
237
+ ```
238
+
239
+ ## Kubernetes deployment
240
+
241
+ The top-level manifests follow the deployment layout used by Happy. Replace
242
+ `{version}` in both workload files and `{public_url}` in the shared config, then
243
+ apply the configuration, server, and web resources:
244
+
245
+ ```sh
246
+ kubectl apply -f deploy/config.yaml
247
+ kubectl apply -f deploy/happy2-server.yaml
248
+ kubectl apply -f deploy/happy2-web.yaml
249
+ ```
250
+
251
+ The server runs as a one-replica StatefulSet because its SQLite database, files,
252
+ generated signing material, and plugin state share one `ReadWriteOnce` volume.
253
+ The checked-in configuration disables local coding-agent execution because the
254
+ pod does not receive a Docker or Podman sandbox provider. The stateless web
255
+ Deployment runs three replicas. Each serves the bundled SPA and proxies `/v0`,
256
+ including uploads and SSE, to the internal `happy2-server` Service. Add an
257
+ Ingress for the `happy2-web` Service according to the cluster's ingress and TLS
258
+ conventions.
259
+
207
260
  ## Authentication
208
261
 
209
262
  Choose exactly one of password, magic-link, OIDC, or Cloudflare Access in `happy2.toml`; startup
@@ -278,11 +331,13 @@ reuse Cloudflare's HttpOnly application cookie.
278
331
  On initial startup, external environment values win. If an auth-capable server
279
332
  has no JWT private key configured, it generates a 3072-bit RS256 key pair and
280
333
  adds base64-encoded `HAPPY2_JWT_PRIVATE_KEY_B64` and
281
- `HAPPY2_JWT_PUBLIC_KEY_B64` to the `.env` file beside its TOML file. It likewise
282
- adds `HAPPY2_PASSWORD_PEPPER` when password auth is enabled. Preserve that file
283
- as a secret; replacing it invalidates existing passwords and sessions. You may
284
- instead supply those variables through the deployment environment or mount PEM
285
- key files via the `jwt` config.
334
+ `HAPPY2_JWT_PUBLIC_KEY_B64` to the `.env` file beside the selected TOML path.
335
+ For the managed default path this is `./.happy2/.env`, whether or not
336
+ `./.happy2/happy2.toml` exists. It likewise adds `HAPPY2_PASSWORD_PEPPER` when
337
+ password auth is enabled. Preserve that file as a secret; replacing it
338
+ invalidates existing passwords and sessions. You may instead supply those
339
+ variables through the deployment environment or mount PEM key files via the
340
+ `jwt` config.
286
341
 
287
342
  Sessions are signed JWTs with a 30-day default lifetime and a stable `sid`.
288
343
  `POST /v0/auth/refresh` re-signs the same session ID and advances the database
@@ -0,0 +1,12 @@
1
+ import type { ServerConfig } from "./modules/config/type.js";
2
+ export interface RunningHappy2 extends AsyncDisposable {
3
+ /** Actual bound URL. This differs from config.server.publicUrl when port 0 is used. */
4
+ url: string;
5
+ close(): Promise<void>;
6
+ }
7
+ export interface BackendOptions {
8
+ logger?: boolean;
9
+ }
10
+ /** Starts only the Happy (2) backend, without serving or proxying the web application. */
11
+ export declare function startBackendHappy2(config: ServerConfig, options?: BackendOptions): Promise<RunningHappy2>;
12
+ //# sourceMappingURL=backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../sources/backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,MAAM,WAAW,aAAc,SAAQ,eAAe;IAClD,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,0FAA0F;AAC1F,wBAAsB,kBAAkB,CACpC,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,cAAmB,GAC7B,OAAO,CAAC,aAAa,CAAC,CAsCxB"}