- activity_log table (sqlite + pg, structurally identical; schema-parity covers it). Plain-text columns, no FKs — an append-only trail that must outlive the projects/users it references, so target_label snapshots a human-readable name at write time. - ActivityRepository: record() (fire-and-forget, never breaks the caller) + list() (org-scoped, actor/action filters, keyset pagination) + listActors(). - Recording wired into the mutations worth tracking: project create/archive/restore/domain, audit start, team user create/remove/ password-reset, invitation sent. - getActivityLog / getActivityActors server functions (owner/admin gated) + Settings → Activity tab (ActivityLogView: filter by user & action, load more). - Migration: drizzle/0045_*, drizzle-pg/0023_*. The pipeline does not run migrations — see docs/SELF_HOSTING_TEAM_MODE.md step 5 for the one-time `drizzle-kit migrate` on the server. Writes fail silently until the table exists. tsc / oxlint / knip clean. New ActivityRepository.test.ts (4) + schema-parity picks up the new table; suite otherwise unchanged (pre-existing samSkills CRLF failure only). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
99 lines
3.6 KiB
Markdown
99 lines
3.6 KiB
Markdown
# Self-hosting with team logins (`AUTH_MODE=team`)
|
|
|
|
`team` mode turns the app into a single shared workspace with individual
|
|
email/password logins. There is no billing, no Google login, no email
|
|
verification, and no self-serve signup — the owner provisions every account.
|
|
|
|
Use it when you want your team on one internal instance and need to see who did
|
|
what. For a public multi-tenant product, use `hosted` instead.
|
|
|
|
## What you get
|
|
|
|
- Email/password sign-in for each teammate.
|
|
- One shared workspace (organization). Everyone works in the same projects.
|
|
- Roles: **owner** (you), **admin** (full access except billing/owner actions),
|
|
**member** (research + view).
|
|
- Two ways to add people:
|
|
- **Settings → Users** — create an account with a temporary password directly.
|
|
- **Settings → Organization** — send an email invite link (needs no email
|
|
provider config to create the invite; the link is shown in the UI).
|
|
|
|
## 1. Set environment variables
|
|
|
|
Edit `.env` in the deployment directory (for the pm2 setup that is
|
|
`/home/dev/DOCKER/OPEN-SEO/open-seo/.env`):
|
|
|
|
```sh
|
|
AUTH_MODE=team
|
|
BETTER_AUTH_URL=https://seo.thedomainnest.com # the exact public origin, https
|
|
BETTER_AUTH_SECRET=<64 hex chars> # openssl rand -hex 32
|
|
```
|
|
|
|
`AUTH_MODE` is compiled into the browser bundle, so it must be present when
|
|
`vite build` runs — not only at runtime. Vite reads this same `.env` at build
|
|
time, so one edit covers both as long as the build runs in this directory.
|
|
|
|
Keep the existing `DATABASE_URL`, `DATAFORSEO_API_KEY`, etc.
|
|
|
|
## 2. Rebuild and restart
|
|
|
|
If your pipeline builds on push, commit/push and let it run. To do it by hand:
|
|
|
|
```sh
|
|
cd /home/dev/DOCKER/OPEN-SEO/open-seo
|
|
pnpm install --frozen-lockfile
|
|
pnpm build
|
|
pm2 restart OPEN-SEO --update-env
|
|
```
|
|
|
|
Confirm config: `curl -s http://127.0.0.1:3001/api/health | jq .checks.auth`
|
|
should report `team`.
|
|
|
|
## 3. Create the owner account
|
|
|
|
Open the site. With `team` mode on and no users yet, `/sign-in` redirects to
|
|
`/setup`. Fill in name, email, password → **Create workspace**. You are signed
|
|
in as the owner.
|
|
|
|
`/setup` disables itself the moment the owner exists. If you ever need to reset,
|
|
delete all rows from `user` (and `member`, `account`, `session`) in Postgres and
|
|
reload.
|
|
|
|
## 4. Add your team
|
|
|
|
**Settings → Users → Add user.** Enter their email, name, a temporary password,
|
|
and a role. Share the password out-of-band; they change it after signing in
|
|
(Settings → Personal, once that lands) or you reset it from the same screen.
|
|
|
|
Removing a user drops their membership and signs them out everywhere. Their
|
|
`user` row is kept so past activity still attributes correctly; re-adding them
|
|
issues a fresh password.
|
|
|
|
## 5. Activity log
|
|
|
|
**Settings → Activity** (owner/admin only) shows who did what — projects
|
|
created/archived, site audits started, users added/removed, invitations sent —
|
|
filterable by user and action.
|
|
|
|
It writes to a new `activity_log` table, so **run the migration once** after
|
|
deploying:
|
|
|
|
```sh
|
|
cd /home/dev/DOCKER/OPEN-SEO/open-seo
|
|
export $(grep -E '^DATABASE_URL=' .env | xargs)
|
|
pnpm exec drizzle-kit migrate --config drizzle-pg.config.ts
|
|
pm2 restart OPEN-SEO
|
|
```
|
|
|
|
Until the table exists, the writes fail silently (logged to the console) and the
|
|
app keeps working; the Activity tab just shows nothing.
|
|
|
|
## Notes
|
|
|
|
- Password reset by email is not available in `team` mode. The owner/admins
|
|
reset passwords from Settings → Users.
|
|
- The MCP server and its OAuth flow are hosted-only for now; `team` deployments
|
|
serve the app UI only.
|
|
- Rolling back: set `AUTH_MODE=local_noauth`, rebuild, restart. Existing users
|
|
and data stay in the database, just unused.
|