Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ dist
out
.turbo
.git
.github
.env
.env.*
!.env.example
!.env.docker.example
coverage
data
*.log
25 changes: 25 additions & 0 deletions .env.docker.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 实验版本默认跟随仓库主开发分支。需要固定版本时改为对应的版本标签。
NOTEGEN_SERVER_IMAGE=ghcr.io/codexu/note-gen-server:edge

# PostgreSQL 密码。推荐使用 `openssl rand -hex 32` 生成,避免 URL 保留字符。
POSTGRES_PASSWORD=replace-with-a-random-hex-password

# 登录令牌和数据库内敏感配置的加密密钥。上线后不要随意更换。
AUTH_SECRET=replace-with-at-least-32-random-characters

# NoteGen 客户端实际访问的公开地址,不要带路径或末尾斜杠。
# 本机实验可以使用 http://127.0.0.1:3789;远程部署必须使用 HTTPS 域名。
PUBLIC_BASE_URL=http://127.0.0.1:3789

# 默认只监听宿主机回环地址,由 Caddy/Nginx/Traefik 反向代理。
# 仅在受信局域网实验时改为 0.0.0.0,并同时配置防火墙。
SERVER_BIND_ADDRESS=127.0.0.1
SERVER_PORT=3789

# 只有外部无法绕过受信反向代理直连服务端口时才设置为 true。
TRUST_PROXY=false

LOG_LEVEL=info
METRICS_ENABLED=false
METRICS_TOKEN=
OPENAPI_ENABLED=false
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ PUBLIC_BASE_URL=https://sync.example.com
# 登录令牌与数据库内敏感配置的加密密钥,至少 32 个随机字符;上线后不要更换。
AUTH_SECRET=replace-with-at-least-32-random-characters

# 部署模式、服务名称、管理员账号和注册策略在首次打开 Web 页面时设置;
# 服务名称、管理员账号和注册策略在首次打开 Web 页面时设置;
# 业务限额、保留周期和邮件配置使用安全默认值,之后在 Web 管理后台维护。
# SMTP 默认关闭且不是安装依赖;需要邀请邮件或密码找回时再由管理员配置。
# 空实例无需安装密钥,请先完成 Web 安装,再开放公网入口。
76 changes: 76 additions & 0 deletions .github/workflows/publish-container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Publish container image

on:
push:
branches:
- feat/sync-foundation
tags:
- "v*"
workflow_dispatch:

permissions:
contents: read
packages: write
attestations: write
id-token: write

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/note-gen-server

jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Generate image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=edge,enable=${{ github.ref == 'refs/heads/feat/sync-foundation' }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-
labels: |
org.opencontainers.image.title=NoteGen Server
org.opencontainers.image.description=Self-hosted synchronization server and account portal for NoteGen
org.opencontainers.image.source=https://github.com/${{ github.repository }}

- name: Build and push image
id: push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: true
sbom: true

- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ out/
.env
.env.*
!.env.example
!.env.docker.example
coverage/
data/
*.log
Expand Down
73 changes: 73 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 参与 NoteGen Server 开发

NoteGen Server 处于实验阶段,欢迎通过 Issue、文档改进和代码贡献共同完善部署、同步协议、服务端 API 与账号管理 Web。

## 选择运行方式

### 源码开发

适合频繁修改、调试和提交代码。需要 Node.js 22 或更高版本、pnpm 10.20.0 和 PostgreSQL 17。

```bash
git clone https://github.com/codexu/note-gen-server.git
cd note-gen-server
pnpm install
cp .env.example .env
pnpm dev
```

首次创建本地数据库:

```bash
createuser notegen
createdb -O notegen notegen
```

修改 `.env` 中的 `DATABASE_URL`、`AUTH_SECRET` 和 `PUBLIC_BASE_URL`。API 默认监听 `3789`,开发态 Web 默认监听 `3790`。

### 本地容器构建

适合复现 Docker 部署问题或验证镜像构建:

```bash
cp .env.docker.example .env
docker compose -f compose.yaml -f compose.dev.yaml up -d --build
```

完整说明见 [Docker Compose 部署](docs/deployment/docker.md)。

## 仓库结构

```text
apps/server Fastify API、认证、同步和运维 CLI
apps/web 静态导出的账号管理 Web
packages/contracts Web 与服务端共享的协议类型
docs 协议、部署和运维文档
```

## 修改边界

- 数据库结构变化必须通过 Drizzle migration 表达,不手工改 migration journal。
- 同步协议和 capabilities 采用显式版本与能力协商,不能只根据服务端版本猜测行为。
- 不把访问令牌、密码、同步密文、封装密钥或真实 `.env` 提交到仓库。
- Web 使用静态导出,生产环境由 Fastify 同源托管。
- 新配置项需要同步更新 `.env.example`、部署文档和能力说明。

## 提交前检查

项目当前提供以下检查命令,按改动范围选择执行:

```bash
pnpm typecheck
pnpm test
pnpm build
pnpm db:migrate
```

涉及真实同步链路时,可在服务启动后运行:

```bash
INTEGRATION_BASE_URL=http://127.0.0.1:3789 pnpm test:integration
```

提交 Pull Request 时请说明问题、实现方式、兼容性影响,以及自己执行过的验证。涉及数据库迁移、协议、安全边界或备份恢复时,同时说明升级与回退约束。
66 changes: 66 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
FROM node:24-bookworm-slim AS base

ENV PNPM_HOME=/pnpm
ENV PATH=$PNPM_HOME:$PATH

RUN apt-get update \
&& apt-get install -y --no-install-recommends g++ make python3 \
&& rm -rf /var/lib/apt/lists/* \
&& corepack enable \
&& corepack prepare pnpm@10.20.0 --activate

WORKDIR /app

FROM base AS dependencies

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
COPY apps/server/package.json apps/server/package.json
COPY apps/web/package.json apps/web/package.json
COPY packages/contracts/package.json packages/contracts/package.json

RUN pnpm install --frozen-lockfile

FROM dependencies AS builder

COPY . .

RUN pnpm build

FROM base AS production-dependencies

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
COPY apps/server/package.json apps/server/package.json
COPY apps/web/package.json apps/web/package.json
COPY packages/contracts/package.json packages/contracts/package.json

RUN pnpm install --prod --frozen-lockfile

FROM node:24-bookworm-slim AS runner

ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3789

LABEL org.opencontainers.image.source="https://github.com/codexu/note-gen-server"
LABEL org.opencontainers.image.description="Self-hosted synchronization server and account portal for NoteGen"

WORKDIR /app

COPY --from=production-dependencies /app/node_modules ./node_modules
COPY --from=production-dependencies /app/apps/server/node_modules ./apps/server/node_modules
COPY --from=builder /app/apps/server/package.json ./apps/server/package.json
COPY --from=builder /app/apps/server/dist ./apps/server/dist
COPY --from=builder /app/apps/server/drizzle ./apps/server/drizzle
COPY --from=builder /app/apps/web/out ./apps/web/out

RUN mkdir -p /app/data/blobs /app/data/backups \
&& chown -R node:node /app

USER node

EXPOSE 3789

HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD ["node", "-e", "fetch('http://127.0.0.1:3789/health/ready').then((response) => { if (!response.ok) process.exit(1) }).catch(() => process.exit(1))"]

CMD ["node", "apps/server/dist/entrypoint.js"]
51 changes: 49 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,52 @@
# NoteGen Sync Server

NoteGen Sync Server 是 NoteGen 客户端使用的同步服务,可用于自行部署,也可用于运营官方托管服务。
NoteGen Sync Server 是 NoteGen 客户端使用的免费同步服务。官方公共测试服务和用户自行部署使用同一套独立实例运行方式。

> 自托管服务目前处于实验阶段,部署方式、配置和数据库结构仍可能变化。请保留 NoteGen 本地 Markdown,不要把实验实例作为重要数据的唯一副本。

项目包含同步 API 和账号管理 Web。Web 负责注册、登录、设备关联、同步统计和账号管理,不包含 Markdown 编辑器、AI 功能或多人协作功能。

## 开始使用

### Docker Compose(推荐体验)

适合快速搭建个人测试实例。官方 Compose 默认拉取 GHCR 的 `edge` 镜像,并启动 NoteGen Server 与 PostgreSQL 17:

```bash
git clone https://github.com/codexu/note-gen-server.git
cd note-gen-server
cp .env.docker.example .env
# 编辑 .env,设置 POSTGRES_PASSWORD、AUTH_SECRET 和 PUBLIC_BASE_URL
docker compose up -d
docker compose ps
```

详细的首次初始化、HTTPS、日志和数据说明见 [Docker Compose 部署](docs/deployment/docker.md)。

### 从源码运行(开发者)

适合调试服务端、修改功能和提交贡献。需要 Node.js 22 或更高版本、pnpm 10.20.0 和 PostgreSQL 17:

```bash
git clone https://github.com/codexu/note-gen-server.git
cd note-gen-server
createuser notegen
createdb -O notegen notegen
pnpm install
cp .env.example .env
pnpm dev
```

贡献约定、仓库结构与本地容器构建方法见 [CONTRIBUTING.md](CONTRIBUTING.md)。

## 项目定位

- 项目免费且不以商业化为目标,不提供付费订阅、套餐或商业 SLA。
- 官方只提供供社区体验的公共测试实例;它与用户自行部署使用相同代码和运行方式。
- 每个实例由自己的管理员负责注册策略、邀请、数据保留、备份和滥用处理。
- 邮件不是基础依赖。SMTP 默认关闭;未配置时仍可使用账号密码和一次性邀请链接。
- 客户端应继续保留本地数据,公共测试实例不应被视为唯一备份。

## Monorepo 结构

```text
Expand All @@ -15,11 +58,15 @@ apps/web Next.js + shadcn/ui 账号管理页面

当前项目已完成首个可部署的服务端基线,详细方案见:

- [Docker Compose 部署](docs/deployment/docker.md)
- [容器镜像发布](docs/deployment/container-publishing.md)
- [参与服务端开发](CONTRIBUTING.md)
- [同步服务技术规格](docs/sync-server-spec.md)
- [本地运行与运维](docs/operations.md)
- [NoteGen 客户端接入协议](docs/client-protocol.md)
- [NoteGen 接入与配置同步体验](docs/notegen-integration.md)
- [部署后的个人验收用例](docs/self-test.md)
- [公共测试实例上线清单](docs/production-checklist.md)
- [账号服务开发计划总览](docs/account-service/README.md)

## 已确定技术栈
Expand Down Expand Up @@ -70,7 +117,7 @@ createuser notegen
createdb -O notegen notegen
```

复制并填写本地配置。`DATABASE_URL`、`AUTH_SECRET` 与 `SETUP_TOKEN` 必须设置为自己的值:
复制并填写本地配置。`DATABASE_URL`、`AUTH_SECRET` 与 `PUBLIC_BASE_URL` 必须设置为自己的值:

```bash
cp .env.example .env
Expand Down
12 changes: 12 additions & 0 deletions apps/server/drizzle/0002_amazing_proemial_gods.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE "sync_device_cursors" (
"workspace_id" uuid NOT NULL,
"device_id" uuid NOT NULL,
"acknowledged_sequence" bigint DEFAULT 0 NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "sync_device_cursors_workspace_id_device_id_pk" PRIMARY KEY("workspace_id","device_id")
);
--> statement-breakpoint
ALTER TABLE "sync_commands" ADD COLUMN "sync_epoch" uuid;--> statement-breakpoint
ALTER TABLE "sync_device_cursors" ADD CONSTRAINT "sync_device_cursors_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "sync_device_cursors" ADD CONSTRAINT "sync_device_cursors_device_id_devices_id_fk" FOREIGN KEY ("device_id") REFERENCES "public"."devices"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "sync_device_cursors_updated_idx" ON "sync_device_cursors" USING btree ("workspace_id","updated_at");
10 changes: 10 additions & 0 deletions apps/server/drizzle/0003_amused_tag.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE "rate_limit_buckets" (
"scope" text NOT NULL,
"rate_key" text NOT NULL,
"window_start" timestamp with time zone NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"hits" integer DEFAULT 0 NOT NULL,
CONSTRAINT "rate_limit_buckets_scope_rate_key_window_start_pk" PRIMARY KEY("scope","rate_key","window_start")
);
--> statement-breakpoint
CREATE INDEX "rate_limit_buckets_expiry_idx" ON "rate_limit_buckets" USING btree ("expires_at");
Loading
Loading