展示 HN:Hc:一个无需代理的多租户 shell 历史记录接收器
Show HN: Hc: an agentless, multi-tenant shell history sink

原始链接: https://github.com/alessandrocarminati/hc

## hc:分布式系统的集中式 Shell 历史记录 hc 是一个轻量级服务,旨在解决大量服务器上分散的 Shell 历史记录问题。它将命令历史记录集中到 PostgreSQL 数据库中,无需重复 SSH 会话即可轻松搜索和审计。 **主要特性:** * **无代理:** 客户端机器仅需要 `curl`、`wget` 或 `socat` – 无需软件安装。 * **安全:** 支持使用客户端证书和 API 密钥身份验证的 TLS,以确保数据摄取安全。 * **易于 grep:** 通过 HTTP(S) 将历史记录导出为纯文本,允许直接管道传输到 `grep` 等熟悉工具。 * **多租户:** 使用 API 密钥隔离团队或环境的历史记录。 * **可靠:** 使用追加模式的 spool 文件,以保护数据库临时中断。 **工作原理:** 命令通过 TCP/TLS 以标准化格式发送,并权威地存储在 PostgreSQL 中。身份验证是可插拔的,租户被明确分隔。配置通过 JSON 文件管理,定义监听器、身份验证和数据库设置。 **目前:** hc 支持纯文本和 TLS 摄取、PostgreSQL 存储以及 API 密钥身份验证。未来的开发包括 SQLite 支持和 Web UI。它被设计为一个 *历史记录汇聚点* – 可靠且针对搜索进行了优化,而不是一个完整的 SIEM 或分析平台。 有关详细的设置和使用方法,请参阅原始博客文章。

## Hc:一个集中式 Shell 历史工具 Hc 是一款新型的、无代理工具,专为经常在多台服务器上工作的工程师设计。它解决了命令历史记录丢失的问题,为 Shell 活动创建了一个集中的、可搜索的“大脑”,即使是在临时机器上。 与传统的日志记录方法不同,Hc 通过连接代理*实时*捕获终端会话——无需在远程主机上进行安装或配置更改。这提供了高保真度的按键和输出记录。 Hc 还具有“多租户”架构,可以根据项目或组织标签自动整理历史记录,使工作井然有序。重要的是,访问方式直接从命令行进行,避免了繁琐的 Web UI。 本质上,Hc 旨在将您的整个终端历史记录变成一个个人、可搜索的知识库,随时可以访问。有关项目的更多详细信息,请参见项目博客:[https://carminatialessandro.blogspot.com/2026/01/hc-agentles...](https://carminatialessandro.blogspot.com/2026/01/hc-agentles...)
相关文章

原文

The "Boring" Shell History Sink for Distributed Fleets.

hc is a lightweight service that centralizes shell history from across your infrastructure into a PostgreSQL backend.

If you manage 50+ servers, your shell history is fragmented. Finding "that one command that worked" usually involves a dozen SSH sessions and a lot of grep.

hc solves this by providing a centralized, multi-tenant sink that is:

  • Agentless: Only requires curl, wget, or socat on the client. No binaries to install on production nodes.
  • SSL-Native: Secure ingestion via TLS with support for Client Certificate and API Key authentication.
  • Grep-Friendly: Fetch your history as plaintext via HTTPS and pipe it directly into your local tools.
  • Multi-tenant: Isolate history by team or environment using API keys.

More on this story on this article from my blog.

(shell) -- TCP / TLS --> hc
                         |
                         +-- spool file (append-only, per-tenant)
                         |
                         +-- PostgreSQL (authoritative storage)
                                |
                                +-- HTTP(S) export (text)

