From 9eac06bd477dbde7fcc1970255f9f7dbc02cb66f Mon Sep 17 00:00:00 2001 From: eezd Date: Tue, 27 Oct 2026 14:15:57 +0800 Subject: [PATCH] ci(release): publish Linux binaries images and demo in one workflow --- .github/release-notes.md | 21 ++++ .github/workflows/release.yml | 206 ++++++++++++++++++++++++++++++++++ README.md | 12 +- README.zh-CN.md | 12 +- deploy/Dockerfile.release | 12 ++ docs/deployment.md | 17 ++- docs/deployment.zh-CN.md | 17 ++- docs/development.md | 10 ++ docs/development.zh-CN.md | 10 ++ scripts/smoke-release.py | 102 +++++++++++++++++ 10 files changed, 407 insertions(+), 12 deletions(-) create mode 100644 .github/release-notes.md create mode 100644 .github/workflows/release.yml create mode 100644 deploy/Dockerfile.release create mode 100644 scripts/smoke-release.py diff --git a/.github/release-notes.md b/.github/release-notes.md new file mode 100644 index 0000000..811eb70 --- /dev/null +++ b/.github/release-notes.md @@ -0,0 +1,21 @@ +## RawMark 0.1.0 + +The first public release of RawMark, a single-user, self-hosted Markdown workspace. + +- Markdown Live Preview and source editing, with tables, tasks, math, Mermaid, and Atom One Dark code highlighting. +- Folders, search, tabs, outlines, image management, import/export, and a recoverable recycle bin. +- English and Simplified Chinese, light/dark themes, local fonts, customizable shortcuts, and frontend plugins. +- Bearer authentication, API tokens, full-instance backups, and seven editable example notes on a fresh installation. + +### Try or install + +- [Online editor demo](https://eezd.github.io/RawMark/) — edits stay in the current page and reset on refresh. +- Docker: `ghcr.io/eezd/rawmark:0.1.0` (Linux amd64/arm64). +- Native Linux: download the matching archive, verify it using `SHA256SUMS`, extract it, and follow the [deployment guide](https://github.com/eezd/RawMark/blob/v0.1.0/docs/deployment.md). +- [English README](https://github.com/eezd/RawMark/blob/v0.1.0/README.md) · [中文说明](https://github.com/eezd/RawMark/blob/v0.1.0/README.zh-CN.md). + +Existing installations must preserve their complete data directory and mount configuration. Back up before upgrading. No historical cleanup SQL is required for a fresh installation. Public sharing and multi-user collaboration are not included. + +--- + +RawMark 首个公开版本:支持 Markdown 即时预览、笔记与图片管理、搜索与多标签、明暗主题、中英文、完整备份及 Bearer 认证。提供 Linux 双架构程序与 Docker 镜像,以及无需登录的编辑器在线预览。已有实例升级前请备份并保留原有数据目录及挂载配置。 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..cfb6d38 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,206 @@ +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + check: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Validate release tag + env: + RELEASE_TAG: ${{ github.ref_name }} + run: | + version="$(cat VERSION)" + [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] + test "$RELEASE_TAG" = "v$version" + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: '24' + - name: Install frontend dependencies + run: | + corepack enable + corepack prepare pnpm@12.11.2 --activate + pnpm -C web install --frozen-lockfile --registry=https://registry.npmjs.org + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: go.mod + - name: Check and build + run: | + pnpm -C web lint:check + pnpm -C web typecheck + pnpm -C web test + pnpm -C web build + pnpm -C web build:demo + go vet -tags dev ./... + go test ./internal/... + go test -tags dev ./web ./cmd/rawmark + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: frontend + path: web/dist + if-no-files-found: error + retention-days: 3 + - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: web/dist-demo + + linux: + needs: check + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + runner: ubuntu-24.04 + - arch: arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: go.mod + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: frontend + path: web/dist + - name: Build and smoke-test native binary + run: | + mkdir -p package/licenses + CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.releaseVersion=$(cat VERSION)" -o package/rawmark ./cmd/rawmark + go test ./cmd/rawmark ./web + python3 scripts/smoke-release.py package/rawmark + - name: Package download + env: + ARCH: ${{ matrix.arch }} + run: | + cp LICENSE package/ + cp web/LICENSE package/licenses/vue3-starter-MIT.txt + cp web/src/common/appearance/fonts/*-OFL.txt package/licenses/ + cp web/src/common/appearance/fonts/maple/LICENSE.txt package/licenses/Maple-Mono-OFL.txt + mkdir downloads + tar -C package -czf "downloads/rawmark_$(cat VERSION)_linux_$ARCH.tar.gz" rawmark LICENSE licenses + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: linux-${{ matrix.arch }} + path: downloads/*.tar.gz + if-no-files-found: error + retention-days: 3 + + image: + needs: linux + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: read + packages: write + outputs: + digest: ${{ steps.build.outputs.digest }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + pattern: linux-* + path: downloads + merge-multiple: true + - name: Prepare tested binaries + run: | + mkdir -p image/linux/amd64 image/linux/arm64 image/data + for arch in amd64 arm64; do + tar -xzf "downloads/rawmark_$(cat VERSION)_linux_$arch.tar.gz" -C "image/linux/$arch" + done + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Publish version image + id: build + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: image + file: deploy/Dockerfile.release + platforms: linux/amd64,linux/arm64 + push: true + tags: ghcr.io/eezd/rawmark:${{ github.ref_name }} + labels: | + org.opencontainers.image.source=https://github.com/eezd/RawMark + org.opencontainers.image.version=${{ github.ref_name }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.licenses=MIT + - name: Smoke-test packaged image + env: + RELEASE_TAG: ${{ github.ref_name }} + run: | + image="ghcr.io/eezd/rawmark:$RELEASE_TAG" + test "$(docker run --rm "$image" --version)" = "RawMark $(cat VERSION)" + trap 'docker rm -f rawmark-smoke; docker volume rm rawmark-smoke-data' EXIT + docker run -d --name rawmark-smoke -v rawmark-smoke-data:/data -p 127.0.0.1:21876:8080 "$image" + for attempt in {1..60}; do + status="$(docker inspect --format '{{.State.Health.Status}}' rawmark-smoke)" + if [ "$status" = healthy ]; then break; fi + if [ "$status" = unhealthy ]; then docker logs rawmark-smoke; exit 1; fi + sleep 1 + done + test "$status" = healthy + test "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:21876/api/v1/tree)" = 401 + docker exec rawmark-smoke /rawmark healthcheck + + pages: + needs: image + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + - uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 + id: deployment + + release: + needs: [image, pages] + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: write + packages: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + pattern: linux-* + path: downloads + merge-multiple: true + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + - uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Publish release and stable image tags + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ github.ref_name }} + IMAGE_DIGEST: ${{ needs.image.outputs.digest }} + run: | + (cd downloads && sha256sum *.tar.gz > SHA256SUMS) + gh release create "$RELEASE_TAG" downloads/* --verify-tag --title "RawMark $RELEASE_TAG" --notes-file .github/release-notes.md --latest + docker buildx imagetools create --tag "ghcr.io/eezd/rawmark:$(cat VERSION)" --tag ghcr.io/eezd/rawmark:latest "ghcr.io/eezd/rawmark@$IMAGE_DIGEST" diff --git a/README.md b/README.md index 1155267..0ccff82 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@

