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
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.git
.github
.env
data
vault
59 changes: 59 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copy to .env and set real values. Keep .env at mode 0600: chmod 600 .env
# Generate secrets with: openssl rand -base64 48

# Site
SITE_NAME=Example Blog
SITE_URL=https://example.com
DEFAULT_LANG=en
GITHUB_URL=https://github.com/example

# S3 (Garage). Read-only key. HTTPS only.
S3_ENDPOINT=https://s3.example.com
S3_REGION=garage
S3_BUCKET=example-blog
S3_ACCESS_KEY=change-me
S3_SECRET_KEY=change-me
POLL_INTERVAL=60s

# Secrets, at least 32 bytes each
REINDEX_TOKEN=change-me-to-at-least-32-random-bytes
ALTCHA_HMAC_KEY=change-me-to-at-least-32-random-bytes
VISITOR_SECRET=change-me-to-at-least-32-random-bytes

# ALTCHA
ALTCHA_EXPIRES=5m
ALTCHA_MAX_NUMBER=100000

# Content
READING_WPM=200
POPULAR_DAYS=30
POPULAR_LIMIT=5
PAGE_SIZE=10
RSS_LIMIT=20
DEFAULT_OG_IMAGE=
INDEXNOW_KEY=

# Logs: debug | info | warn | error; json | text
LOG_LEVEL=info
LOG_FORMAT=json

# Paths inside the container
DATA_DIR=/data
VAULT_DIR=/vault

# Rate limits per client: count/period
RATE_LIMIT_VOTE=10/1m
RATE_LIMIT_BEACON=30/1m
RATE_LIMIT_SEARCH=30/1m
RATE_LIMIT_REINDEX=5/1m

# Comma-separated CIDRs. Empty = loopback and private ranges.
METRICS_ALLOW=
REINDEX_ALLOW=
# Proxy IPs/CIDRs allowed to set X-Forwarded-For. Empty = trust no proxy.
TRUSTED_PROXIES=

# Bucket limits
MAX_POST_BYTES=1048576
MAX_ASSET_BYTES=52428800
MAX_OBJECTS=10000
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: ci

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
- run: test -z "$(gofmt -l .)"
- run: go vet ./...
- run: go run honnef.co/go/tools/cmd/staticcheck@v0.8.2 ./...
- run: go run golang.org/x/vuln/cmd/govulncheck@v1.9.0 ./...
- run: go test -race ./...

docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: docker build -t hyperhack:ci .
31 changes: 31 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: codeql

on:
push:
branches: [main]
pull_request:
schedule:
- cron: "17 3 * * 1"

permissions:
contents: read

jobs:
analyze:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
- uses: github/codeql-action/init@1fc58d04a4d4374f921bdac573c3811c240f9c26 # v4.39.2
with:
languages: go
build-mode: manual
- run: go build ./...
- uses: github/codeql-action/analyze@1fc58d04a4d4374f921bdac573c3811c240f9c26 # v4.39.2
50 changes: 50 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: release

on:
push:
tags: ["v*.*.*"]

permissions:
contents: read

jobs:
image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Image name
run: echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
- id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ${{ env.IMAGE }}
# Semver tags only. No latest, so a tag is never overwritten.
flavor: latest=false
tags: type=semver,pattern={{version}}
- 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 }}
- id: push
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
# Storage records need an organization-owned repo.
create-storage-record: false
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.env
data/
vault/
/hyperhack
/dist/
72 changes: 72 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# CLAUDE.md

## Source of truth

- `SPEC.md` is the source of truth for this project. Read it before every task.
- `SPEC.ru.md` is the Russian copy. Keep both files identical in content.
- A change to any decision goes into both SPEC files and their Decision log, in the same PR.
- If the SPEC is unclear or contradicts itself, stop and ask. Do not guess.
- Do not add features that are not in the SPEC. Propose them instead.

## Language

- Everything in English: code, comments, commit messages, PR text, docs, UI strings.
- Every doc has a Russian copy: `README.md` + `README.ru.md`, `SPEC.md` + `SPEC.ru.md`. A doc change updates both.
- Russian UI strings live in the i18n files next to the English ones.

## Stack

- Go 1.27 (`go 1.27`, `toolchain go1.27.3` in `go.mod`). Check go.dev/dl for a newer patch before starting.
- Standard library first. Add a dependency only when the SPEC names it or the stdlib cannot do the job in a reasonable amount of code.
- Pin every dependency version. Verify that a package exists and is maintained before adding it.

## Work in stages

One stage = one branch = one PR. Do not start the next stage before the current PR is merged.

