happy2 0.0.16 → 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.
- package/Dockerfile +30 -0
- package/README.md +170 -0
- package/deploy/config.yaml +56 -0
- package/deploy/happy2-server.yaml +140 -0
- package/deploy/happy2-web.yaml +87 -0
- package/package.json +3 -2
- package/packages/happy2-server/README.md +70 -15
- package/packages/happy2-server/dist/backend.d.ts +12 -0
- package/packages/happy2-server/dist/backend.d.ts.map +1 -0
- package/packages/happy2-server/dist/backend.js +48 -0
- package/packages/happy2-server/dist/backend.js.map +1 -0
- package/packages/happy2-server/dist/cli.js +6 -25
- package/packages/happy2-server/dist/cli.js.map +1 -1
- package/packages/happy2-server/dist/index.d.ts +2 -0
- package/packages/happy2-server/dist/index.d.ts.map +1 -1
- package/packages/happy2-server/dist/index.js +2 -0
- package/packages/happy2-server/dist/index.js.map +1 -1
- package/packages/happy2-server/dist/modules/agents/daemon.d.ts +4 -0
- package/packages/happy2-server/dist/modules/agents/daemon.d.ts.map +1 -1
- package/packages/happy2-server/dist/modules/agents/daemon.js +40 -0
- package/packages/happy2-server/dist/modules/agents/daemon.js.map +1 -1
- package/packages/happy2-server/dist/modules/config/defaults.d.ts +4 -4
- package/packages/happy2-server/dist/modules/config/defaults.d.ts.map +1 -1
- package/packages/happy2-server/dist/modules/config/defaults.js +8 -8
- package/packages/happy2-server/dist/modules/config/defaults.js.map +1 -1
- package/packages/happy2-server/dist/modules/config/loader.d.ts +2 -2
- package/packages/happy2-server/dist/modules/config/loader.d.ts.map +1 -1
- package/packages/happy2-server/dist/modules/config/loader.js +83 -73
- package/packages/happy2-server/dist/modules/config/loader.js.map +1 -1
- package/packages/happy2-server/dist/modules/config/loader.test.js +35 -0
- package/packages/happy2-server/dist/modules/config/loader.test.js.map +1 -1
- package/packages/happy2-server/dist/modules/config/runtime.d.ts +1 -1
- package/packages/happy2-server/dist/modules/config/runtime.d.ts.map +1 -1
- package/packages/happy2-server/dist/modules/config/runtime.js +17 -4
- package/packages/happy2-server/dist/modules/config/runtime.js.map +1 -1
- package/packages/happy2-server/dist/modules/config/runtime.test.d.ts +2 -0
- package/packages/happy2-server/dist/modules/config/runtime.test.d.ts.map +1 -0
- package/packages/happy2-server/dist/modules/config/runtime.test.js +99 -0
- package/packages/happy2-server/dist/modules/config/runtime.test.js.map +1 -0
- package/packages/happy2-server/dist/runner.js +29 -12
- package/packages/happy2-server/dist/runner.js.map +1 -1
- package/packages/happy2-server/dist/runnerCommand.d.ts +21 -0
- package/packages/happy2-server/dist/runnerCommand.d.ts.map +1 -0
- package/packages/happy2-server/dist/runnerCommand.js +97 -0
- package/packages/happy2-server/dist/runnerCommand.js.map +1 -0
- package/packages/happy2-server/dist/runnerCommand.test.d.ts +2 -0
- package/packages/happy2-server/dist/runnerCommand.test.d.ts.map +1 -0
- package/packages/happy2-server/dist/runnerCommand.test.js +59 -0
- package/packages/happy2-server/dist/runnerCommand.test.js.map +1 -0
- package/packages/happy2-server/dist/standalone.d.ts +2 -5
- package/packages/happy2-server/dist/standalone.d.ts.map +1 -1
- package/packages/happy2-server/dist/standalone.js +14 -65
- package/packages/happy2-server/dist/standalone.js.map +1 -1
- package/packages/happy2-server/dist/web/assets/index-B51jYSrY.css +2 -0
- package/packages/happy2-server/dist/web/assets/index-szUjvBO1.js +39 -0
- package/packages/happy2-server/dist/web/index.html +2 -2
- package/packages/happy2-server/dist/web.d.ts +12 -0
- package/packages/happy2-server/dist/web.d.ts.map +1 -0
- package/packages/happy2-server/dist/web.js +89 -0
- package/packages/happy2-server/dist/web.js.map +1 -0
- package/packages/happy2-server/happy2.example.toml +3 -5
- package/packages/happy2-server/Dockerfile +0 -18
- package/packages/happy2-server/dist/web/assets/index-BxtJtZpt.js +0 -39
- package/packages/happy2-server/dist/web/assets/index-m8C1tSYV.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.
|
|
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
|
-
"
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
|
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 -
|
|
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
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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"}
|