A self-hosted Markdown workspace for your personal notes. Your files, your server, ready in your browser.

English · 简体中文

+ Try the editor · Quick start · User guide · Deployment · @@ -36,20 +37,21 @@ RawMark is built for **single-user, self-hosted use**. It currently does not off ## Quick start -With Git and Docker installed, build and run locally: +With Docker installed: ```sh -git clone https://github.com/eezd/RawMark.git -cd RawMark -docker build -t rawmark . docker run -d --name rawmark --restart unless-stopped \ -p 127.0.0.1:21000:8080 \ -v rawmark-data:/data \ - rawmark + ghcr.io/eezd/rawmark:0.1.0 ``` Open **http://localhost:21000** and set your password on first use. A new, empty instance starts with seven English example notes; edit or delete them and begin writing. Data lives in the Docker volume `rawmark-data` and persists when you recreate the container with the same volume. +Linux amd64 and arm64 are supported. Prefer a native program? Download it from [Releases](https://github.com/eezd/RawMark/releases), or build from source using the deployment guide. + +[Try the online editor](https://eezd.github.io/RawMark/) without installing anything. Demo edits are temporary and reset on refresh; the demo does not provide a server-backed workspace. + These commands listen on localhost only. For a remote server, complete the initial setup before configuring HTTPS access. See the [deployment guide](docs/deployment.md) for Docker Compose, existing note folders, backups, and upgrades. ## Documentation diff --git a/README.zh-CN.md b/README.zh-CN.md index 3bd573f..b771ed7 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -5,6 +5,7 @@