Key points:

  • The database is authoritative
  • The spool file exists as a safety net (temporary DB outages, restart recovery)
  • No in-memory history representation is kept
  • All commands are preserved (no deduplication)
  • Plain TCP: trusted network only as the command is cleartext
  • TLS (SSL): recommended, your data is protected and you can authenticate the server using SSL
  • BusyBox / ash supported
  • Bash supported
  • HTTP: unauthenticated and clear text, can be CIDR-limited. The API key can be used, but it is planned to be removed.
  • HTTPS: Ciphered authentication-capable using API key or /Client side certificate (support in progress)
  • Output is plain text, ANSI colored and optimized for grep

Command Format (Ingestion Line)

Each ingested command must be sent as one single line:

YYYYMMDD.HHMMSS - SESSIONID - host.example.com [cwd=/path] > command args...

Example

20240101.120305 - a1b2c3d4 - host.example.com [cwd=/root] > ls -la

The session ID:

  • groups commands belonging to the same shell session
  • is generated client-side
  • is not guaranteed globally unique, but collisions are very unlikely

hc supports API key basd tenant resolution.

An API key:

  • identifies a tenant
  • is embedded directly into the ingested command line
  • is removed before storage (never stored in DB or spool)

Example:

hc_9f3a1c2d.QmFzZTY0U2VjcmV0U3RyaW5n

Embedding API keys in ingestion lines

The API key must appear at the beginning of the command payload, wrapped like this:

]apikey[<key_id>.<secret>] command...

Example:

]apikey[hc_9f3a1c2d.QmFzZTY0U2VjcmV0] make build

Notes:

  • The API key is used only for authentication
  • It is stripped from the command before storing
  • If authentication fails, falls back to the next authentication method. If none succeeds, the line is dropped

Use the api_key verb on the server:

./hc.app apy_key \
        -config hc-config.json \
        -loglevel info \
        -api_tenantid 11111111-1111-1111-1111-111111111111 \
        -api_userid 00000000-0000-0000-0000-000000000001

Output example:

tenant_id: 11111111-1111-1111-1111-111111111111
key_id:    hc_9f3a1c2d
api_key:   hc_9f3a1c2d.QmFzZTY0U2VjcmV0U3RyaW5n
note: api_key is shown only now; store it safely.

Notes:

  • The secret is never shown again.
  • The current state does not allows to create tenantIDs or userIDs. as for v0.3 the operation is still manual on the db

Basic (plain TCP, no API key)

export SESSION_ID_HC=$(date +%Y%m%d.%H%M%S | sha1sum | cut -c1-8)
export PROMPT_COMMAND='echo "$(date +%Y%m%d.%H%M%S) - ${SESSION_ID_HC} - $(hostname --fqdn) [cwd=$(pwd)] > $(history -w /dev/stdout | tail -n1)" | nc hc.example.com 12345'

TLS ingestion with API key (recommended)

export SESSION_ID_HC=$(date +%Y%m%d.%H%M%S | sha1sum | cut -c1-8)
export APIKEY_HC="hc_9f3a1c2d.QmFzZTY0U2VjcmV0U3RyaW5n"

export PROMPT_COMMAND='echo "$(date +%Y%m%d.%H%M%S) - ${SESSION_ID_HC} - $(hostname --fqdn) [cwd=$(pwd)] > ]apikey[${APIKEY_HC}] $(history -w /dev/stdout | tail -n1)" | socat - OPENSSL:hc.example.com:1235,verify=0'

Notes:

  • socat is used instead of nc to support TLS
  • BusyBox ash users may need different hooks (see blog link )

Fetching History (Text Export)

History is fetched as plain text, designed to be piped to grep.

Examples:

wget "http://hc.example.com:8080/export?grep1=qemu&grep2=aarch64&grep3=centos&session=8f7f1b24&color=always" -O -
wget --header="Authorization: Bearer hc_9f3a1c2d.QmFzZTY0U2VjcmV0U3RyaW5n"  "https://hc.example.com:8443/export?grep1=make&grep2=test&color=always" -O - -q

  • https, client certificate