1. Skeleton: config from env, `log/slog` logging, HTTP server with timeouts, `/healthz`, Dockerfile, `docker-compose.yml`, `.env.example`, `.gitignore`, CI.
2. S3 sync: polling, `/reindex`, local mirror via `os.Root`, frontmatter parsing, Markdown render with sanitizing.
3. Pages: main page, post page, top bar, themes, languages, templates, styles.
4. Search, tags, RSS, SEO files.
5. Metrics: beacon, views, votes, ALTCHA, rate limits, `/metrics`.
6. Grafana dashboard, `Caddyfile.example`, full README in both languages.

## Git

- Branches: `feat/…`, `fix/…`, `refactor/…`, `docs/…`, `chore/…`. Never commit to `main` directly.
- Conventional Commits: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`, `test:`, `ci:`.
- Small commits, one logical change each.
- Never delete branches or commits. Use `git revert`. No rebase or force push on pushed branches.
- Never mention AI, Claude or any assistant in commits, PRs, docs or code comments.

## Security rules

- Follow SPEC → Security for every change.
- Never commit secrets, real domains, bucket names or keys. Real values live only in `.env`. Examples use `example.com`.
- Never log secrets, raw IPs or visitor hashes.
- Validate all input at trust boundaries: HTTP requests, bucket objects, frontmatter, cookies.
- New endpoint → add it to the SPEC endpoint table with its access and protection.

## Quality checks

Run before every commit. Do not say a task is done until all pass:

```
go vet ./...
staticcheck ./...
govulncheck ./...
go test -race ./...
```

- New non-trivial logic gets tests. Parsers and decoders also get Go fuzz tests.
- Security code (sanitizer, path checks, rate limits, ALTCHA, Origin check) always gets tests.

## Docker

- Bind mounts to subfolders only (`./data`, `./vault`). Never named volumes.
- Static binary, distroless or `scratch` runtime image, non-root user. See SPEC → Container.

## Communication

- Short, direct answers. No filler.
- When a task is done: what changed, what was not checked, any risk.
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# syntax=docker/dockerfile:1
FROM golang:1.27.3@sha256:2920bb124f1a2e12a1d39f0cab756ae0068441ee1da7655dec7f5e374944095b AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/hyperhack .

FROM gcr.io/distroless/static-debian13:nonroot@sha256:25ac8f0c3b2334a9eb05452266bd4697864a8c8ef0ef71e5e6480d7f92868329
COPY --from=build /out/hyperhack /hyperhack
USER 10001:10001
EXPOSE 8080
ENTRYPOINT ["/hyperhack"]
36 changes: 36 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Security policy

Russian version: [SECURITY.ru.md](SECURITY.ru.md).

## Reporting a vulnerability

Please report vulnerabilities privately through GitHub private vulnerability reporting:

1. Open the repository's **Security** tab.
2. Click **Report a vulnerability**.

Direct link: <https://github.com/Mimic890/hyperhack/security/advisories/new>

Do not open a public issue, pull request or discussion for a vulnerability.

Please include:

- what is affected and the impact;
- steps to reproduce or a proof of concept;
- the version (image tag or commit).

## What happens next

- I reply within 7 days.
- I confirm the issue, prepare a fix and publish a new release.
- The advisory is published after the fix is released. You are credited unless you ask not to be.

## Supported versions

Only the latest release gets security fixes.

## Scope

In scope: the `hyperhack` service, its container image and this repository's CI.

Out of scope: Caddy, Garage, VictoriaMetrics, Grafana and other external services, unless the issue is in the configuration this repository ships (`docker-compose.yml`, `Caddyfile.example`).
36 changes: 36 additions & 0 deletions SECURITY.ru.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Политика безопасности

English version: [SECURITY.md](SECURITY.md).

## Как сообщить об уязвимости

Сообщайте об уязвимостях закрыто, через приватные отчёты об уязвимостях GitHub:

1. Откройте вкладку **Security** в репозитории.
2. Нажмите **Report a vulnerability**.

Прямая ссылка: <https://github.com/Mimic890/hyperhack/security/advisories/new>

Не открывайте публичный issue, pull request или обсуждение об уязвимости.

Укажите:

- что затронуто и чем это опасно;
- шаги воспроизведения или proof of concept;
- версию (тег образа или коммит).

## Что дальше

- Я отвечу в течение 7 дней.
- Я подтвержу проблему, подготовлю исправление и выпущу новый релиз.
- Advisory публикуется после выхода исправления. Вас укажут в благодарностях, если вы не попросите иначе.

## Поддерживаемые версии

Исправления безопасности получает только последний релиз.

## Область

Входит: сервис `hyperhack`, его образ контейнера и CI этого репозитория.

Не входит: Caddy, Garage, VictoriaMetrics, Grafana и другие внешние сервисы, если проблема не в конфигурации из этого репозитория (`docker-compose.yml`, `Caddyfile.example`).
Loading
Loading