自托管的个人 Markdown 笔记工作台。文件属于你,打开浏览器就能继续写。

English · 简体中文

+ 在线体验 · 快速开始 · 使用指南 · 部署文档 · @@ -36,20 +37,21 @@ RawMark 面向**单用户、自托管**场景。目前不提供多人协作或 ## 快速开始 -需要 Git 和 Docker。在本地构建镜像并启动: +安装 Docker 后运行: ```sh -git clone https://github.com/eezd/RawMark.git -cd RawMark -docker build -t rawmark . docker run -d --name rawmark --restart unless-stopped \ -p 127.0.0.1:21000:8080 \ -v rawmark-data:/data \ - rawmark + ghcr.io/eezd/rawmark:0.1.0 ``` 打开 **http://localhost:21000**,首次访问设置登录密码。全新空实例会提供 7 篇英文示例笔记,可以自由编辑或删除,然后开始记录自己的内容。数据保存在 Docker 卷 `rawmark-data` 中,重建容器不会丢失。 +支持 Linux amd64 和 arm64。也可以在 [Releases](https://github.com/eezd/RawMark/releases) 下载原生程序,或按照部署文档从源码构建。 + +无需安装即可[在线体验编辑器](https://eezd.github.io/RawMark/)。Demo 修改仅在当前页面保留,刷新恢复示例,不提供服务端工作区。 + 以上命令仅监听本机。部署到服务器时,请先完成初始化,再配置 HTTPS 访问;Docker Compose、已有笔记目录接入、备份与升级见[部署文档](docs/deployment.zh-CN.md)。 ## 文档 diff --git a/deploy/Dockerfile.release b/deploy/Dockerfile.release new file mode 100644 index 0000000..166146b --- /dev/null +++ b/deploy/Dockerfile.release @@ -0,0 +1,12 @@ +# Package the exact native binaries already tested by the release workflow. +FROM gcr.io/distroless/static:nonroot +ARG TARGETARCH +COPY linux/${TARGETARCH}/rawmark /rawmark +COPY linux/${TARGETARCH}/LICENSE /LICENSE +COPY linux/${TARGETARCH}/licenses /licenses +COPY --chown=65532:65532 data/ /data/ +ENV RAWMARK_DATA=/data +EXPOSE 8080 +VOLUME /data +HEALTHCHECK --interval=30s --timeout=5s --start-period=5s CMD ["/rawmark", "healthcheck"] +ENTRYPOINT ["/rawmark"] diff --git a/docs/deployment.md b/docs/deployment.md index ba88aeb..75fff0d 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -4,7 +4,7 @@ [Project home](../README.md) · [User guide](guide.md) · [Development guide](development.md) -RawMark embeds the frontend in its Go service and uses SQLite and the filesystem at runtime. No separate database service is needed. Docker images are currently built from source; the commands below do not depend on a prepublished image registry. +RawMark embeds the frontend in its Go service and uses SQLite and the filesystem at runtime. No separate database service is needed. You can use a release image or build from source; the source-build commands below remain available. ## Docker quick start @@ -52,6 +52,21 @@ This configuration publishes port **21000** on all network interfaces by default Do not point the quick-start container and a Compose instance at the same database or vault concurrently. Stop the old instance, back up, and verify data locations before switching deployment methods. +## Prebuilt images and native programs + +Release images are available as `ghcr.io/eezd/rawmark:0.1.0` for Linux amd64 and arm64. Replace `rawmark` in the Docker commands above with that image name to skip source builds. `latest` follows the latest stable release; use a version tag for controlled upgrades. The repository Compose file continues to support source builds. + +Native Linux archives and `SHA256SUMS` are available on the [Releases page](https://github.com/eezd/RawMark/releases). Download the archive matching your architecture and verify its checksum before extracting it: + +```sh +sha256sum --ignore-missing --check SHA256SUMS +tar -xzf rawmark_0.1.0_linux_amd64.tar.gz +./rawmark --version +RAWMARK_DATA="$HOME/rawmark-data" RAWMARK_ADDR=127.0.0.1:21000 ./rawmark +``` + +Use the arm64 archive on ARM systems. Run as an unprivileged user with write access to the chosen data directory. The executable embeds the frontend and examples; no Node.js or separate database service is needed. Configure your service manager and HTTPS proxy for long-running remote use. + ## Storage and configuration | Environment variable | Default | Description | diff --git a/docs/deployment.zh-CN.md b/docs/deployment.zh-CN.md index 0b48a3a..1a3ab8e 100644 --- a/docs/deployment.zh-CN.md +++ b/docs/deployment.zh-CN.md @@ -4,7 +4,7 @@ [返回项目首页](../README.zh-CN.md) · [使用指南](guide.zh-CN.md) · [开发指南](development.zh-CN.md) -RawMark 将前端嵌入 Go 服务,运行时使用 SQLite 与文件系统,不需要额外数据库服务。目前从源码构建 Docker 镜像;以下命令不依赖预先发布的镜像仓库。 +RawMark 将前端嵌入 Go 服务,运行时使用 SQLite 与文件系统,不需要额外数据库服务。可以使用正式镜像或从源码构建,以下源码构建方式继续保留。 ## Docker 快速启动 @@ -52,6 +52,21 @@ RAWMARK_DATA_DIR=/your/storage/rawmark/data \ 不要把快速开始中的容器与 Compose 实例同时指向同一份数据库或笔记库;切换部署方式前先停止旧实例、备份并确认数据位置。 +## 预构建镜像与原生程序 + +正式镜像为 `ghcr.io/eezd/rawmark:0.1.0`,支持 Linux amd64 和 arm64。将上述 Docker 命令中的 `rawmark` 镜像名替换为它即可跳过源码构建。`latest` 跟随最新稳定版本;需要可控升级时请固定版本标签。仓库 Compose 文件继续支持源码构建。 + +[Releases 页面](https://github.com/eezd/RawMark/releases) 提供 Linux 原生压缩包及 `SHA256SUMS`。下载对应架构,先核对校验和再解压: + +```sh +sha256sum --ignore-missing --check SHA256SUMS +tar -xzf rawmark_0.1.0_linux_amd64.tar.gz +./rawmark --version +RAWMARK_DATA="$HOME/rawmark-data" RAWMARK_ADDR=127.0.0.1:21000 ./rawmark +``` + +ARM 系统请使用 arm64 压缩包。以普通用户运行,并确保所选数据目录可写。程序已内嵌前端和示例,无需 Node.js 或独立数据库服务。长期远程运行请配置服务管理器和 HTTPS 反向代理。 + ## 存储与配置 | 环境变量 | 默认值 | 说明 | diff --git a/docs/development.md b/docs/development.md index ba83885..50fd57a 100644 --- a/docs/development.md +++ b/docs/development.md @@ -107,3 +107,13 @@ Vue I18n Composition API supports `en` and `zh-CN`, defaulting to English. Share Account language reuses the settings store's serial writes, server settings, and event synchronization. The sign-in page updates only the local startup cache; authentication loads the server value. CodeMirror updates controls through a locale Compartment without replacing the document, selection, or undo history. User content, existing paths, and plugin-supplied text remain unchanged. Backend `internal/i18n/` preserves message IDs and parameters. Errors render at the HTTP boundary according to `Accept-Language`, retaining `errors.Is/As` and original diagnostics; logs keep their existing language. Existing index warnings are reanalyzed from source notes only when needed for translation, refreshing the entry's complete metadata and revision without changing the index format. Stale entries for deleted or moved notes are skipped; other read errors fail explicitly. Add both languages and interpolation parameters for new messages and run the corresponding dictionary-completeness, settings-sync, editor, and API tests. + +## Version releases and the public demo + +`VERSION` is the release-version source. Docker builds and release binaries inject it into `--version`; Vite uses it for the settings display. The existing content-derived PWA build hash remains independent. + +For a release, update `VERSION`, `.github/release-notes.md`, and the paired installation examples; run the required checks, review and commit the changes, push `main`, then push the matching `vX.Y.Z` tag. `.github/workflows/release.yml` is the only release workflow: ordinary branch pushes do not run it, and publishing its Release does not start another workflow. Jobs check the project, test native Linux amd64/arm64 binaries, package those same binaries into a multi-platform image, deploy the demo, and publish the Release and stable image tags. Rerun failed jobs in the existing run when necessary; do not move published tags or overwrite release assets. + +GitHub Pages must use the GitHub Actions publishing source, with the `github-pages` environment allowing release tags. The first GHCR package must be made public once in its package settings; verify anonymous pulls before announcing the release. Runtime production deployment is separate from publishing a version. + +Build the static editor preview with `pnpm -C web build:demo`. Output is `web/dist-demo/`, with `/RawMark/` as its base path. It uses the existing editor with temporary page-only edits; no private API, authentication, persistent workspace, or Service Worker is used. The workflow deploys it to https://eezd.github.io/RawMark/ in the same release run. diff --git a/docs/development.zh-CN.md b/docs/development.zh-CN.md index 3b86d57..b0f969a 100644 --- a/docs/development.zh-CN.md +++ b/docs/development.zh-CN.md @@ -107,3 +107,13 @@ python3 scripts/prepare-maple-font.py /path/to/MapleMonoNormalNL-NF-CN.zip 账户语言沿用 settings store 的串行写入、服务器设置与事件同步;登录页仅更新本机启动缓存,认证后读取服务器值。CodeMirror 使用 locale Compartment 更新控件,不替换文档、选区或撤销历史。用户正文、现有路径和插件提供的文字保持原样。 后端 `internal/i18n/` 保存消息 ID 与参数,错误在 HTTP 边界根据 `Accept-Language` 渲染,并保留 `errors.Is/As` 及原始诊断;日志遵循原有语言。既有索引警告只在需要时重新分析源笔记生成译文,同时更新该条目的完整元数据和版本,不改索引格式;已删除或移动的过期条目跳过,其他读取错误明确返回。新增文案同时补充中英字典与参数,运行字典完整性、设置同步、编辑器和 API 的相应测试。 + +## 版本发布与公开 Demo + +`VERSION` 是发布版本的唯一来源。Docker 构建和发布二进制将其写入 `--version`,Vite 用于设置页面展示;既有 PWA 内容哈希保持独立。 + +发布时更新 `VERSION`、`.github/release-notes.md` 和双语安装示例,完成检查、review 和提交后 push `main`,再推送对应的 `vX.Y.Z` 标签。`.github/workflows/release.yml` 是唯一发布工作流:普通分支推送不触发,创建 Release 也不启动第二个工作流。同一次运行完成检查、Linux amd64/arm64 原生程序测试、以相同程序封装双架构镜像、Demo 部署及 Release 和稳定镜像标签发布。失败时在原 run 中重跑失败 job,不移动已发布标签或覆盖发布附件。 + +GitHub Pages 必须选择 GitHub Actions 作为发布来源,`github-pages` 环境需要允许版本标签部署。首次 GHCR 镜像需在包设置中将可见性改为 Public,公告前验证匿名拉取。生产实例部署与版本发布是不同操作。 + +执行 `pnpm -C web build:demo` 构建静态编辑器预览,输出至 `web/dist-demo/`,基础路径为 `/RawMark/`。复用现有编辑器,修改仅在当前页面保留;不使用私有 API、认证、持久化工作区或 Service Worker。工作流在同一次发布运行中部署至 https://eezd.github.io/RawMark/。 diff --git a/scripts/smoke-release.py b/scripts/smoke-release.py new file mode 100644 index 0000000..0c9b717 --- /dev/null +++ b/scripts/smoke-release.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Exercise a release binary using disposable data only, on either Linux architecture.""" + +import contextlib +import hashlib +import json +import os +from pathlib import Path +import secrets +import socket +import subprocess +import sys +import tempfile +import time +import urllib.error +import urllib.parse +import urllib.request + + +binary = str(Path(sys.argv[1]).resolve()) +version = Path('VERSION').read_text().strip() +assert subprocess.check_output([binary, '--version'], text=True).strip() == f'RawMark {version}' + + +with tempfile.TemporaryDirectory(prefix='rawmark-release-') as directory: + data = Path(directory) / 'data' + with socket.socket() as listener: + listener.bind(('127.0.0.1', 0)) + port = listener.getsockname()[1] + origin = f'http://127.0.0.1:{port}' + environment = {**os.environ, 'RAWMARK_DATA': str(data), + 'RAWMARK_VAULT': str(data / 'vault'), 'RAWMARK_ADDR': f'127.0.0.1:{port}'} + + def request(path, *, method='GET', body=None, token=None, status=200): + headers = {'Accept-Language': 'en'} + if token: + headers['Authorization'] = 'Bearer ' + token + if body is not None: + headers['Content-Type'] = 'application/json' + body = json.dumps(body).encode() + req = urllib.request.Request(origin + '/api/v1/' + path, data=body, headers=headers, method=method) + try: + response = urllib.request.urlopen(req, timeout=5) + except urllib.error.HTTPError as error: + response = error + with response: + assert response.status == status, (path, response.status, response.read()) + result = json.load(response) + assert (result['code'] == 0) == (status < 400), result + return result['data'] + + @contextlib.contextmanager + def running(): + with (Path(directory) / 'server.log').open('w+') as log: + process = subprocess.Popen([binary], env=environment, stdout=log, stderr=log) + try: + for _ in range(100): + if process.poll() is not None: + raise RuntimeError(f'Release binary exited with {process.returncode}') + try: + request('health') + break + except urllib.error.URLError: + time.sleep(0.1) + else: + raise TimeoutError('Release binary did not become healthy') + yield + except BaseException: + log.flush() + log.seek(0) + print(log.read(), file=sys.stderr) + raise + finally: + if process.poll() is None: + process.terminate() + process.wait(timeout=20) + + with running(): + assert request('auth/state')['setUp'] is False + request('tree', status=401) + token = request('auth/setup', method='POST', body={'password': secrets.token_urlsafe(24)})['token'] + assert token.startswith('rms_') + entries = request('tree', token=token)['entries'] + assert len([entry for entry in entries if not entry['dir'] and entry['path'].endswith('.md')]) == 7 + for note in Path('deploy/data/vault').rglob('*.md'): + assert (data / 'vault' / note.relative_to('deploy/data/vault')).read_bytes() == note.read_bytes() + path = urllib.parse.quote('Getting Started/Welcome to RawMark.md') + original = (data / 'vault/Getting Started/Welcome to RawMark.md').read_bytes() + req = urllib.request.Request(origin + '/api/v1/files/' + path, data=original + b'\nRelease smoke test.\n', + headers={'Authorization': 'Bearer ' + token}, method='PUT') + with urllib.request.urlopen(req, timeout=5) as response: + assert json.load(response)['code'] == 0 + + before = {str(p.relative_to(data / 'vault')): hashlib.sha256(p.read_bytes()).hexdigest() + for p in (data / 'vault').rglob('*') if p.is_file()} + with running(): + assert request('auth/state')['setUp'] is True + request('auth/me', token=token) + after = {str(p.relative_to(data / 'vault')): hashlib.sha256(p.read_bytes()).hexdigest() + for p in (data / 'vault').rglob('*') if p.is_file()} + assert before == after, 'Restart modified existing notes' + print(f'RawMark {version}: initialization, authentication, examples, editing and restart passed')