wget --certificate=client.example.com.crt --private-key=client.example.com.key "https://hc.example.com:8443/export?grep1=make&grep2=test&color=always" -O - -q

Supported query parameters

  • grep1, grep2, grep3: regex filters (ordered)
  • session: restrict to a specific session ID
  • color=always|never|auto ANSI color text
  • limit
  • order=asc|desc (ingestion order)

Output format mirrors the ingestion format for familiarity.

  • Ingestion listeners: plain TCP + TLS
  • Export over HTTP / HTTPS
  • Authentication is pluggable and ordered
  • Tenants, ACLs, and auth are clearly separated (Postgres is currently the primary backend; SQLite support is planned as a lightweight alternative.)

Configuration File (hc-config.json)

hc is configured using a single JSON configuration file. The configuration defines listeners, authentication behavior, database backend, and operational limits.

At a high level:

  • server defines all exposed services:
    • listner_clear / listner_tls control command ingestion over plain TCP and TLS.
    • http / https control text export endpoints.
    • Each service can be independently enabled and bound to a specific address.
    • Authentication methods (auth) are evaluated in order, and the first successful one assigns the tenant.
  • tenants defines known tenants, their identifiers, and optional ACL rules.
  • db specifies the database backend (currently PostgreSQL).
  • tls specifies certificate and key files used by TLS ingestion and HTTPS export.
  • limits defines safety limits (for example, maximum accepted line size).
  • Export controls global export limits (maximum rows and execution time).

The configuration is intentionally explicit: authentication, authorization, and transport are configured separately to keep the model understandable and extensible. A full example configuration is provided in hc-config.json and is meant to be copied and edited rather than generated.

Database Quick Start (PostgreSQL)

hc uses PostgreSQL as its authoritative storage backend. The schema is intentionally simple and append-only.

To initialize the database:

createdb history
psql history < pg_schema.sql

The schema (pg_schema.sql) creates:

  • tenants: logical isolation units
  • cmd_events: all ingested commands (authoritative history)
  • api_keys: API keys used for authentication
  • app_users: future-facing user metadata (not required for ingestion)

After schema creation, at least one tenant must exist. You can insert it manually or let hc create it during bootstrap (depending on configuration). API keys are generated using the api_key verb and stored hashed in the database.

The database is designed so that:

  • all ingested commands are preserved
  • ingestion order is maintained via a sequence number
  • old data from text only storage <v0.1.19 can be exported, filtered, and reprocessed safely

Temporary tenantID and userID creation

Since there's still no function to create tenants and users it must fall back to manual insertion into the database.

Example

insert into tenants values ('11111111-1111-1111-1111-111111111111', 'default');
insert into app_users (id, tenant_id, username, created_at) values ('00000000-0000-0000-0000-000000000001', '11111111-1111-1111-1111-111111111111', 'username', now());

Handling Firewalls & NAT (The SSH Tunnel Method) Hack of the day

Sometimes your target server is behind a restrictive firewall or a VPN and cannot "see" the hc collector directly.

In the example.bash, there is a bootstrap script that:

  • Logs into the remote server via SSH.
  • Sets up a Reverse SSH Tunnel so the remote server can "talk back" to your collector through the established SSH connection.
  • Installs the PROMPT_COMMAND hook automatically.

This allows you to collect history from servers that have zero inbound or outbound access to the collector's network, as long as you can SSH into them from your workstation without leaving a single byte of configuration on the remote host.

  • [OK] Plain + TLS ingestion
  • [OK] PostgreSQL storage
  • [OK] API key authentication
  • [OK] Text export over HTTP
  • [OK] HTTPS export auth (client certs, API keys)
  • [WIP] SQLite support
  • [WIP] Web UI (optional, not a priority)

hc is intentionally not:

  • a SIEM
  • a real-time analytics engine
  • a web-heavy UI product

It is:

  • a history sink
  • optimized for grep
  • reliable, inspectable, and boring
联系我们 contact @ memedata.com