From 40c1414aa225f31ab273a14642d7511e6d59e834 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 16 Aug 2026 21:00:59 +0800 Subject: [PATCH 1/2] docs(readme): restructure project documentation --- README.en.md | 466 +++++++++++++++++---------------------------------- README.md | 466 +++++++++++++++++---------------------------------- 2 files changed, 312 insertions(+), 620 deletions(-) diff --git a/README.en.md b/README.en.md index 6d6aee7..9ff7549 100644 --- a/README.en.md +++ b/README.en.md @@ -1,96 +1,76 @@
- -

Terminal Blog

+ Terminal Blog logo +

Terminal Blog

+

A blog system where the terminal is the primary interface.

+ +

+ English | 简体中文 +

+ +

+ CI status + Version 0.1.0-beta.1 + Node.js 24 or newer + Next.js 16 + GPL-3.0-only license +

-
- - -**English** | [简体中文](./README.md) - -![License](https://img.shields.io/badge/license-GPL--3.0--only-blue.svg) ![Node.js](https://img.shields.io/badge/Node.js-24%2B-339933.svg) ![Next.js](https://img.shields.io/badge/Next.js-16-black.svg) ![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg) ![Version](https://img.shields.io/badge/Version-Beta-red.svg) -
- -Terminal Blog is a blog system whose primary interface is an actual terminal workspace. It does not place a command-line theme around a conventional web page. Articles, categories, drafts, attachments, configuration, and administrative operations are exposed through a Unix-like filesystem and command model. - -Visitors browse with `ls`, `cd`, `cat`, `less`, `head`, `tail`, `grep`, and pipelines. Administrators use `su root` or `sudo` together with an in-terminal `nano` editor, draft management, uploads, moves, and removals. +

+ Terminal Blog terminal workspace +

-```shell -Terminal Blog Shell 2.6.0 (tty/07) -Copyright (c) 2026 Terminal Blog. All signals preserved. -Last login: Fri Aug 15 04:42:07 from public.gateway +Terminal Blog maps articles, categories, drafts, attachments, configuration, and administration to a Unix-like file and command model. Visitors read Markdown as if they were browsing a filesystem; administrators can edit, publish, and maintain the site from the terminal. +```text guest@terminal.blog:~ $ cd systems guest@terminal.blog:~/systems $ ls -r--r--r-- 2026-08-12 6 min packet-garden.md guest@terminal.blog:~/systems $ cat packet-garden render ``` -## Why Terminal Blog - -Terminal Blog is designed for two audiences at once. Experienced terminal users get a continuous, composable workflow, while visitors unfamiliar with command-line tools still receive prediction, completion, parameter hints, help output, and an optional collapsible file tree. - -| Design | Implementation and benefit | -| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| Terminal-only workspace | There is no traditional header, footer, or fixed command bar. The prompt remains at the end of the scrollback buffer | -| Composable commands | A central registry generates help, argument descriptions, and completions; text commands support pipelines such as `cat \| grep` | -| Large-buffer performance | TanStack Virtual mounts only visible output entries plus a small overscan window | -| On-demand CJK font loading | Maple Mono is split into 46 `unicode-range` WOFF2 files, so the browser downloads only glyph ranges used on the page | -| In-terminal editing | `nano` and `less` use an alternate-screen model and restore scrollback when closed instead of opening web modals | -| Files and index are separate | Markdown files remain the source of truth; SQLite indexes metadata without duplicating article bodies | -| Safer Markdown | React Markdown disables raw HTML, Remark GFM adds common extensions, and Shiki is loaded dynamically for dual-theme highlighting | -| Revocable authentication | Root sessions are stored server-side with password versions and revocation instead of non-revocable signed tokens | -| Deployment configuration | Blog identity, title templates, links, filing records, icons, and the cookie notice share one configuration model | - -## Terminal Experience - -### Input model +## Contents -- `/` opens the command menu, single-letter input predicts commands, `Tab` completes, and arrow keys navigate suggestions and history. -- `Insert` switches between insert and overwrite cursor modes. -- Password input echoes neither characters nor password length. -- Terminal-style copy and paste use `Ctrl+Shift+C`, `Ctrl+Shift+V`, and middle-click paste. -- A custom context menu provides copy, paste, paste selection, select all, and clear. -- `clear` recreates the session output while preserving the boot copyright text and a fresh prompt. +- [About](#about) +- [Current Version](#current-version) +- [Features](#features) +- [Quick Start](#quick-start) +- [Docker Deployment](#docker-deployment) +- [Usage](#usage) +- [Configuration](#configuration) +- [Technical Overview](#technical-overview) +- [Development](#development) +- [Contributing and Releases](#contributing-and-releases) +- [Changelog](./CHANGELOG.md) +- [License](#license) -### Virtual scrollback +## About -The scrollback uses `@tanstack/react-virtual` with stable entry IDs, live DOM height measurement, an initial estimate of roughly `52px`, and `8` overscan items. +Terminal Blog is not a command-line skin around a conventional webpage. The terminal is the workspace: command output goes into a scrollback buffer, input stays at its end, and `nano` and `less` use alternate screens that restore the previous reading position when they close. -As command history, Markdown, screenfetch data, and article output grow, the browser does not keep every output node mounted. Only entries around the viewport are rendered. The prompt can still remain at the logical end of the buffer, and closing `nano` or `less` restores the previous scroll position. +The project is designed for visitors who enjoy a continuous, composable reading flow, while command menus, completion, help text, and a collapsible file tree keep the experience approachable. -### Maple Mono Unicode sharding +## Current Version -The full Maple Mono CJK font is too large for a smooth first load. Terminal Blog splits it into 46 WOFF2 resources: +`0.1.0-beta.1` is the current baseline release and includes: -- The ASCII shard is about 34 KB and is preloaded during application startup. -- Latin Extended, symbols, CJK punctuation, and full-width characters use separate shards. -- CJK Unified Ideographs are divided into ranges of roughly 512 code points, such as `U+4E00-4FFF` and `U+5000-51FF`. -- All shards total about 6.4 MB, but a browser requests only ranges required by characters currently rendered. +- A Next.js standalone Docker image and a Compose deployment with persistent volumes. +- The shorter `config` virtual path, configurable title templates, and synchronized site metadata. +- A first-visit local-storage prompt controlled by `enable`, with `y`, `n`, and `Ctrl+C` handling. +- Terminal reading, article administration, drafts, uploads, authentication, and composable command pipelines. -Declarations live in [`app/maple-mono.css`](./app/maple-mono.css), while the WOFF2 files live under `public/fonts/maple-mono/`. This avoids loading the entire font before first paint while preserving consistent Chinese and Latin typography. +See [`CHANGELOG.md`](./CHANGELOG.md) for the complete change history. -### Markdown and syntax highlighting +## Features -- `react-markdown` renders React output without a Vue or external rendering runtime. -- `remark-gfm` adds tables, strikethrough, task lists, and autolinks. -- `skipHtml` prevents raw article HTML from being rendered. -- The Shiki Web Bundle is dynamically imported only when a fenced code block is present. -- Shiki generates both `github-light` and `github-dark` colors and follows the terminal theme. -- Images are lazy-loaded, and safe relative article paths can resolve into the whitelisted `access/` directory. - -## Technology Stack - -| Layer | Technology | -| -------------- | ---------------------------------------- | -| Web framework | Next.js 16 App Router, React 19 | -| Language | TypeScript 5 with `strict: true` | -| UI and styling | Tailwind CSS 4, native CSS, Lucide React | -| Virtualization | TanStack React Virtual | -| Markdown | React Markdown, Remark GFM, Shiki | -| Database | SQLite, better-sqlite3, WAL | -| Validation | Zod 4 | -| Quality | ESLint 9, Prettier 3, Vitest 3 | +- **Terminal-first reading**: `ls`, `cd`, `cat`, `less`, `head`, `tail`, `grep`, `search`, and text pipelines. +- **In-terminal administration**: root sessions, `nano`, draft lifecycle, uploads, moves, deletion, and password changes. +- **Markdown content**: frontmatter, GFM tables and task lists, Shiki syntax highlighting, and article attachments. +- **Large scrollback without DOM growth**: TanStack Virtual renders the visible output around the viewport. +- **Bilingual experience**: language, theme, and terminal preferences persist in browser localStorage; Maple Mono loads by Unicode shard. +- **Configurable identity**: blog name, title templates, links, filing records, favicon, and the first-visit local-storage notice share one site configuration. +- **Persistent deployment**: a Next.js standalone image and Compose setup persist articles, drafts, attachments, and SQLite data. +- **Security boundaries**: revocable root sessions, atomic writes, upload signature checks, same-origin validation, body limits, and runtime schemas. ## Quick Start @@ -99,82 +79,104 @@ Declarations live in [`app/maple-mono.css`](./app/maple-mono.css), while the WOF - Node.js 24 or newer - npm 10 or newer - Windows, Linux, or macOS -- Native module support for `better-sqlite3`; common platforms normally use a prebuilt binary +- Native module support for `better-sqlite3` (common platforms normally use a prebuilt binary) -### Install and run +### Run locally ```bash -git clone -cd terminal_blog +git clone https://github.com/bao-cn/Terminal-Blog.git +cd Terminal-Blog npm install npm run dev ``` -Open . +Open . When the database is created for the first time without `TERMINAL_ROOT_PASSWORD`, the initial administrator credentials are `root` / `root`. Use this default only for local development. -### Production build +For a production build: ```bash npm run build npm run start ``` -A production deployment needs persistent volumes for `articles/`, `draft/`, `access/`, and `data/`. An ephemeral container without those volumes will lose content, uploads, or database state during redeployment. - -## Default Administrator and Security Warning +### Content directories -When the database is created for the first time without `TERMINAL_ROOT_PASSWORD`, the initial credentials are: +The following directories can be prepared before starting the application and are ignored by Git by default: ```text -username: root -password: root +articles/ published Markdown articles +draft/ unpublished drafts +access/ article images and other attachments +data/ SQLite database ``` -Enter a privileged session: - -```text -su root -``` +## Docker Deployment -Run one elevated command: +The production image uses Next.js standalone output. Set a high-entropy password for the initial root credential, then start the service: -```text -sudo nano article.md +```bash +export TERMINAL_ROOT_PASSWORD='replace-with-a-random-secret-at-least-16-characters' +docker compose up --build -d ``` -Use `passwd` after logging in. Production deployments must set a high-entropy password before the first startup: +The service listens on by default. Set `TERMINAL_BLOG_PORT` to change the host port: ```bash -TERMINAL_ROOT_PASSWORD=replace-with-a-random-secret-at-least-16-characters +TERMINAL_BLOG_PORT=8080 docker compose up --build -d ``` -The default `root` password is for local initialization only and is not production-safe. +Compose persists `articles/`, `draft/`, `access/`, and `data/`. Removing the container preserves the content; `docker compose down -v` removes the named volumes and their data. Production deployments should place nginx, Caddy, or another reverse proxy in front of the container for TLS and rate limiting. -Authentication includes: +`TERMINAL_ROOT_PASSWORD` is used only when the database creates the root credential for the first time. Changing the environment variable does not overwrite the stored password. -- Asynchronous `scrypt` password hashing to avoid blocking the Node.js event loop. -- Per-IP and global login limits with exponential backoff. -- Random opaque session tokens with only SHA-256 digests stored in SQLite. -- Expiration, revocation, password-version, and future-time checks. -- Password changes revoke every previous session. -- HttpOnly and SameSite=Strict cookies, with Secure added in production. -- Same-origin mutation checks, body limits, Content-Type validation, and Zod runtime schemas. +## Usage -## Content Model +### Visitor commands -### Articles +| Command | Purpose | +| -------------------------------- | ------------------------------------------------- | +| `help` / `man` | Show generated help | +| `ls [limit] [page]` | List categories or paginated articles | +| `cd [category\|..\|/]` | Change article category | +| `cat
[render\|source]` | Render an article or print Markdown source | +| `less
` | Read in an alternate screen; press `Q` to exit | +| `head` / `tail` | Read the beginning or end of an article | +| `grep [article]` | Search an article or piped input | +| `search` | Search by title, pinyin, tags, category, and date | +| `stat
` | Show complete metadata | +| `history` / `clear` | Manage the session scrollback | +| `theme [auto\|light\|dark]` | Change the theme | +| `lang [zh\|en]` | Change the interface language | +| `drawer` / `tree` | Expand or collapse the file tree | + +Commands can be composed with pipelines: -Articles live one category deep under `articles//`: +```text +cat packet-garden source | grep network +head -n 30 packet-garden | grep latency +tail -c 512 packet-garden | grep signal +``` + +Type `/` to open the command menu, press `Tab` for completion, and use the arrow keys to navigate suggestions and history. `Ctrl+Shift+C`, `Ctrl+Shift+V`, and middle-click provide terminal-style copy and paste. + +### Administrator commands ```text -articles/ - systems/ - packet-garden.md - field-notes/ - local-first-sunday.md +su root enter a root session +sudo authenticate and run one root command +nano
create or edit an article +draft new|list|edit|publish|rm manage drafts +mkdir create a top-level category +mv
move an article +rm
remove an article and its index entry +upload upload Markdown or an attachment +passwd change the password and revoke old sessions +email [address] read or update the contact email ``` -Each Markdown file uses frontmatter: +### Article format + +Articles live under `articles//`, with at most one category level. The Markdown file is the source of truth; SQLite stores metadata for lists and search: ```markdown --- @@ -183,48 +185,29 @@ slug: example-article date: 2026-08-15 readTime: 5 min tags: [terminal, nextjs] -pinyin: example article excerpt: "Article summary" --- # Article body - -![Architecture](../../access/architecture.png) ``` -Article bodies remain exclusively in Markdown. The SQLite `article_index` table stores slug, title, category, date, reading time, tags, pinyin, and source path. A full index synchronization removes records whose files no longer exist. - -### Drafts and attachments +Store images and other attachments under `access/` and reference them with a relative path such as `../../access/architecture.png`. -- `draft/` stores unpublished Markdown drafts. -- `access/` stores article images. -- `upload` can write only to whitelisted `articles/` and `access/` paths. -- Attachment uploads validate extension, MIME type, and real file signature. -- File changes use same-directory temporary files, `fsync`, and atomic replacement so failed writes preserve the previous file. +## Configuration -### Site configuration - -The initial configuration lives in `config/site.config.json`. Root can edit the mapped virtual system file: +The initial site configuration lives in [`config/site.config.json`](./config/site.config.json). Root can edit it through the shorter virtual path: ```text sudo nano config ``` -`config` is not a real file on disk. Its reads and writes are mapped to SQLite `system_config`. The configuration controls: - -- Blog name and description -- `{BlogName}` and `{ArticleName}` title templates -- Favicon -- Contact email -- ICP and public-security filing text -- Friendly links -- Cookie / local-storage notice toggle and message -- Source-address fallback label - -The cookie notice uses this structure: +`config` is virtual and is not written as a file on disk; reads and writes map to SQLite's `system_config` record. Common settings look like this: ```json { + "blogName": "terminal.blog", + "description": "Field notes from the command line.", + "titleTemplate": "{BlogName} | {ArticleName}", "cookieNotice": { "enable": true, "message": "This site stores language, theme, and terminal preferences locally." @@ -232,149 +215,40 @@ The cookie notice uses this structure: } ``` -When enabled, a visitor without a stored choice sees the notice at the end of the scrollback on first entry. Enter `y` to accept, or enter `n` / press `Ctrl+C` to decline. The choice is stored in localStorage so later visits do not repeat the prompt. Legacy string values for `cookieNotice` remain readable and are treated as enabled. +- `titleTemplate` supports `{BlogName}` and `{ArticleName}`. It drives server metadata and the browser tab; before an article is opened, `{ArticleName}` uses the site description, and after an article is opened it uses the article title. +- `cookieNotice.enable` controls the first-visit prompt. When enabled and no localStorage choice exists, the notice is appended to the end of the scrollback; enter `y` to accept, or `n` / `Ctrl+C` to decline. The choice is stored and the prompt is not shown again. +- The site configuration also supports the favicon, contact email, friendly links, ICP / public-security filing text, and a source-address fallback label. -The title template drives both server metadata and the browser tab title. Before an article is opened, `{ArticleName}` uses the site `description`; after `cat` or `less` opens an article, it uses that article's title. `{BlogName}` always uses the current blog name. +Environment variables: -## Command System +| Variable | Default | Description | +| ------------------------ | -------------- | ------------------------------------------------------------------------------------- | +| `TERMINAL_ROOT_PASSWORD` | `root` | Used only when the root credential is first created; set a strong value in production | +| `TERMINAL_BLOG_PORT` | `3000` | Host port in Compose | +| `NODE_ENV` | Set by Next.js | Controls Secure cookies, HSTS, and development CSP | -### Visitor commands +## Technical Overview -| Command | Purpose | -| -------------------------------- | ----------------------------------------------------------- | -| `help` / `man` | Show help generated from the command registry | -| `ls [limit] [page]` | List categories or paginated articles | -| `cd [category\|..\|/]` | Change article category | -| `cat
[render\|source]` | Render Markdown or print its source | -| `less
` | Open the alternate-screen pager; press `Q` to exit | -| `head` / `tail` | Read the beginning or end by lines or bytes | -| `grep [article]` | Search an article or piped input | -| `search` | Search by title, pinyin, initials, tags, category, and date | -| `stat
` | Print complete metadata | -| `history` / `clear` | Manage the terminal scrollback session | -| `theme [auto\|light\|dark]` | Change the color theme | -| `lang [zh\|en]` | Change UI language without translating command names | -| `drawer` / `tree` | Expand or collapse the helper file tree | -| `screenfetch` | Print browser, engine, GPU, memory, and device information | - -Text commands support pipelines: - -```text -cat packet-garden source | grep network -head -n 30 packet-garden | grep latency -tail -c 512 packet-garden | grep signal -``` - -### Administrator commands - -| Command | Purpose | -| ------------------------------------ | ------------------------------------------------ | -| `su root` / `exit` | Enter or leave a root session | -| `sudo ` | Authenticate and run one command as root | -| `nano
` | Create or edit an article inside the terminal | -| `draft new\|list\|edit\|publish\|rm` | Manage the draft lifecycle | -| `mkdir ` | Create a top-level category | -| `mv
` | Move an article | -| `rm
` | Remove an article and its index entry | -| `upload ` | Upload Markdown or an image | -| `passwd` | Change the root password and revoke old sessions | -| `email [address]` | Read or update the contact email | - -Fun commands include `cmatrix`, `hollywood`, `cbonsai`, `cowsay`, and `nyancat`. Their output is appended directly to the scrollback like every other terminal command. - -### Adding a command - -1. Register the command, aliases, permissions, and argument definitions in `lib/command-registry.ts`. -2. Put pure parsing or text processing in `lib/terminal-command-parser.ts` or another focused domain module. -3. Connect stateful React or API behavior in the terminal controller. -4. Add Vitest coverage for validation, aliases, pipelines, or output behavior. -5. `help`, the command palette, and parameter hints automatically consume the registry; do not maintain a second hard-coded help list. - -## Architecture +| Layer | Technology | +| ----------- | -------------------------------------------------- | +| Web | Next.js 16 App Router, React 19, TypeScript strict | +| Styling | Tailwind CSS 4, native CSS, Lucide React | +| Content | React Markdown, Remark GFM, Shiki | +| Interaction | TanStack React Virtual | +| Data | SQLite, better-sqlite3, WAL | +| Quality | ESLint 9, Prettier 3, Vitest 3 | ```mermaid -flowchart TD - B["Browser / Terminal UI"] --> P["Next.js App Router"] - B --> C["Command registry, parser and pipeline"] - P --> R["Route Handlers"] - R --> S["Request security and Zod schemas"] - S --> A["Auth store"] - S --> AS["Article / draft / upload stores"] - S --> CS["Config store"] - AS --> F["Markdown and access filesystem"] - AS --> I["Article metadata index"] - A --> D["SQLite"] - I --> D - CS --> D +flowchart LR + UI[Terminal workspace] --> Commands[Command registry and pipelines] + UI --> Routes[Next.js Route Handlers] + Routes --> Security[Authentication and request checks] + Security --> Stores[Article, draft, upload, and config stores] + Stores --> Files[Markdown and attachments] + Stores --> SQLite[(SQLite)] ``` -### Layer responsibilities - -| Layer | Main files | Responsibility | -| ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| Entry points | `app/page.tsx`, `app/layout.tsx` | SSR data, metadata, global font preload | -| Terminal workspace | `components/TerminalBlog.tsx` | Session state, command dispatch, scrollback, and Drawer coordination | -| Terminal components | `components/terminal/*` | Prompt, Markdown, nano, less, screenfetch, context menu | -| Command domain | `command-registry.ts`, `terminal-command-parser.ts`, `terminal-text-pipeline.ts` | Registration, completion, argument rules, pipeline parsing, pure text execution | -| API boundary | `request-security.ts`, `api-schemas.ts` | Origin checks, limits, Content-Type, errors, runtime schemas | -| Data access | `auth-store.ts`, `article-store.ts`, `draft-store.ts`, `upload-store.ts`, `config-store.ts` | Domain-specific persistence and authorization boundaries | -| Persistence | `database.ts`, `article-index-store.ts`, `atomic-file.ts` | SQLite migrations, metadata indexing, atomic file updates | - -### Request and state flow - -1. Every page request reads current site configuration, articles, categories, and attachments on the server. -2. The client stores only theme, language, the cookie-notice choice, and configuration MD5 in localStorage. Articles are never restored from localStorage; server data remains authoritative. -3. Mutation requests pass origin, authentication, size, Content-Type, and Zod checks. -4. Successful filesystem changes synchronize the SQLite metadata index. -5. Client article state changes only after server confirmation, avoiding irreversible optimistic updates. - -## Project Structure - -```text -app/ Next.js pages, APIs, and global styles - api/ auth, articles, drafts, upload, config - maple-mono.css 46 Unicode-range font declarations -components/ - terminal/ Reusable terminal views and alternate screens -lib/ - command-registry.ts Commands, arguments, and generated help - terminal-*.ts Parsing and testable pipeline execution - *-store.ts Domain-specific data access - request-security.ts Request security boundary - database.ts SQLite schema and migrations -public/fonts/maple-mono/ WOFF2 font shards -articles/ Published content, ignored by Git -draft/ Draft content, ignored by Git -access/ Article assets -data/ SQLite data, ignored by Git -config/ Initial site configuration -tests/ Vitest unit tests -.github/ CI, Issue Forms, and PR template -``` - -## Configuration and Environment - -| Variable | Required | Default | Description | -| ------------------------ | -------------- | ------------- | --------------------------------------------------- | -| `TERMINAL_ROOT_PASSWORD` | No | `root` | Used only when the root credential is first created | -| `NODE_ENV` | Set by Next.js | `development` | Controls Secure cookies, HSTS, and development CSP | - -Site configuration is not statically cached by Next.js. Requests read SQLite or the initial JSON and generate an MD5 fingerprint that lets the client detect configuration changes. - -## Docker Deployment - -The production image uses Next.js standalone output. Set a high-entropy password for the initial root credential, then start the service: - -```bash -export TERMINAL_ROOT_PASSWORD='replace-with-a-random-secret-at-least-16-characters' -docker compose up --build -d -``` - -The service listens on `http://localhost:3000` by default. Set `TERMINAL_BLOG_PORT` to change the host port. Compose uses named volumes for `articles/`, `draft/`, `access/`, and `data/`; removing the container preserves them, while `docker compose down -v` also removes the volumes and their data. - -Production deployments should place nginx, Caddy, or another reverse proxy in front of the container for TLS, rate limiting, and malformed or slow connections. `TERMINAL_ROOT_PASSWORD` is used only when the database creates the root credential for the first time; changing it later does not overwrite the stored password. - -## Development and Quality Checks +## Development ```bash npm run lint @@ -384,47 +258,19 @@ npx prettier --check . npm run build ``` -Tests cover frontmatter, API schemas, body and origin policy, session timing, command registration, and pipeline execution. New work should add unit or Route Handler integration coverage proportional to its risk. - -## Contribution Workflow - -See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full policy. The standard flow is: - -1. Search existing Issues before opening a new report or proposal. -2. Use the Bug Report form for reproducible defects and the Feature Request form for terminal semantics or workflow proposals. -3. Fork the repository and branch from the latest default branch using `fix/`, `feat/`, `docs/`, or `refactor/`. -4. Install dependencies and run the existing checks before changing code. -5. Keep changes focused. Never commit `articles/`, `draft/`, `data/`, local environment files, or Agent instruction files. -6. New commands must use the command registry instead of a separate hard-coded help list. -7. Run every quality command and verify the main terminal workflow at `http://localhost:3000`. -8. Prefer Conventional Commits, for example `feat(commands): add wc command`. -9. A Pull Request must explain motivation, implementation, risk, verification, and visible UI changes. Include screenshots or recordings for visual work. -10. Address review feedback with additional commits and merge only after CI passes and discussions are resolved. - -Do not open a public Issue containing exploit details, passwords, tokens, or real user data. Use GitHub Private vulnerability reporting from the repository Security page. +New commands belong in `lib/command-registry.ts`. Put parsing and text processing in `lib/terminal-command-parser.ts` or a focused domain module, and add Vitest coverage for validation, aliases, pipelines, and output. -## GitHub Automation +## Contributing and Releases -- `CI` runs Prettier, ESLint, TypeScript, Vitest, and a production build for pushes and Pull Requests. -- `Dependency Review` checks newly introduced dependencies for known vulnerabilities and license risk. -- Issue Forms collect reproducible environment and motivation details. -- The Pull Request template requires verification, risk, and visual-change information. +See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the complete policy. The essential workflow is: -## Backups +1. Create a focused `feat/`, `fix/`, `docs/`, or `refactor/` branch from the latest `main`. +2. Implement and verify the change on that branch, using Conventional Commits. +3. Open a Pull Request from the feature branch to `main`; maintainers review and merge it manually. +4. When releasing, create `release/` from the latest `main`; synchronize release fixes back to `main` first. -Git ignores articles, drafts, and database files. Back up at least: - -```text -articles/ -draft/ -access/ -data/terminal-blog.sqlite -``` - -SQLite runs in WAL mode. For online backups, use the SQLite backup API or include the `-wal` and `-shm` files. The safest approach is to stop writes before copying data. +Do not commit articles, drafts, databases, local environment variables, or Agent instruction files. Do not disclose exploit details in public issues; use Private vulnerability reporting from the repository Security page. ## License -Terminal Blog is released under the [GNU General Public License v3.0 only](./LICENSE). - -Maple Mono is distributed under its own license. See [`public/fonts/maple-mono/LICENSE.txt`](./public/fonts/maple-mono/LICENSE.txt). +Terminal Blog is released under the [GNU General Public License v3.0 only](./LICENSE). Maple Mono is distributed under its own license; see [`public/fonts/maple-mono/LICENSE.txt`](./public/fonts/maple-mono/LICENSE.txt). diff --git a/README.md b/README.md index e74cb91..4e16d1e 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,76 @@
- -

Terminal Blog

+ Terminal Blog logo +

Terminal Blog

+

一个真正以终端为主要交互界面的博客系统。

+ +

+ English | 简体中文 +

+ +

+ CI status + Version 0.1.0-beta.1 + Node.js 24 or newer + Next.js 16 + GPL-3.0-only license +

-
- - -[English](./README.en.md) | **简体中文** - -![License](https://img.shields.io/badge/license-GPL--3.0--only-blue.svg) ![Node.js](https://img.shields.io/badge/Node.js-24%2B-339933.svg) ![Next.js](https://img.shields.io/badge/Next.js-16-black.svg) ![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg) ![Version](https://img.shields.io/badge/Version-Beta-red.svg) -
- -Terminal Blog 是一个真正以终端作为主要交互界面的博客系统。它不是在普通网页外面套一层命令行皮肤,而是把文章、分类、草稿、附件、配置和管理员操作统一映射为类 Unix 文件与命令模型。 - -访客可以使用 `ls`、`cd`、`cat`、`less`、`head`、`tail`、`grep` 和管道符阅读内容;管理员可以通过 `su root` 或 `sudo` 使用终端内 `nano`、草稿箱、上传、移动和删除等功能维护博客。 +

+ Terminal Blog terminal workspace +

-```shell -Terminal Blog Shell 2.6.0 (tty/07) -Copyright (c) 2026 Terminal Blog. All signals preserved. -Last login: Fri Aug 15 04:42:07 from public.gateway +Terminal Blog 把文章、分类、草稿、附件、配置和管理员操作映射为类 Unix 文件与命令。访客可以像浏览文件系统一样阅读 Markdown 文章,管理员可以在终端内完成编辑、发布和维护。 +```text guest@terminal.blog:~ $ cd systems guest@terminal.blog:~/systems $ ls -r--r--r-- 2026-08-12 6 min packet-garden.md guest@terminal.blog:~/systems $ cat packet-garden render ``` -## 为什么是 Terminal Blog - -Terminal Blog 试图同时解决两类问题:为熟悉命令行的人提供连续、可组合的阅读体验,也为不熟悉终端的访客保留命令预测、补全、帮助信息和可折叠文件树。 - -| 设计 | 实现与收益 | -| ---------------- | ---------------------------------------------------------------------------------------- | -| 纯终端工作区 | 页面没有传统 Header、Footer 或独立固定输入栏,命令输入始终位于回滚缓冲区末尾 | -| 可组合命令 | 命令注册表统一生成帮助、参数描述和补全信息,文本命令支持 `cat \| grep` 等管道组合 | -| 大缓冲区性能 | 使用 TanStack Virtual 渲染回滚缓冲区,仅挂载可见输出和少量 overscan 节点 | -| 中文字体按需加载 | Maple Mono 被切分为 46 个带 `unicode-range` 的 WOFF2,浏览器只请求页面实际出现的字形区间 | -| 终端内编辑 | `nano` 和 `less` 使用备用屏幕模型,退出后恢复原回滚缓冲区位置,不弹出网页模态框 | -| 文件与索引分离 | Markdown 文件是文章正文的唯一来源,SQLite 只索引 metadata,不复制正文 | -| 安全 Markdown | React Markdown 禁用原始 HTML,Remark GFM 提供扩展语法,Shiki 动态加载并提供双主题高亮 | -| 可撤销认证 | root 会话使用数据库 session、密码版本和服务端撤销,不使用无法失效的纯签名令牌 | -| 可配置部署 | 博客名称、标题模板、友情链接、备案信息、图标和 Cookie 提示由统一配置模型管理 | - -## 核心体验 - -### 终端输入模型 +## 目录 -- 支持 `/` 打开命令菜单,单字母预测命令,`Tab` 补全,方向键选择建议和历史。 -- 支持 `Insert` 在插入模式与覆盖模式之间切换。 -- 密码输入不回显字符,也不显示密码长度。 -- 使用 `Ctrl+Shift+C`、`Ctrl+Shift+V` 和鼠标中键执行终端式复制粘贴。 -- 自定义右键菜单提供复制、粘贴、粘贴选择区、全选和清屏。 -- `clear` 重建会话输出,但保留启动版权信息和新的命令输入。 +- [关于](#关于) +- [当前版本](#当前版本) +- [功能](#功能) +- [快速开始](#快速开始) +- [Docker 部署](#docker-部署) +- [使用指南](#使用指南) +- [配置](#配置) +- [技术概览](#技术概览) +- [开发与检查](#开发与检查) +- [贡献与发布](#贡献与发布) +- [更新记录](./CHANGELOG.md) +- [许可证](#许可证) -### 虚拟回滚缓冲区 +## 关于 -终端使用 `@tanstack/react-virtual` 管理输出项。当前实现采用稳定 Entry ID、动态 DOM 高度测量、约 `52px` 的初始估算和 `8` 项 overscan。 +Terminal Blog 不是普通网页套一层命令行皮肤,而是把终端作为主要工作区:命令输出进入滚动缓冲区,输入始终位于缓冲区末尾,`nano` 和 `less` 使用备用屏幕,退出后回到原来的阅读位置。 -这意味着文章、命令历史、screenfetch 和 Markdown 输出持续增长时,浏览器不需要同时保留所有输出 DOM。虚拟列表只渲染当前视口附近的内容,同时仍允许输入组件跟随缓冲区末尾,并在退出 `nano` 或 `less` 后恢复滚动位置。 +项目适合希望体验连续、可组合阅读流程的访客,也为不熟悉终端的用户提供命令菜单、补全、帮助信息和可折叠文件树。 -### Maple Mono Unicode 分片 +## 当前版本 -完整 Maple Mono 中文字体体积较大,一次加载会导致首屏系统字体与 Web Font 突然切换。项目将字体拆分为 46 个 WOFF2: +`0.1.0-beta.1` 是当前基线版本,包含: -- ASCII 分片约 34 KB,并在页面启动时预加载。 -- Latin Extended、Symbols、CJK 标点和全角字符使用独立分片。 -- CJK Unified Ideographs 以约 512 个码位为区间切分,例如 `U+4E00-4FFF`、`U+5000-51FF`。 -- 所有分片合计约 6.4 MB,但浏览器只下载当前页面出现字符所覆盖的区间。 +- Next.js standalone Docker 镜像与 Compose 持久化部署。 +- `config` 虚拟配置路径、可配置标题模板和站点元数据同步。 +- 可通过 `enable` 开关控制的首访本地存储提示,支持 `y`、`n` 和 `Ctrl+C`。 +- 终端阅读、文章管理、草稿、上传、认证和可组合命令管道。 -分片声明位于 [`app/maple-mono.css`](./app/maple-mono.css),字体文件位于 `public/fonts/maple-mono/`。这种设计降低了初始字体阻塞时间,同时保留 Maple Mono 的中英文一致性。 +完整变更记录见 [`CHANGELOG.md`](./CHANGELOG.md)。 -### Markdown 与代码高亮 +## 功能 -- `react-markdown` 负责 React 渲染,不注入 Vue 或外部渲染运行时。 -- `remark-gfm` 支持表格、删除线、任务列表和自动链接。 -- `skipHtml` 禁止文章中的原始 HTML。 -- Shiki Web Bundle 仅在出现 fenced code block 时动态加载。 -- Shiki 同时生成 `github-light` 与 `github-dark` 主题颜色,跟随终端主题切换。 -- 图片默认懒加载,文章相对路径可以安全解析到白名单 `access/` 资源。 - -## 技术栈 - -| 层级 | 技术 | -| --------- | -------------------------------------- | -| Web 框架 | Next.js 16 App Router、React 19 | -| 开发语言 | TypeScript 5,`strict: true` | -| UI 与样式 | Tailwind CSS 4、原生 CSS、Lucide React | -| 虚拟列表 | TanStack React Virtual | -| Markdown | React Markdown、Remark GFM、Shiki | -| 数据库 | SQLite、better-sqlite3、WAL | -| 校验 | Zod 4 | -| 代码质量 | ESLint 9、Prettier 3、Vitest 3 | +- **终端优先阅读**:支持 `ls`、`cd`、`cat`、`less`、`head`、`tail`、`grep`、`search` 和文本管道。 +- **终端内维护**:root 会话支持 `nano`、草稿生命周期、上传、移动、删除和密码修改。 +- **Markdown 内容**:支持 frontmatter、GFM 表格与任务列表、Shiki 代码高亮和文章附件。 +- **流畅的大缓冲区**:使用 TanStack Virtual 渲染可见滚动内容,避免历史输出持续增长导致 DOM 膨胀。 +- **中英文体验**:界面语言、主题和终端偏好可保存到浏览器 localStorage;Maple Mono 使用 Unicode 分片按需加载。 +- **可配置站点**:博客名称、标题模板、友情链接、备案信息、图标和首访本地存储提示统一由站点配置管理。 +- **可持久化部署**:提供 Next.js standalone Docker 镜像和 Compose 配置,文章、草稿、附件与 SQLite 数据使用持久化卷。 +- **安全边界**:root session 可撤销,写入采用原子替换,上传校验文件签名,mutation API 具备同源、大小和 Schema 校验。 ## 快速开始 @@ -99,82 +79,104 @@ Terminal Blog 试图同时解决两类问题:为熟悉命令行的人提供连 - Node.js 24 或更高版本 - npm 10 或更高版本 - Windows、Linux 或 macOS -- 用于编译或安装 `better-sqlite3` 的原生模块支持;主流平台通常直接使用预编译包 +- 能够安装 `better-sqlite3` 原生模块的环境(主流平台通常使用预编译包) -### 安装与开发 +### 本地运行 ```bash -git clone -cd terminal_blog +git clone https://github.com/bao-cn/Terminal-Blog.git +cd Terminal-Blog npm install npm run dev ``` -访问 。 +打开 。首次创建数据库时,如果没有设置 `TERMINAL_ROOT_PASSWORD`,初始管理员凭据为 `root` / `root`,仅适用于本地开发。 -### 生产构建 +生产构建可以使用: ```bash npm run build npm run start ``` -生产部署应提供持久化的 `articles/`、`draft/`、`access/` 和 `data/` 目录。只部署无状态容器但不挂载这些目录会导致文章、附件或数据库在重新部署后丢失。 - -## 默认管理员与安全提示 +### 本地内容目录 -首次创建数据库且没有设置 `TERMINAL_ROOT_PASSWORD` 时: +运行前可以准备以下目录;它们在 Git 中默认被忽略: ```text -用户名: root -密码: root +articles/ 已发布的 Markdown 文章 +draft/ 未发布的草稿 +access/ 文章图片和其他附件 +data/ SQLite 数据库 ``` -进入 root 会话: - -```text -su root -``` +## Docker 部署 -执行一次提权命令: +项目使用 Next.js standalone 输出构建生产镜像。设置首次初始化 root 凭据所需的高强度密码后启动: -```text -sudo nano article.md +```bash +export TERMINAL_ROOT_PASSWORD='replace-with-a-random-secret-at-least-16-characters' +docker compose up --build -d ``` -登录后执行 `passwd` 可以修改密码。生产环境必须在第一次启动之前提供高熵密码: +服务默认监听 ,可以通过 `TERMINAL_BLOG_PORT` 修改宿主机端口: ```bash -TERMINAL_ROOT_PASSWORD=replace-with-a-random-secret-at-least-16-characters +TERMINAL_BLOG_PORT=8080 docker compose up --build -d ``` -默认密码 `root` 仅用于本地初始化,不满足生产安全要求。 +Compose 会持久化 `articles/`、`draft/`、`access/` 和 `data/`。删除容器不会删除这些内容;只有 `docker compose down -v` 才会删除命名卷及其中的数据。生产环境应在容器前配置 nginx、Caddy 等反向代理来处理 TLS 和请求限速。 + +`TERMINAL_ROOT_PASSWORD` 只在数据库第一次创建 root 凭据时生效。修改环境变量不会覆盖已经保存的密码。 -认证实现包括: +## 使用指南 -- 异步 `scrypt` 密码哈希,避免同步哈希阻塞 Node.js 事件循环。 -- IP 与全局登录频率限制及指数退避。 -- 随机不透明 session token,数据库只保存 SHA-256 摘要。 -- session 过期、撤销、密码版本和未来时间校验。 -- 修改密码会撤销所有旧 session。 -- HttpOnly、SameSite=Strict Cookie,生产环境自动添加 Secure。 -- mutation API 的同源检查、请求大小限制、Content-Type 检查和 Zod 运行时校验。 +### 访客命令 -## 内容模型 +| 命令 | 作用 | +| -------------------------------- | ---------------------------------- | +| `help` / `man` | 查看自动生成的帮助 | +| `ls [limit] [page]` | 查看分类或分页文章 | +| `cd [category\|..\|/]` | 切换文章分类 | +| `cat
[render\|source]` | 渲染文章或输出 Markdown 源码 | +| `less
` | 在备用屏幕中分页阅读,按 `Q` 退出 | +| `head` / `tail` | 查看文章开头或末尾 | +| `grep [article]` | 搜索文章或管道输入 | +| `search` | 按标题、拼音、标签、分类和日期搜索 | +| `stat
` | 查看完整 metadata | +| `history` / `clear` | 管理会话滚动缓冲区 | +| `theme [auto\|light\|dark]` | 切换主题 | +| `lang [zh\|en]` | 切换界面语言 | +| `drawer` / `tree` | 展开或折叠辅助文件树 | + +命令支持管道组合: + +```text +cat packet-garden source | grep network +head -n 30 packet-garden | grep latency +tail -c 512 packet-garden | grep signal +``` -### 文章目录 +输入 `/` 打开命令菜单,按 `Tab` 补全,方向键选择建议和历史。`Ctrl+Shift+C`、`Ctrl+Shift+V` 和鼠标中键提供终端式复制粘贴。 -文章放入 `articles//`,只支持一级分类: +### 管理员命令 ```text -articles/ - systems/ - packet-garden.md - field-notes/ - local-first-sunday.md +su root 进入 root 会话 +sudo 验证密码并执行单次 root 命令 +nano
创建或编辑文章 +draft new|list|edit|publish|rm 管理草稿 +mkdir 新建一级分类 +mv
移动文章 +rm
删除文章及索引 +upload 上传 Markdown 或附件 +passwd 修改密码并撤销旧 session +email [address] 查看或修改联系邮箱 ``` -文章使用 frontmatter: +### 文章格式 + +文章放在 `articles//`,分类最多一级。正文保存在 Markdown 文件中,SQLite 只保存用于列表和搜索的 metadata: ```markdown --- @@ -183,48 +185,29 @@ slug: example-article date: 2026-08-15 readTime: 5 min tags: [terminal, nextjs] -pinyin: example article excerpt: "Article summary" --- # Article body - -![Architecture](../../access/architecture.png) ``` -正文只保存在 Markdown 文件中。SQLite `article_index` 保存 slug、标题、分类、日期、阅读时间、标签、拼音和源文件路径。每次扫描文章时会全量同步索引,并清理磁盘上已经不存在的记录。 - -### 草稿与附件 +图片等附件放在 `access/`,文章中使用相对路径引用,例如 `../../access/architecture.png`。 -- `draft/` 保存未发布 Markdown 草稿。 -- `access/` 保存文章图片。 -- `upload` 只允许写入 `articles/` 或 `access/` 白名单路径。 -- 附件上传校验扩展名、MIME 类型和实际文件签名。 -- 文件写入使用同目录临时文件、`fsync` 和原子替换,写入失败时保留旧文件。 +## 配置 -### 站点配置 - -默认配置文件为 `config/site.config.json`。root 可以通过虚拟路径编辑站点配置: +初始站点配置位于 [`config/site.config.json`](./config/site.config.json)。root 可以通过较短的虚拟路径编辑它: ```text sudo nano config ``` -虚拟文件并不实际存在于磁盘;读取和保存 `config` 会映射到 SQLite `system_config`。配置包括: - -- 博客名称与描述 -- `{BlogName}`、`{ArticleName}` 标题模板 -- 网站图标 -- 联系邮箱 -- ICP 与公安备案信息 -- 友情链接 -- Cookie / 本地存储提示开关与文案 -- 访问来源回退名称 - -Cookie 提示使用以下结构: +`config` 是虚拟文件,不会作为文件写入磁盘;读取和保存会映射到 SQLite 的 `system_config`。常用配置如下: ```json { + "blogName": "terminal.blog", + "description": "Field notes from the command line.", + "titleTemplate": "{BlogName} | {ArticleName}", "cookieNotice": { "enable": true, "message": "本站使用本地存储保存语言、主题和终端偏好。" @@ -232,149 +215,40 @@ Cookie 提示使用以下结构: } ``` -启用后,尚未选择的访客会在首次进入时于回滚缓冲区末尾看到提示。输入 `y` 表示同意,输入 `n` 或按 `Ctrl+C` 表示拒绝;选择会保存到 localStorage,后续访问不再重复提示。旧版字符串形式的 `cookieNotice` 仍可读取,并按启用状态处理。 +- `titleTemplate` 支持 `{BlogName}` 和 `{ArticleName}`。它同时用于服务端 metadata 和浏览器标签;未打开文章时,`{ArticleName}` 使用站点描述,打开文章后使用文章标题。 +- `cookieNotice.enable` 控制首访提示。启用且 localStorage 没有选择时,提示会追加到滚动缓冲区末尾;输入 `y` 同意,输入 `n` 或按 `Ctrl+C` 拒绝。选择保存后不会重复提示。 +- 站点配置还支持 favicon、联系邮箱、友情链接、ICP / 公安备案信息和来源地址回退名称。 -标题模板会同时用于服务端 metadata 和浏览器标签标题。未打开文章时,`{ArticleName}` 使用站点 `description`;通过 `cat` 或 `less` 打开文章后,它会替换为文章标题,`{BlogName}` 始终使用当前博客名称。 +环境变量: -## 命令系统 +| 变量 | 默认值 | 说明 | +| ------------------------ | --------------- | -------------------------------------------------------- | +| `TERMINAL_ROOT_PASSWORD` | `root` | 仅在第一次创建 root 凭据时使用;生产环境必须设置高强度值 | +| `TERMINAL_BLOG_PORT` | `3000` | Compose 映射到宿主机的端口 | +| `NODE_ENV` | 由 Next.js 设置 | 控制 Secure Cookie、HSTS 和开发 CSP | -### 访客命令 +## 技术概览 -| 命令 | 说明 | -| -------------------------------- | ------------------------------------------ | -| `help` / `man` | 查看由注册表自动生成的帮助 | -| `ls [limit] [page]` | 查看分类或分页文章列表 | -| `cd [category\|..\|/]` | 切换文章分类 | -| `cat
[render\|source]` | 渲染 Markdown 或输出源文件 | -| `less
` | 使用备用屏幕分页查看,按 `Q` 退出 | -| `head` / `tail` | 按行数或字节数查看文章开头、末尾 | -| `grep [article]` | 搜索文章或管道输入 | -| `search` | 按标题、拼音、首字母、标签、分类和日期搜索 | -| `stat
` | 查看完整 metadata | -| `history` / `clear` | 管理会话回滚缓冲区 | -| `theme [auto\|light\|dark]` | 切换主题 | -| `lang [zh\|en]` | 切换界面语言,命令名不翻译 | -| `drawer` / `tree` | 展开或折叠辅助文件树 | -| `screenfetch` | 输出浏览器、内核、GPU、内存和设备信息 | - -文本命令支持管道: - -```text -cat packet-garden source | grep network -head -n 30 packet-garden | grep latency -tail -c 512 packet-garden | grep signal -``` - -### 管理员命令 - -| 命令 | 说明 | -| ------------------------------------ | ------------------------------ | -| `su root` / `exit` | 进入或退出 root 会话 | -| `sudo ` | 验证密码并执行单次 root 命令 | -| `nano
` | 在终端内创建或编辑文章 | -| `draft new\|list\|edit\|publish\|rm` | 管理草稿生命周期 | -| `mkdir ` | 新建一级分类 | -| `mv
` | 移动文章 | -| `rm
` | 删除文章及对应索引 | -| `upload ` | 上传 Markdown 或图片 | -| `passwd` | 修改 root 密码并撤销旧 session | -| `email [address]` | 查看或修改联系邮箱 | - -彩蛋命令包括 `cmatrix`、`hollywood`、`cbonsai`、`cowsay` 和 `nyancat`。它们与普通命令一样直接写入回滚缓冲区。 - -### 扩展新命令 - -1. 在 `lib/command-registry.ts` 注册命令、别名、权限和参数定义。 -2. 把纯解析或文本处理逻辑放入 `lib/terminal-command-parser.ts` 或独立领域模块。 -3. 在终端控制器中连接需要 React 状态或 API 的执行逻辑。 -4. 为参数验证、别名、管道或输出增加 Vitest 测试。 -5. `help`、命令菜单和参数提示会自动读取注册表,不需要维护第二份帮助文本。 - -## 系统架构 +| 层级 | 技术 | +| ---- | -------------------------------------------------- | +| Web | Next.js 16 App Router、React 19、TypeScript strict | +| 样式 | Tailwind CSS 4、原生 CSS、Lucide React | +| 内容 | React Markdown、Remark GFM、Shiki | +| 交互 | TanStack React Virtual | +| 数据 | SQLite、better-sqlite3、WAL | +| 质量 | ESLint 9、Prettier 3、Vitest 3 | ```mermaid -flowchart TD - B["Browser / Terminal UI"] --> P["Next.js App Router"] - B --> C["Command registry, parser and pipeline"] - P --> R["Route Handlers"] - R --> S["Request security and Zod schemas"] - S --> A["Auth store"] - S --> AS["Article / draft / upload stores"] - S --> CS["Config store"] - AS --> F["Markdown and access filesystem"] - AS --> I["Article metadata index"] - A --> D["SQLite"] - I --> D - CS --> D -``` - -### 分层职责 - -| 层级 | 主要文件 | 职责 | -| ---------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------- | -| 页面入口 | `app/page.tsx`、`app/layout.tsx` | SSR 初始数据、Metadata、全局字体预加载 | -| 终端工作区 | `components/TerminalBlog.tsx` | 会话状态、命令调度、回滚缓冲区和 Drawer 协调 | -| 终端组件 | `components/terminal/*` | Prompt、Markdown、nano、less、screenfetch、右键菜单 | -| 命令领域 | `lib/command-registry.ts`、`terminal-command-parser.ts`、`terminal-text-pipeline.ts` | 注册、补全、参数计算、管道解析和纯文本执行 | -| API 安全 | `lib/request-security.ts`、`api-schemas.ts` | 同源、大小限制、Content-Type、错误响应和运行时 Schema | -| 数据访问 | `auth-store.ts`、`article-store.ts`、`draft-store.ts`、`upload-store.ts`、`config-store.ts` | 分域的数据读写和授权边界 | -| 持久化 | `database.ts`、`article-index-store.ts`、`atomic-file.ts` | SQLite 迁移、metadata 索引和原子文件更新 | - -### 请求与状态流 - -1. 服务端每次页面请求读取站点配置、文章目录、分类和附件列表。 -2. 客户端只把主题、语言、Cookie 提示选择和配置 MD5 保存到 localStorage;文章不从 localStorage 恢复,服务端数据始终是权威来源。 -3. mutation 请求经过同源检查、认证、请求大小限制和 Zod 校验。 -4. 文件系统写入成功后同步 SQLite metadata 索引。 -5. 客户端在服务端确认成功之后更新文章状态,避免无回滚的乐观更新。 - -## 项目目录 - -```text -app/ Next.js 页面、API 和全局样式 - api/ auth、articles、drafts、upload、config - maple-mono.css 46 个 Unicode-range 字体声明 -components/ - terminal/ 可复用终端视图与备用屏幕组件 -lib/ - command-registry.ts 命令定义、参数与自动帮助 - terminal-*.ts 命令解析和可测试的管道执行 - *-store.ts 分域数据访问层 - request-security.ts 请求安全边界 - database.ts SQLite 建表与迁移 -public/fonts/maple-mono/ 字体 WOFF2 分片 -articles/ 发布文章,默认被 Git 忽略 -draft/ 草稿,默认被 Git 忽略 -access/ 文章附件 -data/ SQLite 数据,默认被 Git 忽略 -config/ 初始站点配置 -tests/ Vitest 单元测试 -.github/ CI、Issue 表单和 PR 模板 -``` - -## 配置与环境变量 - -| 变量 | 必需 | 默认值 | 说明 | -| ------------------------ | --------------- | ------------- | ----------------------------------- | -| `TERMINAL_ROOT_PASSWORD` | 否 | `root` | 仅在第一次创建 root 凭据时使用 | -| `NODE_ENV` | 由 Next.js 设置 | `development` | 控制 Secure Cookie、HSTS 和开发 CSP | - -站点内容配置不使用 Next.js 静态缓存。页面请求会读取 SQLite 或初始 JSON,并生成 MD5 供客户端判断配置是否发生变化。 - -## Docker 部署 - -项目使用 Next.js standalone 输出构建生产镜像。先设置首次初始化 root 凭据所需的高强度密码,再启动服务: - -```bash -export TERMINAL_ROOT_PASSWORD='replace-with-a-random-secret-at-least-16-characters' -docker compose up --build -d +flowchart LR + UI[终端工作区] --> Commands[命令注册表与管道] + UI --> Routes[Next.js Route Handlers] + Routes --> Security[认证与请求校验] + Security --> Stores[文章、草稿、上传、配置 Store] + Stores --> Files[Markdown 与附件] + Stores --> SQLite[(SQLite)] ``` -默认监听 `http://localhost:3000`。可通过 `TERMINAL_BLOG_PORT` 修改宿主机端口。Compose 使用命名卷持久化 `articles/`、`draft/`、`access/` 和 `data/`;删除容器不会删除这些内容,执行 `docker compose down -v` 才会移除卷和其中的数据。 - -生产环境应在容器前部署 nginx、Caddy 等反向代理,并由代理处理 TLS、请求限速和异常连接。`TERMINAL_ROOT_PASSWORD` 只在数据库首次创建 root 凭据时生效,之后修改环境变量不会覆盖已有密码。 - -## 开发与质量检查 +## 开发与检查 ```bash npm run lint @@ -384,47 +258,19 @@ npx prettier --check . npm run build ``` -测试覆盖文章 frontmatter、API Schema、请求大小与同源策略、session 时间策略、命令注册与管道执行。新增功能应根据风险补充单元测试或 Route Handler 集成测试。 - -## 贡献流程 - -完整规范见 [CONTRIBUTING.md](./CONTRIBUTING.md)。标准流程如下: - -1. 先搜索现有 Issue,确认问题或方案尚未被跟踪。 -2. Bug 使用 Bug Report 表单并提供可复现步骤、浏览器、系统和日志;功能建议使用 Feature Request 表单说明终端语义和使用场景。 -3. Fork 仓库,从最新默认分支创建 `fix/`、`feat/`、`docs/` 或 `refactor/` 分支。 -4. 安装依赖并先运行现有测试,确认基线正常。 -5. 修改应保持单一职责,不提交 `articles/`、`draft/`、`data/`、本地环境变量或 Agent 指令文件。 -6. 新命令必须通过命令注册表接入;不要维护独立的硬编码帮助列表。 -7. 提交前运行全部质量命令,并验证 `http://localhost:3000` 的主要终端流程。 -8. Commit 推荐使用 Conventional Commits,例如 `feat(commands): add wc command`。 -9. Pull Request 说明动机、实现、风险、验证结果和 UI 变化;可视变化需要提供截图或录屏。 -10. 评审意见通过新增提交处理,不重写已经进入评审的公共历史;合并前确保 CI 通过且讨论已解决。 - -安全漏洞不要提交包含利用细节、密码、token 或真实数据的公开 Issue。请使用 GitHub 仓库 Security 页面中的 Private vulnerability reporting。 +新增命令请在 `lib/command-registry.ts` 注册;解析和文本处理逻辑放在 `lib/terminal-command-parser.ts` 或独立领域模块,并为参数验证、别名、管道和输出补充 Vitest 测试。 -## GitHub 自动化 +## 贡献与发布 -- `CI` 工作流在 push 和 Pull Request 上运行 Prettier、ESLint、TypeScript、Vitest 和生产构建。 -- `Dependency Review` 在 Pull Request 中检查新增依赖的已知漏洞与许可证风险。 -- Issue Forms 会强制收集复现信息、运行环境和需求动机。 -- Pull Request 模板要求填写验证项、风险和可视变化。 +完整规范见 [`CONTRIBUTING.md`](./CONTRIBUTING.md)。贡献流程的关键约定: -## 数据备份 +1. 从最新 `main` 创建单一职责的 `feat/`、`fix/`、`docs/` 或 `refactor/` 特性分支。 +2. 在特性分支完成实现和检查,提交使用 Conventional Commits。 +3. 特性分支提交 Pull Request 到 `main`,由维护者 Code Review 并手动合并。 +4. 发布时从最新 `main` 创建 `release/` 分支;发布修复先同步回 `main`。 -Git 默认忽略文章、草稿和数据库。升级或迁移前至少备份: - -```text -articles/ -draft/ -access/ -data/terminal-blog.sqlite -``` - -SQLite 使用 WAL 模式。在线复制数据库时需要使用 SQLite backup API 或同时处理 `-wal`、`-shm` 文件;最稳妥的方式是在停止写入后执行备份。 +请不要提交文章、草稿、数据库、本地环境变量或 Agent 指令文件。安全漏洞不要公开提交利用细节,请使用 GitHub Security 页面中的 Private vulnerability reporting。 ## 许可证 -Terminal Blog 使用 [GNU General Public License v3.0 only](./LICENSE) 发布。 - -Maple Mono 字体使用其自身许可证,详见 [`public/fonts/maple-mono/LICENSE.txt`](./public/fonts/maple-mono/LICENSE.txt)。 +Terminal Blog 使用 [GNU General Public License v3.0 only](./LICENSE) 发布。Maple Mono 字体使用其自身许可证,详见 [`public/fonts/maple-mono/LICENSE.txt`](./public/fonts/maple-mono/LICENSE.txt)。 From 075407c518948702d5e357a8dd85be0e6cbb436d Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 16 Aug 2026 21:10:09 +0800 Subject: [PATCH 2/2] docs(readme): add user deployment instructions --- README.en.md | 104 +++++++++++++++++++++++++++++++++++++++++---------- README.md | 104 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 168 insertions(+), 40 deletions(-) diff --git a/README.en.md b/README.en.md index 9ff7549..100d8c7 100644 --- a/README.en.md +++ b/README.en.md @@ -34,8 +34,8 @@ guest@terminal.blog:~/systems $ cat packet-garden render - [About](#about) - [Current Version](#current-version) - [Features](#features) +- [User Deployment](#user-deployment) - [Quick Start](#quick-start) -- [Docker Deployment](#docker-deployment) - [Usage](#usage) - [Configuration](#configuration) - [Technical Overview](#technical-overview) @@ -72,8 +72,91 @@ See [`CHANGELOG.md`](./CHANGELOG.md) for the complete change history. - **Persistent deployment**: a Next.js standalone image and Compose setup persist articles, drafts, attachments, and SQLite data. - **Security boundaries**: revocable root sessions, atomic writes, upload signature checks, same-origin validation, body limits, and runtime schemas. +## User Deployment + +These steps are for users who only want to run the blog; no knowledge of Next.js or the project code is required. Before the first deployment, prepare a high-entropy password for the root administrator. Do not use the default `root` password in production. + +### Option 1: Manual deployment + +Use this option when you do not want Docker and plan to run the application directly on Windows, Linux, or macOS. + +1. Install [Node.js 24 or newer](https://nodejs.org/); npm is included with the installer. +2. Click **Code → Download ZIP** in the GitHub repository and extract it to a permanent directory such as `terminal-blog`. +3. Open PowerShell, a terminal, or Command Prompt in that directory and install dependencies: + + ```bash + npm install + ``` + +4. Set the initial root password. In PowerShell: + + ```powershell + $env:TERMINAL_ROOT_PASSWORD = "replace-with-a-random-secret-at-least-16-characters" + ``` + + On Linux or macOS: + + ```bash + export TERMINAL_ROOT_PASSWORD='replace-with-a-random-secret-at-least-16-characters' + ``` + +5. Build and start the production server: + + ```bash + npm run build + npm run start + ``` + +6. Open in a browser. Keep the terminal window running; press `Ctrl+C` to stop the service. + +The application stores the following content in its working directory. Back them up regularly: + +```text +articles/ published articles +draft/ drafts +access/ images and other attachments +data/ SQLite database +``` + +To update a manual installation, back up these directories and press `Ctrl+C` to stop the service. Replace the project files, then run `npm install`, `npm run build`, and `npm run start` again. Do not delete or overwrite the content directories. + +### Option 2: Docker deployment + +Use this option with Docker Desktop (Windows, macOS) or Docker Engine and Compose v2 (Linux). Docker handles Node.js and native dependencies automatically, so it is the recommended option for most users. + +1. Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine, then confirm that `docker compose version` works. +2. Download and extract the project ZIP. Create a `.env` file in the project directory: + + ```dotenv + TERMINAL_ROOT_PASSWORD=replace-with-a-random-secret-at-least-16-characters + TERMINAL_BLOG_PORT=3000 + ``` + + Keep `.env` on the local machine and never publish the password. + +3. Run this command from the project directory: + + ```bash + docker compose up --build -d + ``` + +4. Open in a browser. If you changed `TERMINAL_BLOG_PORT`, use that port instead. + +Useful management commands: + +```bash +docker compose ps # show status +docker compose logs -f # follow logs; press Ctrl+C to stop viewing +docker compose stop # stop containers and keep data +docker compose up -d --build # rebuild and restart after an update +``` + +Compose stores `articles/`, `draft/`, `access/`, and `data/` in named volumes. `docker compose stop` and container removal preserve the data; `docker compose down -v` deletes the volumes and all articles, attachments, and database contents. Run it only when you intend to erase the site. + ## Quick Start +The following section is for contributors who need to modify code or run the development server. + ### Requirements - Node.js 24 or newer @@ -110,25 +193,6 @@ access/ article images and other attachments data/ SQLite database ``` -## Docker Deployment - -The production image uses Next.js standalone output. Set a high-entropy password for the initial root credential, then start the service: - -```bash -export TERMINAL_ROOT_PASSWORD='replace-with-a-random-secret-at-least-16-characters' -docker compose up --build -d -``` - -The service listens on by default. Set `TERMINAL_BLOG_PORT` to change the host port: - -```bash -TERMINAL_BLOG_PORT=8080 docker compose up --build -d -``` - -Compose persists `articles/`, `draft/`, `access/`, and `data/`. Removing the container preserves the content; `docker compose down -v` removes the named volumes and their data. Production deployments should place nginx, Caddy, or another reverse proxy in front of the container for TLS and rate limiting. - -`TERMINAL_ROOT_PASSWORD` is used only when the database creates the root credential for the first time. Changing the environment variable does not overwrite the stored password. - ## Usage ### Visitor commands diff --git a/README.md b/README.md index 4e16d1e..8712fd4 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ guest@terminal.blog:~/systems $ cat packet-garden render - [关于](#关于) - [当前版本](#当前版本) - [功能](#功能) +- [用户部署](#用户部署) - [快速开始](#快速开始) -- [Docker 部署](#docker-部署) - [使用指南](#使用指南) - [配置](#配置) - [技术概览](#技术概览) @@ -72,8 +72,91 @@ Terminal Blog 不是普通网页套一层命令行皮肤,而是把终端作为 - **可持久化部署**:提供 Next.js standalone Docker 镜像和 Compose 配置,文章、草稿、附件与 SQLite 数据使用持久化卷。 - **安全边界**:root session 可撤销,写入采用原子替换,上传校验文件签名,mutation API 具备同源、大小和 Schema 校验。 +## 用户部署 + +以下步骤面向只想运行博客的用户,不要求了解 Next.js 或项目代码。首次部署前,请准备一个用于 root 管理员的高强度密码;不要在生产环境使用默认密码 `root`。 + +### 方式一:手动部署 + +适用于不使用 Docker、希望直接在 Windows、Linux 或 macOS 上运行的场景。 + +1. 安装 [Node.js 24 或更高版本](https://nodejs.org/),安装程序会同时提供 npm。 +2. 在 GitHub 仓库中点击 **Code → Download ZIP**,解压到一个固定目录,例如 `terminal-blog`。 +3. 在该目录打开 PowerShell、终端或命令提示符,安装依赖: + + ```bash + npm install + ``` + +4. 设置首次初始化管理员密码。PowerShell 使用: + + ```powershell + $env:TERMINAL_ROOT_PASSWORD = "replace-with-a-random-secret-at-least-16-characters" + ``` + + Linux 或 macOS 使用: + + ```bash + export TERMINAL_ROOT_PASSWORD='replace-with-a-random-secret-at-least-16-characters' + ``` + +5. 创建生产版本并启动: + + ```bash + npm run build + npm run start + ``` + +6. 浏览器打开 。运行博客的终端窗口需要保持开启;停止服务时按 `Ctrl+C`。 + +应用会在运行目录中保存以下内容,请定期备份: + +```text +articles/ 已发布文章 +draft/ 草稿 +access/ 图片和其他附件 +data/ SQLite 数据库 +``` + +更新手动部署时,先备份上述目录并按 `Ctrl+C` 停止服务,再替换项目文件,重新执行 `npm install`、`npm run build` 和 `npm run start`。不要删除或覆盖这些内容目录。 + +### 方式二:Docker 部署 + +适用于已经安装 Docker Desktop(Windows、macOS)或 Docker Engine 与 Compose v2(Linux)的场景。Docker 会自动处理 Node.js 和原生依赖,推荐普通用户使用此方式。 + +1. 安装 [Docker Desktop](https://www.docker.com/products/docker-desktop/) 或 Docker Engine,并确认 `docker compose version` 可以正常运行。 +2. 下载并解压项目 ZIP,在项目目录中创建 `.env` 文件: + + ```dotenv + TERMINAL_ROOT_PASSWORD=replace-with-a-random-secret-at-least-16-characters + TERMINAL_BLOG_PORT=3000 + ``` + + `.env` 只保存在本机,不要提交或公开其中的密码。 + +3. 在项目目录执行: + + ```bash + docker compose up --build -d + ``` + +4. 浏览器打开 。如果修改了 `TERMINAL_BLOG_PORT`,请访问对应端口。 + +常用管理命令: + +```bash +docker compose ps # 查看运行状态 +docker compose logs -f # 查看实时日志,按 Ctrl+C 退出日志查看 +docker compose stop # 停止容器,保留数据 +docker compose up -d --build # 更新镜像并重新启动 +``` + +Compose 会把 `articles/`、`draft/`、`access/` 和 `data/` 保存到命名卷中。`docker compose stop` 或删除容器不会删除数据;`docker compose down -v` 会删除命名卷及其中的所有文章、附件和数据库,只有在确认要清空站点时才执行。 + ## 快速开始 +以下内容面向需要修改代码、运行开发服务器的贡献者。 + ### 环境要求 - Node.js 24 或更高版本 @@ -110,25 +193,6 @@ access/ 文章图片和其他附件 data/ SQLite 数据库 ``` -## Docker 部署 - -项目使用 Next.js standalone 输出构建生产镜像。设置首次初始化 root 凭据所需的高强度密码后启动: - -```bash -export TERMINAL_ROOT_PASSWORD='replace-with-a-random-secret-at-least-16-characters' -docker compose up --build -d -``` - -服务默认监听 ,可以通过 `TERMINAL_BLOG_PORT` 修改宿主机端口: - -```bash -TERMINAL_BLOG_PORT=8080 docker compose up --build -d -``` - -Compose 会持久化 `articles/`、`draft/`、`access/` 和 `data/`。删除容器不会删除这些内容;只有 `docker compose down -v` 才会删除命名卷及其中的数据。生产环境应在容器前配置 nginx、Caddy 等反向代理来处理 TLS 和请求限速。 - -`TERMINAL_ROOT_PASSWORD` 只在数据库第一次创建 root 凭据时生效。修改环境变量不会覆盖已经保存的密码。 - ## 使用指南 ### 访客命令